sclab 0.1.7__py3-none-any.whl → 0.2.2__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.
- sclab/__init__.py +3 -1
- sclab/_io.py +89 -12
- sclab/_methods_registry.py +65 -0
- sclab/_sclab.py +229 -21
- sclab/dataset/_dataset.py +3 -5
- sclab/dataset/processor/_processor.py +22 -15
- sclab/dataset/processor/_results_panel.py +80 -0
- sclab/dataset/processor/step/_processor_step_base.py +12 -6
- sclab/examples/processor_steps/__init__.py +2 -0
- sclab/examples/processor_steps/_cluster.py +2 -2
- sclab/examples/processor_steps/_differential_expression.py +328 -0
- sclab/examples/processor_steps/_neighbors.py +2 -2
- sclab/examples/processor_steps/_pca.py +2 -2
- sclab/examples/processor_steps/_preprocess.py +2 -2
- sclab/examples/processor_steps/_qc.py +2 -2
- sclab/examples/processor_steps/_umap.py +2 -2
- sclab/methods/__init__.py +28 -0
- sclab/scanpy/_compat.py +92 -0
- sclab/scanpy/_settings.py +526 -0
- sclab/scanpy/logging.py +290 -0
- sclab/scanpy/plotting/__init__.py +0 -0
- sclab/scanpy/plotting/_rcmod.py +73 -0
- sclab/scanpy/plotting/palettes.py +221 -0
- sclab/scanpy/readwrite.py +1108 -0
- {sclab-0.1.7.dist-info → sclab-0.2.2.dist-info}/METADATA +22 -11
- sclab-0.2.2.dist-info/RECORD +42 -0
- {sclab-0.1.7.dist-info → sclab-0.2.2.dist-info}/WHEEL +1 -1
- sclab-0.2.2.dist-info/licenses/LICENSE +29 -0
- sclab-0.1.7.dist-info/RECORD +0 -30
sclab/scanpy/_compat.py
ADDED
|
@@ -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()
|