sclab 0.1.8__py3-none-any.whl → 0.2.3__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.

Potentially problematic release.


This version of sclab might be problematic. Click here for more details.

@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from dataclasses import dataclass, field
5
+ from functools import cache, partial
6
+ from importlib.util import find_spec
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING
9
+
10
+ from packaging.version import Version
11
+
12
+ if TYPE_CHECKING:
13
+ from importlib.metadata import PackageMetadata
14
+
15
+
16
+ if TYPE_CHECKING:
17
+ # type checkers are confused and can only see …core.Array
18
+ from dask.array.core import Array as DaskArray
19
+ elif find_spec("dask"):
20
+ from dask.array import Array as DaskArray
21
+ else:
22
+
23
+ class DaskArray:
24
+ pass
25
+
26
+
27
+ if find_spec("zappy") or TYPE_CHECKING:
28
+ from zappy.base import ZappyArray
29
+ else:
30
+
31
+ class ZappyArray:
32
+ pass
33
+
34
+
35
+ __all__ = [
36
+ "DaskArray",
37
+ "ZappyArray",
38
+ "fullname",
39
+ "pkg_metadata",
40
+ "pkg_version",
41
+ ]
42
+
43
+
44
+ def fullname(typ: type) -> str:
45
+ module = typ.__module__
46
+ name = typ.__qualname__
47
+ if module == "builtins" or module is None:
48
+ return name
49
+ return f"{module}.{name}"
50
+
51
+
52
+ if sys.version_info >= (3, 11):
53
+ from contextlib import chdir
54
+ else:
55
+ import os
56
+ from contextlib import AbstractContextManager
57
+
58
+ @dataclass
59
+ class chdir(AbstractContextManager):
60
+ path: Path
61
+ _old_cwd: list[Path] = field(default_factory=list)
62
+
63
+ def __enter__(self) -> None:
64
+ self._old_cwd.append(Path.cwd())
65
+ os.chdir(self.path)
66
+
67
+ def __exit__(self, *_excinfo) -> None:
68
+ os.chdir(self._old_cwd.pop())
69
+
70
+
71
+ def pkg_metadata(package: str) -> PackageMetadata:
72
+ from importlib.metadata import metadata
73
+
74
+ return metadata(package)
75
+
76
+
77
+ @cache
78
+ def pkg_version(package: str) -> Version:
79
+ from importlib.metadata import version
80
+
81
+ return Version(version(package))
82
+
83
+
84
+ if find_spec("legacy_api_wrap") or TYPE_CHECKING:
85
+ from legacy_api_wrap import legacy_api # noqa: TID251
86
+
87
+ old_positionals = partial(legacy_api, category=FutureWarning)
88
+ else:
89
+ # legacy_api_wrap is currently a hard dependency,
90
+ # but this code makes it possible to run scanpy without it.
91
+ def old_positionals(*old_positionals: str):
92
+ return lambda func: func
@@ -0,0 +1,526 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import sys
5
+ from contextlib import contextmanager
6
+ from enum import IntEnum
7
+ from logging import getLevelName
8
+ from pathlib import Path
9
+ from time import time
10
+ from typing import TYPE_CHECKING
11
+
12
+ from . import logging
13
+ from ._compat import old_positionals
14
+ from .logging import _RootLogger, _set_log_file, _set_log_level
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Generator, Iterable
18
+ from typing import Any, Literal, TextIO
19
+
20
+ # Collected from the print_* functions in matplotlib.backends
21
+ _Format = (
22
+ Literal["png", "jpg", "tif", "tiff"]
23
+ | Literal["pdf", "ps", "eps", "svg", "svgz", "pgf"]
24
+ | Literal["raw", "rgba"]
25
+ )
26
+
27
+ _VERBOSITY_TO_LOGLEVEL = {
28
+ "error": "ERROR",
29
+ "warning": "WARNING",
30
+ "info": "INFO",
31
+ "hint": "HINT",
32
+ "debug": "DEBUG",
33
+ }
34
+ # Python 3.7+ ensures iteration order
35
+ for v, level in enumerate(list(_VERBOSITY_TO_LOGLEVEL.values())):
36
+ _VERBOSITY_TO_LOGLEVEL[v] = level
37
+
38
+
39
+ class Verbosity(IntEnum):
40
+ """Logging verbosity levels."""
41
+
42
+ error = 0
43
+ warning = 1
44
+ info = 2
45
+ hint = 3
46
+ debug = 4
47
+
48
+ def __eq__(self, other: Verbosity | int | str) -> bool:
49
+ if isinstance(other, Verbosity):
50
+ return self is other
51
+ if isinstance(other, int):
52
+ return self.value == other
53
+ if isinstance(other, str):
54
+ return self.name == other
55
+ return NotImplemented
56
+
57
+ @property
58
+ def level(self) -> int:
59
+ # getLevelName(str) returns the int level…
60
+ return getLevelName(_VERBOSITY_TO_LOGLEVEL[self.name])
61
+
62
+ @contextmanager
63
+ def override(
64
+ self, verbosity: Verbosity | str | int
65
+ ) -> Generator[Verbosity, None, None]:
66
+ """\
67
+ Temporarily override verbosity
68
+ """
69
+ settings.verbosity = verbosity
70
+ yield self
71
+ settings.verbosity = self
72
+
73
+
74
+ # backwards compat
75
+ Verbosity.warn = Verbosity.warning
76
+
77
+
78
+ def _type_check(var: Any, varname: str, types: type | tuple[type, ...]):
79
+ if isinstance(var, types):
80
+ return
81
+ if isinstance(types, type):
82
+ possible_types_str = types.__name__
83
+ else:
84
+ type_names = [t.__name__ for t in types]
85
+ possible_types_str = "{} or {}".format(
86
+ ", ".join(type_names[:-1]), type_names[-1]
87
+ )
88
+ raise TypeError(f"{varname} must be of type {possible_types_str}")
89
+
90
+
91
+ class ScanpyConfig:
92
+ """\
93
+ Config manager for scanpy.
94
+ """
95
+
96
+ N_PCS: int
97
+ """Default number of principal components to use."""
98
+
99
+ def __init__(
100
+ self,
101
+ *,
102
+ verbosity: Verbosity | int | str = Verbosity.warning,
103
+ plot_suffix: str = "",
104
+ file_format_data: str = "h5ad",
105
+ file_format_figs: str = "pdf",
106
+ autosave: bool = False,
107
+ autoshow: bool = True,
108
+ writedir: Path | str = "./write/",
109
+ cachedir: Path | str = "./cache/",
110
+ datasetdir: Path | str = "./data/",
111
+ figdir: Path | str = "./figures/",
112
+ cache_compression: str | None = "lzf",
113
+ max_memory=15,
114
+ n_jobs=1,
115
+ logfile: Path | str | None = None,
116
+ categories_to_ignore: Iterable[str] = ("N/A", "dontknow", "no_gate", "?"),
117
+ _frameon: bool = True,
118
+ _vector_friendly: bool = False,
119
+ _low_resolution_warning: bool = True,
120
+ n_pcs=50,
121
+ ):
122
+ # logging
123
+ self._root_logger = _RootLogger(logging.INFO) # level will be replaced
124
+ self.logfile = logfile
125
+ self.verbosity = verbosity
126
+ # rest
127
+ self.plot_suffix = plot_suffix
128
+ self.file_format_data = file_format_data
129
+ self.file_format_figs = file_format_figs
130
+ self.autosave = autosave
131
+ self.autoshow = autoshow
132
+ self.writedir = writedir
133
+ self.cachedir = cachedir
134
+ self.datasetdir = datasetdir
135
+ self.figdir = figdir
136
+ self.cache_compression = cache_compression
137
+ self.max_memory = max_memory
138
+ self.n_jobs = n_jobs
139
+ self.categories_to_ignore = categories_to_ignore
140
+ self._frameon = _frameon
141
+ """bool: See set_figure_params."""
142
+
143
+ self._vector_friendly = _vector_friendly
144
+ """Set to true if you want to include pngs in svgs and pdfs."""
145
+
146
+ self._low_resolution_warning = _low_resolution_warning
147
+ """Print warning when saving a figure with low resolution."""
148
+
149
+ self._start = time()
150
+ """Time when the settings module is first imported."""
151
+
152
+ self._previous_time = self._start
153
+ """Variable for timing program parts."""
154
+
155
+ self._previous_memory_usage = -1
156
+ """Stores the previous memory usage."""
157
+
158
+ self.N_PCS = n_pcs
159
+
160
+ @property
161
+ def verbosity(self) -> Verbosity:
162
+ """
163
+ Verbosity level (default `warning`)
164
+
165
+ Level 0: only show 'error' messages.
166
+ Level 1: also show 'warning' messages.
167
+ Level 2: also show 'info' messages.
168
+ Level 3: also show 'hint' messages.
169
+ Level 4: also show very detailed progress for 'debug'ging.
170
+ """
171
+ return self._verbosity
172
+
173
+ @verbosity.setter
174
+ def verbosity(self, verbosity: Verbosity | int | str):
175
+ verbosity_str_options = [
176
+ v for v in _VERBOSITY_TO_LOGLEVEL if isinstance(v, str)
177
+ ]
178
+ if isinstance(verbosity, Verbosity):
179
+ self._verbosity = verbosity
180
+ elif isinstance(verbosity, int):
181
+ self._verbosity = Verbosity(verbosity)
182
+ elif isinstance(verbosity, str):
183
+ verbosity = verbosity.lower()
184
+ if verbosity not in verbosity_str_options:
185
+ raise ValueError(
186
+ f"Cannot set verbosity to {verbosity}. "
187
+ f"Accepted string values are: {verbosity_str_options}"
188
+ )
189
+ else:
190
+ self._verbosity = Verbosity(verbosity_str_options.index(verbosity))
191
+ else:
192
+ _type_check(verbosity, "verbosity", (str, int))
193
+ _set_log_level(self, _VERBOSITY_TO_LOGLEVEL[self._verbosity.name])
194
+
195
+ @property
196
+ def plot_suffix(self) -> str:
197
+ """Global suffix that is appended to figure filenames."""
198
+ return self._plot_suffix
199
+
200
+ @plot_suffix.setter
201
+ def plot_suffix(self, plot_suffix: str):
202
+ _type_check(plot_suffix, "plot_suffix", str)
203
+ self._plot_suffix = plot_suffix
204
+
205
+ @property
206
+ def file_format_data(self) -> str:
207
+ """File format for saving AnnData objects.
208
+
209
+ Allowed are 'txt', 'csv' (comma separated value file) for exporting and 'h5ad'
210
+ (hdf5) for lossless saving.
211
+ """
212
+ return self._file_format_data
213
+
214
+ @file_format_data.setter
215
+ def file_format_data(self, file_format: str):
216
+ _type_check(file_format, "file_format_data", str)
217
+ file_format_options = {"txt", "csv", "h5ad"}
218
+ if file_format not in file_format_options:
219
+ raise ValueError(
220
+ f"Cannot set file_format_data to {file_format}. "
221
+ f"Must be one of {file_format_options}"
222
+ )
223
+ self._file_format_data = file_format
224
+
225
+ @property
226
+ def file_format_figs(self) -> str:
227
+ """File format for saving figures.
228
+
229
+ For example 'png', 'pdf' or 'svg'. Many other formats work as well (see
230
+ `matplotlib.pyplot.savefig`).
231
+ """
232
+ return self._file_format_figs
233
+
234
+ @file_format_figs.setter
235
+ def file_format_figs(self, figure_format: str):
236
+ _type_check(figure_format, "figure_format_data", str)
237
+ self._file_format_figs = figure_format
238
+
239
+ @property
240
+ def autosave(self) -> bool:
241
+ """\
242
+ Automatically save figures in :attr:`~scanpy._settings.ScanpyConfig.figdir` (default `False`).
243
+
244
+ Do not show plots/figures interactively.
245
+ """
246
+ return self._autosave
247
+
248
+ @autosave.setter
249
+ def autosave(self, autosave: bool):
250
+ _type_check(autosave, "autosave", bool)
251
+ self._autosave = autosave
252
+
253
+ @property
254
+ def autoshow(self) -> bool:
255
+ """\
256
+ Automatically show figures if `autosave == False` (default `True`).
257
+
258
+ There is no need to call the matplotlib pl.show() in this case.
259
+ """
260
+ return self._autoshow
261
+
262
+ @autoshow.setter
263
+ def autoshow(self, autoshow: bool):
264
+ _type_check(autoshow, "autoshow", bool)
265
+ self._autoshow = autoshow
266
+
267
+ @property
268
+ def writedir(self) -> Path:
269
+ """\
270
+ Directory where the function scanpy.write writes to by default.
271
+ """
272
+ return self._writedir
273
+
274
+ @writedir.setter
275
+ def writedir(self, writedir: Path | str):
276
+ _type_check(writedir, "writedir", (str, Path))
277
+ self._writedir = Path(writedir)
278
+
279
+ @property
280
+ def cachedir(self) -> Path:
281
+ """\
282
+ Directory for cache files (default `'./cache/'`).
283
+ """
284
+ return self._cachedir
285
+
286
+ @cachedir.setter
287
+ def cachedir(self, cachedir: Path | str):
288
+ _type_check(cachedir, "cachedir", (str, Path))
289
+ self._cachedir = Path(cachedir)
290
+
291
+ @property
292
+ def datasetdir(self) -> Path:
293
+ """\
294
+ Directory for example :mod:`~scanpy.datasets` (default `'./data/'`).
295
+ """
296
+ return self._datasetdir
297
+
298
+ @datasetdir.setter
299
+ def datasetdir(self, datasetdir: Path | str):
300
+ _type_check(datasetdir, "datasetdir", (str, Path))
301
+ self._datasetdir = Path(datasetdir).resolve()
302
+
303
+ @property
304
+ def figdir(self) -> Path:
305
+ """\
306
+ Directory for saving figures (default `'./figures/'`).
307
+ """
308
+ return self._figdir
309
+
310
+ @figdir.setter
311
+ def figdir(self, figdir: Path | str):
312
+ _type_check(figdir, "figdir", (str, Path))
313
+ self._figdir = Path(figdir)
314
+
315
+ @property
316
+ def cache_compression(self) -> str | None:
317
+ """\
318
+ Compression for `sc.read(..., cache=True)` (default `'lzf'`).
319
+
320
+ May be `'lzf'`, `'gzip'`, or `None`.
321
+ """
322
+ return self._cache_compression
323
+
324
+ @cache_compression.setter
325
+ def cache_compression(self, cache_compression: str | None):
326
+ if cache_compression not in {"lzf", "gzip", None}:
327
+ raise ValueError(
328
+ f"`cache_compression` ({cache_compression}) "
329
+ "must be in {'lzf', 'gzip', None}"
330
+ )
331
+ self._cache_compression = cache_compression
332
+
333
+ @property
334
+ def max_memory(self) -> int | float:
335
+ """\
336
+ Maximum memory usage in Gigabyte.
337
+
338
+ Is currently not well respected…
339
+ """
340
+ return self._max_memory
341
+
342
+ @max_memory.setter
343
+ def max_memory(self, max_memory: int | float):
344
+ _type_check(max_memory, "max_memory", (int, float))
345
+ self._max_memory = max_memory
346
+
347
+ @property
348
+ def n_jobs(self) -> int:
349
+ """\
350
+ Default number of jobs/ CPUs to use for parallel computing.
351
+
352
+ Set to `-1` in order to use all available cores.
353
+ Not all algorithms support special behavior for numbers < `-1`,
354
+ so make sure to leave this setting as >= `-1`.
355
+ """
356
+ return self._n_jobs
357
+
358
+ @n_jobs.setter
359
+ def n_jobs(self, n_jobs: int):
360
+ _type_check(n_jobs, "n_jobs", int)
361
+ self._n_jobs = n_jobs
362
+
363
+ @property
364
+ def logpath(self) -> Path | None:
365
+ """\
366
+ The file path `logfile` was set to.
367
+ """
368
+ return self._logpath
369
+
370
+ @logpath.setter
371
+ def logpath(self, logpath: Path | str | None):
372
+ _type_check(logpath, "logfile", (str, Path))
373
+ # set via “file object” branch of logfile.setter
374
+ self.logfile = Path(logpath).open("a") # noqa: SIM115
375
+ self._logpath = Path(logpath)
376
+
377
+ @property
378
+ def logfile(self) -> TextIO:
379
+ """\
380
+ The open file to write logs to.
381
+
382
+ Set it to a :class:`~pathlib.Path` or :class:`str` to open a new one.
383
+ The default `None` corresponds to :obj:`sys.stdout` in jupyter notebooks
384
+ and to :obj:`sys.stderr` otherwise.
385
+
386
+ For backwards compatibility, setting it to `''` behaves like setting it to `None`.
387
+ """
388
+ return self._logfile
389
+
390
+ @logfile.setter
391
+ def logfile(self, logfile: Path | str | TextIO | None):
392
+ if not hasattr(logfile, "write") and logfile:
393
+ self.logpath = logfile
394
+ else: # file object
395
+ if not logfile: # None or ''
396
+ logfile = sys.stdout if self._is_run_from_ipython() else sys.stderr
397
+ self._logfile = logfile
398
+ self._logpath = None
399
+ _set_log_file(self)
400
+
401
+ @property
402
+ def categories_to_ignore(self) -> list[str]:
403
+ """\
404
+ Categories that are omitted in plotting etc.
405
+ """
406
+ return self._categories_to_ignore
407
+
408
+ @categories_to_ignore.setter
409
+ def categories_to_ignore(self, categories_to_ignore: Iterable[str]):
410
+ categories_to_ignore = list(categories_to_ignore)
411
+ for i, cat in enumerate(categories_to_ignore):
412
+ _type_check(cat, f"categories_to_ignore[{i}]", str)
413
+ self._categories_to_ignore = categories_to_ignore
414
+
415
+ # --------------------------------------------------------------------------------
416
+ # Functions
417
+ # --------------------------------------------------------------------------------
418
+
419
+ @old_positionals(
420
+ "scanpy",
421
+ "dpi",
422
+ "dpi_save",
423
+ "frameon",
424
+ "vector_friendly",
425
+ "fontsize",
426
+ "figsize",
427
+ "color_map",
428
+ "format",
429
+ "facecolor",
430
+ "transparent",
431
+ "ipython_format",
432
+ )
433
+ def set_figure_params(
434
+ self,
435
+ *,
436
+ scanpy: bool = True,
437
+ dpi: int = 80,
438
+ dpi_save: int = 150,
439
+ frameon: bool = True,
440
+ vector_friendly: bool = True,
441
+ fontsize: int = 14,
442
+ figsize: int | None = None,
443
+ color_map: str | None = None,
444
+ format: _Format = "pdf",
445
+ facecolor: str | None = None,
446
+ transparent: bool = False,
447
+ ipython_format: str = "png2x",
448
+ ) -> None:
449
+ """\
450
+ Set resolution/size, styling and format of figures.
451
+
452
+ Parameters
453
+ ----------
454
+ scanpy
455
+ Init default values for :obj:`matplotlib.rcParams` suited for Scanpy.
456
+ dpi
457
+ Resolution of rendered figures – this influences the size of figures in notebooks.
458
+ dpi_save
459
+ Resolution of saved figures. This should typically be higher to achieve
460
+ publication quality.
461
+ frameon
462
+ Add frames and axes labels to scatter plots.
463
+ vector_friendly
464
+ Plot scatter plots using `png` backend even when exporting as `pdf` or `svg`.
465
+ fontsize
466
+ Set the fontsize for several `rcParams` entries. Ignored if `scanpy=False`.
467
+ figsize
468
+ Set plt.rcParams['figure.figsize'].
469
+ color_map
470
+ Convenience method for setting the default color map. Ignored if `scanpy=False`.
471
+ format
472
+ This sets the default format for saving figures: `file_format_figs`.
473
+ facecolor
474
+ Sets backgrounds via `rcParams['figure.facecolor'] = facecolor` and
475
+ `rcParams['axes.facecolor'] = facecolor`.
476
+ transparent
477
+ Save figures with transparent back ground. Sets
478
+ `rcParams['savefig.transparent']`.
479
+ ipython_format
480
+ Only concerns the notebook/IPython environment; see
481
+ :func:`~IPython.display.set_matplotlib_formats` for details.
482
+ """
483
+ if self._is_run_from_ipython():
484
+ import IPython
485
+
486
+ if isinstance(ipython_format, str):
487
+ ipython_format = [ipython_format]
488
+ IPython.display.set_matplotlib_formats(*ipython_format)
489
+
490
+ from matplotlib import rcParams
491
+
492
+ self._vector_friendly = vector_friendly
493
+ self.file_format_figs = format
494
+ if dpi is not None:
495
+ rcParams["figure.dpi"] = dpi
496
+ if dpi_save is not None:
497
+ rcParams["savefig.dpi"] = dpi_save
498
+ if transparent is not None:
499
+ rcParams["savefig.transparent"] = transparent
500
+ if facecolor is not None:
501
+ rcParams["figure.facecolor"] = facecolor
502
+ rcParams["axes.facecolor"] = facecolor
503
+ if scanpy:
504
+ from .plotting._rcmod import set_rcParams_scanpy
505
+
506
+ set_rcParams_scanpy(fontsize=fontsize, color_map=color_map)
507
+ if figsize is not None:
508
+ rcParams["figure.figsize"] = figsize
509
+ self._frameon = frameon
510
+
511
+ @staticmethod
512
+ def _is_run_from_ipython():
513
+ """Determines whether we're currently in IPython."""
514
+ import builtins
515
+
516
+ return getattr(builtins, "__IPYTHON__", False)
517
+
518
+ def __str__(self) -> str:
519
+ return "\n".join(
520
+ f"{k} = {v!r}"
521
+ for k, v in inspect.getmembers(self)
522
+ if not k.startswith("_") and k != "getdoc"
523
+ )
524
+
525
+
526
+ settings = ScanpyConfig()