sclab 0.1.7__py3-none-any.whl → 0.3.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. sclab/__init__.py +3 -1
  2. sclab/_io.py +83 -12
  3. sclab/_methods_registry.py +65 -0
  4. sclab/_sclab.py +241 -21
  5. sclab/dataset/_dataset.py +4 -6
  6. sclab/dataset/processor/_processor.py +41 -19
  7. sclab/dataset/processor/_results_panel.py +94 -0
  8. sclab/dataset/processor/step/_processor_step_base.py +12 -6
  9. sclab/examples/processor_steps/__init__.py +8 -0
  10. sclab/examples/processor_steps/_cluster.py +2 -2
  11. sclab/examples/processor_steps/_differential_expression.py +329 -0
  12. sclab/examples/processor_steps/_doublet_detection.py +68 -0
  13. sclab/examples/processor_steps/_gene_expression.py +125 -0
  14. sclab/examples/processor_steps/_integration.py +116 -0
  15. sclab/examples/processor_steps/_neighbors.py +26 -6
  16. sclab/examples/processor_steps/_pca.py +13 -8
  17. sclab/examples/processor_steps/_preprocess.py +52 -25
  18. sclab/examples/processor_steps/_qc.py +24 -8
  19. sclab/examples/processor_steps/_umap.py +2 -2
  20. sclab/gui/__init__.py +0 -0
  21. sclab/gui/components/__init__.py +7 -0
  22. sclab/gui/components/_guided_pseudotime.py +482 -0
  23. sclab/gui/components/_transfer_metadata.py +186 -0
  24. sclab/methods/__init__.py +50 -0
  25. sclab/preprocess/__init__.py +26 -0
  26. sclab/preprocess/_cca.py +176 -0
  27. sclab/preprocess/_cca_integrate.py +109 -0
  28. sclab/preprocess/_filter_obs.py +42 -0
  29. sclab/preprocess/_harmony.py +421 -0
  30. sclab/preprocess/_harmony_integrate.py +53 -0
  31. sclab/preprocess/_normalize_weighted.py +65 -0
  32. sclab/preprocess/_pca.py +51 -0
  33. sclab/preprocess/_preprocess.py +155 -0
  34. sclab/preprocess/_qc.py +38 -0
  35. sclab/preprocess/_rpca.py +116 -0
  36. sclab/preprocess/_subset.py +208 -0
  37. sclab/preprocess/_transfer_metadata.py +196 -0
  38. sclab/preprocess/_transform.py +82 -0
  39. sclab/preprocess/_utils.py +96 -0
  40. sclab/scanpy/__init__.py +0 -0
  41. sclab/scanpy/_compat.py +92 -0
  42. sclab/scanpy/_settings.py +526 -0
  43. sclab/scanpy/logging.py +290 -0
  44. sclab/scanpy/plotting/__init__.py +0 -0
  45. sclab/scanpy/plotting/_rcmod.py +73 -0
  46. sclab/scanpy/plotting/palettes.py +221 -0
  47. sclab/scanpy/readwrite.py +1108 -0
  48. sclab/tools/__init__.py +0 -0
  49. sclab/tools/cellflow/__init__.py +0 -0
  50. sclab/tools/cellflow/density_dynamics/__init__.py +0 -0
  51. sclab/tools/cellflow/density_dynamics/_density_dynamics.py +349 -0
  52. sclab/tools/cellflow/pseudotime/__init__.py +0 -0
  53. sclab/tools/cellflow/pseudotime/_pseudotime.py +336 -0
  54. sclab/tools/cellflow/pseudotime/timeseries.py +226 -0
  55. sclab/tools/cellflow/utils/__init__.py +0 -0
  56. sclab/tools/cellflow/utils/density_nd.py +215 -0
  57. sclab/tools/cellflow/utils/interpolate.py +334 -0
  58. sclab/tools/cellflow/utils/periodic_genes.py +106 -0
  59. sclab/tools/cellflow/utils/smoothen.py +124 -0
  60. sclab/tools/cellflow/utils/times.py +55 -0
  61. sclab/tools/differential_expression/__init__.py +7 -0
  62. sclab/tools/differential_expression/_pseudobulk_edger.py +309 -0
  63. sclab/tools/differential_expression/_pseudobulk_helpers.py +290 -0
  64. sclab/tools/differential_expression/_pseudobulk_limma.py +257 -0
  65. sclab/tools/doublet_detection/__init__.py +5 -0
  66. sclab/tools/doublet_detection/_scrublet.py +64 -0
  67. sclab/tools/embedding/__init__.py +0 -0
  68. sclab/tools/imputation/__init__.py +0 -0
  69. sclab/tools/imputation/_alra.py +135 -0
  70. sclab/tools/labeling/__init__.py +6 -0
  71. sclab/tools/labeling/sctype.py +233 -0
  72. sclab/tools/utils/__init__.py +5 -0
  73. sclab/tools/utils/_aggregate_and_filter.py +290 -0
  74. sclab/utils/__init__.py +5 -0
  75. sclab/utils/_write_excel.py +510 -0
  76. {sclab-0.1.7.dist-info → sclab-0.3.4.dist-info}/METADATA +29 -12
  77. sclab-0.3.4.dist-info/RECORD +93 -0
  78. {sclab-0.1.7.dist-info → sclab-0.3.4.dist-info}/WHEEL +1 -1
  79. sclab-0.3.4.dist-info/licenses/LICENSE +29 -0
  80. sclab-0.1.7.dist-info/RECORD +0 -30
@@ -0,0 +1,510 @@
1
+ import math
2
+ import re
3
+ from collections import defaultdict
4
+ from pathlib import Path
5
+ from typing import Literal, Sequence
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ _column_format_type = Literal[
11
+ "text", "bold", "number", "scientific", "percentage", "nodecimal", "boolean"
12
+ ]
13
+
14
+
15
+ def write_excel(
16
+ dataframes: pd.DataFrame | dict[str, pd.DataFrame],
17
+ file_path: str | Path,
18
+ sortby: str | list[str] | None = None,
19
+ sort_ascending: bool | list[bool] = None,
20
+ autofilter: bool = True,
21
+ guess_formats: bool = True,
22
+ guess_widths: bool = True,
23
+ column_formats: dict[str, _column_format_type] | None = None,
24
+ column_widths: dict[str, int] | None = None,
25
+ verbose: bool = False,
26
+ ) -> None:
27
+ """
28
+ Write pandas DataFrame(s) to an Excel file with enhanced formatting and styling.
29
+
30
+ This function provides advanced Excel writing capabilities including automatic format detection,
31
+ column width adjustment, conditional formatting, and table headers with autofilter.
32
+
33
+ Parameters
34
+ ----------
35
+ dataframes : pandas.DataFrame or dict[str, pandas.DataFrame]
36
+ Single DataFrame or dictionary of DataFrames where keys are sheet names.
37
+ file_path : str or pathlib.Path
38
+ Path where the Excel file will be saved.
39
+ sortby : str or None, optional
40
+ Column name to sort the data by. If specified, applies 3-color scale conditional formatting
41
+ to the sorted column. Default is None.
42
+ sort_ascending : bool, optional
43
+ Sort order when sortby is specified. Default is True.
44
+ autofilter : bool, optional
45
+ Whether to add filter buttons to column headers. Default is True.
46
+ guess_formats : bool, optional
47
+ Whether to automatically detect and apply appropriate formats based on column data types.
48
+ Default is True.
49
+ guess_widths : bool, optional
50
+ Whether to automatically estimate and apply column widths based on column name/contents.
51
+ Default is True.
52
+ column_formats : dict[str, str] or None, optional
53
+ Custom format specifications for columns. Keys are column names, values are format types:
54
+ "text", "bold", "number", "scientific", "percentage", "nodecimal", or "boolean".
55
+ Default is None.
56
+ column_widths : dict[str, int] or None, optional
57
+ Custom width specifications for columns. Keys are column names, values are widths in
58
+ Excel units. Default is None.
59
+ verbose : bool, optional
60
+ Whether to print detailed information about column formatting. Default is False.
61
+
62
+ Returns
63
+ -------
64
+ None
65
+
66
+ Notes
67
+ -----
68
+ - If a single DataFrame is provided, it will be written to 'Sheet1'
69
+ - Percentage columns get conditional formatting based on their values
70
+ - The function validates file writability and format specifications before proceeding
71
+ - Column formats and widths are either explicitly specified or automatically detected
72
+ """
73
+ if not isinstance(sortby, list):
74
+ sortby = [sortby]
75
+
76
+ if sort_ascending is None:
77
+ sort_ascending = [True]
78
+
79
+ if not isinstance(sort_ascending, list):
80
+ sort_ascending = [sort_ascending]
81
+
82
+ if column_formats is None:
83
+ column_formats = {}
84
+ else:
85
+ _validate_column_formats(column_formats)
86
+
87
+ if column_widths is None:
88
+ column_widths = {}
89
+ else:
90
+ _validate_column_widths(column_widths)
91
+
92
+ if not isinstance(dataframes, dict):
93
+ dataframes = {"Sheet1": dataframes}
94
+
95
+ file_path = Path(file_path)
96
+
97
+ if not _is_writable(file_path):
98
+ print(f"File {file_path} is not writable. Please make sure it's not in use.")
99
+ return
100
+
101
+ with pd.ExcelWriter(file_path) as writer:
102
+ for sheet_name, df in dataframes.items():
103
+ df = df.reset_index()
104
+
105
+ _sortby = [c for c in sortby if c in df.columns]
106
+ _sort_ascending = [a for a in sort_ascending if a]
107
+ if not _sort_ascending:
108
+ _sort_ascending = True
109
+ if _sortby:
110
+ df = df.sort_values(by=_sortby, ascending=_sort_ascending)
111
+
112
+ df.to_excel(
113
+ writer, sheet_name=sheet_name, index=False, startrow=1, header=False
114
+ )
115
+ max_row, max_col = df.shape
116
+
117
+ workbook = writer.book
118
+ formats = _get_formats_dict(workbook)
119
+
120
+ worksheet = writer.sheets[sheet_name]
121
+
122
+ for i, col in enumerate(df.columns):
123
+ if col in column_formats:
124
+ column_format = column_formats[col]
125
+ elif guess_formats:
126
+ column_format = _guess_column_format(df[col])
127
+ else:
128
+ column_format = "noformat"
129
+
130
+ if col in column_widths:
131
+ column_width = column_widths[col]
132
+ elif guess_widths:
133
+ try:
134
+ column_width = _guess_column_width(df[col], column_format)
135
+ except ValueError:
136
+ column_width = 10
137
+ else:
138
+ column_width = 10
139
+
140
+ fmt = formats[column_format]
141
+ worksheet.set_column(i, i, column_width, fmt)
142
+
143
+ if verbose:
144
+ _print_column_info(
145
+ i, sheet_name, col, column_format, column_width, max_col
146
+ )
147
+
148
+ if column_format == "percentage":
149
+ fmt = _make_percentage_conditional_format()
150
+ worksheet.conditional_format(0, i, max_row, i, fmt)
151
+
152
+ if column_format == "boolean":
153
+ fmt = _make_cell_color_conditional_format(
154
+ "==", "TRUE", formats["cellGreen"]
155
+ )
156
+ worksheet.conditional_format(0, i, max_row, i, fmt)
157
+
158
+ # if col in sortby:
159
+ # fmt = _make_3_color_scale_conditional_format(df[col])
160
+ # worksheet.conditional_format(0, i, max_row, i, fmt)
161
+
162
+ if m := re.match(
163
+ r"^number_(Blue|Green|Red)(Blue|Green|Red)\|?([\-\+\d.]+)?,?([\-\+\d.]+)?,?([\-\+\d.]+)?$",
164
+ column_format,
165
+ ):
166
+ min_color = m.group(1)
167
+ max_color = m.group(2)
168
+
169
+ if m.group(3):
170
+ min_value = float(m.group(3))
171
+ else:
172
+ min_value = None
173
+
174
+ if m.group(4):
175
+ mid_value = float(m.group(4))
176
+ else:
177
+ mid_value = None
178
+
179
+ if m.group(5):
180
+ max_value = float(m.group(5))
181
+ else:
182
+ max_value = None
183
+
184
+ fmt = _make_3_color_scale_conditional_format(
185
+ df[col],
186
+ min_color=min_color,
187
+ mid_color="White",
188
+ max_color=max_color,
189
+ min_value=min_value,
190
+ mid_value=mid_value,
191
+ max_value=max_value,
192
+ )
193
+ worksheet.conditional_format(0, i, max_row, i, fmt)
194
+
195
+ if m := re.match(
196
+ r"^number_(Blue|Green|Red)(_desc)?\|?([\-\+\d.]+)?,?([\-\+\d.]+)?$",
197
+ column_format,
198
+ ):
199
+ desc = m.group(2) == "_desc"
200
+ if desc:
201
+ min_color = "White"
202
+ max_color = m.group(1)
203
+ else:
204
+ min_color = m.group(1)
205
+ max_color = "White"
206
+
207
+ if m.group(3):
208
+ min_value = float(m.group(3))
209
+ else:
210
+ min_value = None
211
+
212
+ if m.group(4):
213
+ max_value = float(m.group(4))
214
+ else:
215
+ max_value = None
216
+
217
+ fmt = _make_2_color_scale_conditional_format(
218
+ df[col],
219
+ min_color=min_color,
220
+ max_color=max_color,
221
+ min_value=min_value,
222
+ max_value=max_value,
223
+ )
224
+ # print(col, df[col].dtype, df[col].iloc[:5].to_list(), fmt)
225
+ worksheet.conditional_format(0, i, max_row, i, fmt)
226
+
227
+ if column_format == "3color":
228
+ fmt = _make_3_color_scale_conditional_format(df[col])
229
+ worksheet.conditional_format(0, i, max_row, i, fmt)
230
+
231
+ _write_table_header(df.columns, worksheet, formats["header_format"])
232
+
233
+ if autofilter:
234
+ worksheet.autofilter(0, 0, max_row - 1, max_col - 1)
235
+
236
+
237
+ def _color(color: str):
238
+ match color:
239
+ case "Blue":
240
+ return "#B3CDE3"
241
+ case "Green":
242
+ return "#B3E3B3"
243
+ case "Red":
244
+ return "#FFB3B3"
245
+ case "White":
246
+ return "#F7F7F7"
247
+ case _:
248
+ raise ValueError(f"Invalid color: {color}")
249
+
250
+
251
+ def _validate_column_formats(column_formats: dict[str, str]):
252
+ assert isinstance(column_formats, dict), "column_formats must be a dict"
253
+ for col, fmt in column_formats.items():
254
+ if re.match(
255
+ r"^number_(Blue|Green|Red)(Blue|Green|Red)?(_desc)?\|?([\-\+\d.]+)?,?([\-\+\d.]+)?,?([\-\+\d.]+)?$",
256
+ fmt,
257
+ ):
258
+ continue
259
+ else:
260
+ assert fmt in (
261
+ "text",
262
+ "bold",
263
+ "number",
264
+ "scientific",
265
+ "percentage",
266
+ "nodecimal",
267
+ "boolean",
268
+ ), f"Unknown format: {fmt}"
269
+
270
+
271
+ def _validate_column_widths(column_widths: dict[str, int]):
272
+ assert isinstance(column_widths, dict), "column_widths must be a dict"
273
+ for col, width in column_widths.items():
274
+ assert isinstance(width, int | float), f"Invalid column width: {width}"
275
+ assert width > 0, f"Invalid column width: {width}"
276
+
277
+
278
+ def _is_writable(file_path: str | Path) -> None:
279
+ file_path = Path(file_path)
280
+
281
+ if file_path.exists() and file_path.is_file():
282
+ try:
283
+ with open(file_path, "a"):
284
+ return True
285
+ except PermissionError:
286
+ return False
287
+
288
+ return True
289
+
290
+
291
+ def _get_formats_dict(workbook):
292
+ return defaultdict(
293
+ lambda: workbook.add_format(),
294
+ **{
295
+ "noformat": workbook.add_format(),
296
+ "header_format": workbook.add_format(
297
+ {
298
+ "bold": True,
299
+ "text_wrap": True,
300
+ "valign": "top",
301
+ "fg_color": "#FCFCFC",
302
+ "border": 1,
303
+ }
304
+ ),
305
+ "text": workbook.add_format({"font_name": "Calibri", "font_size": 11}),
306
+ "bold": workbook.add_format({"bold": True}),
307
+ "number": workbook.add_format({"num_format": "0.00"}),
308
+ "scientific": workbook.add_format({"num_format": "0.00E+00"}),
309
+ "percentage": workbook.add_format({"num_format": "0.00%"}),
310
+ "nodecimal": workbook.add_format({"num_format": "0"}),
311
+ "boolean": workbook.add_format(
312
+ {
313
+ "bold": True,
314
+ "valign": "center",
315
+ "align": "center",
316
+ "font_name": "Monospace",
317
+ "font_size": 11,
318
+ }
319
+ ),
320
+ **{
321
+ f"cell{color}": workbook.add_format(
322
+ {
323
+ "bg_color": _color(color),
324
+ }
325
+ )
326
+ for color in ["Blue", "Green", "Red", "White"]
327
+ },
328
+ },
329
+ )
330
+
331
+
332
+ def _guess_column_format(column: pd.Series) -> str:
333
+ if pd.api.types.is_bool_dtype(column):
334
+ return "boolean"
335
+
336
+ if pd.api.types.is_numeric_dtype(column):
337
+ if pd.api.types.is_float_dtype(column):
338
+ eps = np.finfo(column.dtype).eps
339
+
340
+ if (column % 1 == 0).all():
341
+ return "nodecimal"
342
+
343
+ if np.log10(column.abs() + eps).max() > 6:
344
+ return "scientific"
345
+
346
+ if np.log10(column.abs() + eps).min() < -3:
347
+ return "scientific"
348
+
349
+ return "number"
350
+
351
+ return "nodecimal"
352
+
353
+ return "text"
354
+
355
+
356
+ def _guess_column_width(
357
+ column: pd.Series,
358
+ column_format: str,
359
+ step: int = 6,
360
+ min_width: int = 10,
361
+ max_width: int = 75,
362
+ ) -> int:
363
+ # widths should be multiple of 5, minimum 10 and maximum 75
364
+ colname = str(column.name)
365
+ colname_len = len(colname)
366
+ if len(column) == 0:
367
+ return _round_width(colname_len, step, min_width, max_width)
368
+
369
+ match column_format:
370
+ case "text":
371
+ max_len = int(column.str.len().max())
372
+ return _round_width(max(max_len, colname_len), step, min_width, max_width)
373
+
374
+ case "number" | "nodecimal" | "scientific" | "boolean":
375
+ return _round_width(colname_len, step, min_width, max_width)
376
+
377
+ case x if re.match("^number_.*", x):
378
+ return _round_width(colname_len, step, min_width, max_width)
379
+
380
+ case "percentage":
381
+ return _round_width(max(min_width, colname_len), step, min_width, max_width)
382
+
383
+ case _:
384
+ raise ValueError(f"Unknown format: {column_format}")
385
+
386
+
387
+ def _round_width(value: float, step: int, min_width: int, max_width: int) -> int:
388
+ out = math.ceil(value * 1.4)
389
+ return int(max(min_width, min(max_width, out)))
390
+
391
+
392
+ def _print_column_info(i, sheet_name, col, column_format, column_width, max_col):
393
+ if i == 0:
394
+ print()
395
+ print("-" * 55)
396
+ print(f"| Sheet Name: {sheet_name:<39} |")
397
+ print("-" * 55)
398
+ print(f"| {'Column':<20} | {'Format':<20} | {'Width':>5} |")
399
+ print("-" * 55)
400
+ print(f"| {col[:20]:>20} | {column_format:>20} | {column_width:>5} |")
401
+ if i == max_col - 1:
402
+ print("-" * 55)
403
+
404
+
405
+ def _make_percentage_conditional_format():
406
+ return {
407
+ "type": "data_bar",
408
+ "bar_color": "#76C1E1", # Light blue for the bar color
409
+ "min_type": "num", # Define minimum as a numeric value
410
+ "min_value": 0, # Minimum percentage (0%)
411
+ "max_type": "num", # Define maximum as a numeric value
412
+ "max_value": 1, # Maximum percentage (100%)
413
+ }
414
+
415
+
416
+ def _make_cell_color_conditional_format(criteria, value, fmt):
417
+ return {
418
+ "type": "cell",
419
+ "criteria": criteria,
420
+ "value": value,
421
+ "format": fmt,
422
+ }
423
+
424
+
425
+ def _make_3_color_scale_conditional_format(
426
+ series: pd.Series,
427
+ scale: float = 1.5,
428
+ min_color: str = "Blue", # soft blue
429
+ mid_color: str = "White", # off-white
430
+ max_color: str = "Red", # soft red
431
+ min_value: int | float | None = None,
432
+ mid_value: int | float | None = None,
433
+ max_value: int | float | None = None,
434
+ ):
435
+ values = series.loc[np.isfinite(series)]
436
+
437
+ if min_value is None and mid_value is None and max_value is None:
438
+ bound = values.quantile([0, 1]).abs().max() * scale
439
+ min_value = -bound
440
+ mid_value = 0
441
+ max_value = bound
442
+
443
+ if min_value is None:
444
+ min_value = values.min()
445
+
446
+ if max_value is None:
447
+ max_value = values.max()
448
+
449
+ if mid_value is None:
450
+ mid_value = (values.max() + values.min()) / 2
451
+
452
+ if not min_color.startswith("#"):
453
+ min_color = _color(min_color)
454
+
455
+ if not mid_color.startswith("#"):
456
+ mid_color = _color(mid_color)
457
+
458
+ if not max_color.startswith("#"):
459
+ max_color = _color(max_color)
460
+
461
+ return {
462
+ "type": "3_color_scale",
463
+ "min_type": "num", # Can be "num", "percent", or "percentile"
464
+ "min_value": min_value,
465
+ "min_color": min_color,
466
+ "mid_type": "num",
467
+ "mid_value": mid_value,
468
+ "mid_color": mid_color,
469
+ "max_type": "num",
470
+ "max_value": max_value,
471
+ "max_color": max_color,
472
+ }
473
+
474
+
475
+ def _make_2_color_scale_conditional_format(
476
+ series: pd.Series,
477
+ min_color: str = "#F7F7F7",
478
+ max_color: str = "#B3E3B3",
479
+ min_value: int | float | None = None,
480
+ max_value: int | float | None = None,
481
+ ):
482
+ values = series.loc[np.isfinite(series)]
483
+
484
+ if min_value is None:
485
+ min_value = values.quantile(0.05)
486
+
487
+ if max_value is None:
488
+ max_value = values.quantile(0.95)
489
+
490
+ if not min_color.startswith("#"):
491
+ min_color = _color(min_color)
492
+
493
+ if not max_color.startswith("#"):
494
+ max_color = _color(max_color)
495
+
496
+ return {
497
+ "type": "2_color_scale",
498
+ "min_type": "num",
499
+ "min_value": min_value,
500
+ "min_color": min_color,
501
+ "max_type": "num",
502
+ "max_value": max_value,
503
+ "max_color": max_color,
504
+ }
505
+
506
+
507
+ def _write_table_header(column_names: Sequence[str], worksheet, header_format):
508
+ # Write the column headers with the defined format.
509
+ for col_num, value in enumerate(column_names):
510
+ worksheet.write(0, col_num, value, header_format)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: sclab
3
- Version: 0.1.7
3
+ Version: 0.3.4
4
4
  Summary: sclab
5
5
  Author-email: Argenis Arriojas <ArriojasMaldonado001@umb.edu>
6
6
  Requires-Python: >=3.10,<3.13
@@ -10,23 +10,34 @@ Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ License-File: LICENSE
13
14
  Requires-Dist: anndata
14
15
  Requires-Dist: anywidget
15
16
  Requires-Dist: ipywidgets
16
- Requires-Dist: itables
17
+ Requires-Dist: itables<2.4
18
+ Requires-Dist: matplotlib
17
19
  Requires-Dist: numpy<2.2
18
20
  Requires-Dist: pandas
19
- Requires-Dist: plotly
20
- Requires-Dist: scanpy
21
+ Requires-Dist: plotly<6.0
22
+ Requires-Dist: requests
23
+ Requires-Dist: ripser>=0.6.12
21
24
  Requires-Dist: scikit-learn
22
- Requires-Dist: scikit-misc
25
+ Requires-Dist: scipy<1.16
23
26
  Requires-Dist: svgpathtools
27
+ Requires-Dist: tqdm
28
+ Requires-Dist: jupyterlab>=4.3.6 ; extra == "jupyter"
29
+ Requires-Dist: anndata2ri>=1.3 ; extra == "r"
30
+ Requires-Dist: rpy2>=3.5 ; extra == "r"
31
+ Requires-Dist: scanpy[leiden, skmisc]>=1.10 ; extra == "scanpy"
24
32
  Requires-Dist: pytest>=8.3.4 ; extra == "test"
25
33
  Project-URL: Bug Tracker, https://github.com/umbibio/sclab/issues
26
34
  Project-URL: Changelog, https://github.com/umbibio/sclab/blob/main/CHANGELOG.md
27
35
  Project-URL: Documentation, https://github.com/umbibio/sclab/docs
28
36
  Project-URL: Homepage, https://github.com/umbibio/sclab
29
37
  Project-URL: Repository, https://github.com/umbibio/sclab.git
38
+ Provides-Extra: jupyter
39
+ Provides-Extra: r
40
+ Provides-Extra: scanpy
30
41
  Provides-Extra: test
31
42
 
32
43
  # SCLab
@@ -53,9 +64,9 @@ pip install sclab
53
64
  Open a Jupyter Notebook and run the following:
54
65
 
55
66
  ```python
67
+ from IPython.display import display
56
68
  from sclab import SCLabDashboard
57
69
  import scanpy as sc
58
- from IPython.display import display
59
70
 
60
71
  # Load your data
61
72
  adata = sc.read_10x_h5("your_data.h5")
@@ -67,9 +78,14 @@ dashboard = SCLabDashboard(adata, name="My Analysis")
67
78
  display(dashboard)
68
79
 
69
80
  # The dashboard provides easy access to components:
70
- dashboard.ds # Dataset (wrapper for AnnData)
71
- dashboard.pl # Plotter
72
- dashboard.pr # Processor
81
+ # dashboard.ds # Dataset (wrapper for AnnData)
82
+ # dashboard.pl # Plotter
83
+ # dashboard.pr # Processor
84
+
85
+ # the active AnnData object is found within the dataset object:
86
+ # dashboard.ds.adata
87
+
88
+ # by default, the dashboard will update the loaded AnnData object in-place
73
89
  ```
74
90
 
75
91
  ## Components
@@ -78,6 +94,7 @@ dashboard.pr # Processor
78
94
 
79
95
  The main interface that integrates all components with a tabbed layout:
80
96
  - Main graph for visualizations
97
+ - Results panel
81
98
  - Observations table
82
99
  - Genes table
83
100
  - Event logs
@@ -129,10 +146,10 @@ This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICE
129
146
  If you use SCLab in your research, please cite:
130
147
 
131
148
  ```bibtex
132
- @software{sclab2024,
149
+ @software{sclab2025,
133
150
  author = {Arriojas, Argenis},
134
151
  title = {SCLab: Interactive Single-Cell Analysis Toolkit},
135
- year = {2024},
152
+ year = {2025},
136
153
  publisher = {GitHub},
137
154
  url = {https://github.com/umbibio/sclab}
138
155
  }