sclab 0.1.8__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.

@@ -0,0 +1,290 @@
1
+ """Logging and Profiling"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import sys
7
+ import warnings
8
+ from datetime import datetime, timedelta, timezone
9
+ from functools import partial, update_wrapper
10
+ from logging import CRITICAL, DEBUG, ERROR, INFO, WARNING
11
+ from typing import TYPE_CHECKING
12
+
13
+ import anndata.logging
14
+
15
+ if TYPE_CHECKING:
16
+ from typing import IO
17
+
18
+ from ._settings import ScanpyConfig
19
+
20
+
21
+ # This is currently the only documented API
22
+ __all__ = ["print_versions"]
23
+
24
+ HINT = (INFO + DEBUG) // 2
25
+ logging.addLevelName(HINT, "HINT")
26
+
27
+
28
+ class _RootLogger(logging.RootLogger):
29
+ def __init__(self, level):
30
+ super().__init__(level)
31
+ self.propagate = False
32
+ _RootLogger.manager = logging.Manager(self)
33
+
34
+ def log(
35
+ self,
36
+ level: int,
37
+ msg: str,
38
+ *,
39
+ extra: dict | None = None,
40
+ time: datetime | None = None,
41
+ deep: str | None = None,
42
+ ) -> datetime:
43
+ from ._settings import settings
44
+
45
+ now = datetime.now(timezone.utc)
46
+ time_passed: timedelta = None if time is None else now - time
47
+ extra = {
48
+ **(extra or {}),
49
+ "deep": deep if settings.verbosity.level < level else None,
50
+ "time_passed": time_passed,
51
+ }
52
+ super().log(level, msg, extra=extra)
53
+ return now
54
+
55
+ def critical(self, msg, *, time=None, deep=None, extra=None) -> datetime:
56
+ return self.log(CRITICAL, msg, time=time, deep=deep, extra=extra)
57
+
58
+ def error(self, msg, *, time=None, deep=None, extra=None) -> datetime:
59
+ return self.log(ERROR, msg, time=time, deep=deep, extra=extra)
60
+
61
+ def warning(self, msg, *, time=None, deep=None, extra=None) -> datetime:
62
+ return self.log(WARNING, msg, time=time, deep=deep, extra=extra)
63
+
64
+ def info(self, msg, *, time=None, deep=None, extra=None) -> datetime:
65
+ return self.log(INFO, msg, time=time, deep=deep, extra=extra)
66
+
67
+ def hint(self, msg, *, time=None, deep=None, extra=None) -> datetime:
68
+ return self.log(HINT, msg, time=time, deep=deep, extra=extra)
69
+
70
+ def debug(self, msg, *, time=None, deep=None, extra=None) -> datetime:
71
+ return self.log(DEBUG, msg, time=time, deep=deep, extra=extra)
72
+
73
+
74
+ def _set_log_file(settings: ScanpyConfig):
75
+ file = settings.logfile
76
+ name = settings.logpath
77
+ root = settings._root_logger
78
+ h = logging.StreamHandler(file) if name is None else logging.FileHandler(name)
79
+ h.setFormatter(_LogFormatter())
80
+ h.setLevel(root.level)
81
+ for handler in list(root.handlers):
82
+ root.removeHandler(handler)
83
+ root.addHandler(h)
84
+
85
+
86
+ def _set_log_level(settings: ScanpyConfig, level: int):
87
+ root = settings._root_logger
88
+ root.setLevel(level)
89
+ for h in list(root.handlers):
90
+ h.setLevel(level)
91
+
92
+
93
+ class _LogFormatter(logging.Formatter):
94
+ def __init__(
95
+ self, fmt="{levelname}: {message}", datefmt="%Y-%m-%d %H:%M", style="{"
96
+ ):
97
+ super().__init__(fmt, datefmt, style)
98
+
99
+ def format(self, record: logging.LogRecord):
100
+ format_orig = self._style._fmt
101
+ if record.levelno == INFO:
102
+ self._style._fmt = "{message}"
103
+ elif record.levelno == HINT:
104
+ self._style._fmt = "--> {message}"
105
+ elif record.levelno == DEBUG:
106
+ self._style._fmt = " {message}"
107
+ if record.time_passed:
108
+ # strip microseconds
109
+ if record.time_passed.microseconds:
110
+ record.time_passed = timedelta(
111
+ seconds=int(record.time_passed.total_seconds())
112
+ )
113
+ if "{time_passed}" in record.msg:
114
+ record.msg = record.msg.replace(
115
+ "{time_passed}", str(record.time_passed)
116
+ )
117
+ else:
118
+ self._style._fmt += " ({time_passed})"
119
+ if record.deep:
120
+ record.msg = f"{record.msg}: {record.deep}"
121
+ result = logging.Formatter.format(self, record)
122
+ self._style._fmt = format_orig
123
+ return result
124
+
125
+
126
+ print_memory_usage = anndata.logging.print_memory_usage
127
+ get_memory_usage = anndata.logging.get_memory_usage
128
+
129
+
130
+ _DEPENDENCIES_NUMERICS = [
131
+ "anndata", # anndata actually shouldn't, but as long as it's in development
132
+ "umap",
133
+ "numpy",
134
+ "scipy",
135
+ "pandas",
136
+ ("sklearn", "scikit-learn"),
137
+ "statsmodels",
138
+ "igraph",
139
+ "louvain",
140
+ "leidenalg",
141
+ "pynndescent",
142
+ ]
143
+
144
+
145
+ def _versions_dependencies(dependencies):
146
+ # this is not the same as the requirements!
147
+ for mod in dependencies:
148
+ mod_name, dist_name = mod if isinstance(mod, tuple) else (mod, mod)
149
+ try:
150
+ imp = __import__(mod_name)
151
+ yield dist_name, imp.__version__
152
+ except (ImportError, AttributeError):
153
+ pass
154
+
155
+
156
+ def print_header(*, file=None):
157
+ """\
158
+ Versions that might influence the numerical results.
159
+ Matplotlib and Seaborn are excluded from this.
160
+
161
+ Parameters
162
+ ----------
163
+ file
164
+ Optional path for dependency output.
165
+ """
166
+
167
+ modules = ["scanpy"] + _DEPENDENCIES_NUMERICS
168
+ print(
169
+ " ".join(f"{mod}=={ver}" for mod, ver in _versions_dependencies(modules)),
170
+ file=file or sys.stdout,
171
+ )
172
+
173
+
174
+ def print_versions(*, file: IO[str] | None = None):
175
+ """\
176
+ Print versions of imported packages, OS, and jupyter environment.
177
+
178
+ For more options (including rich output) use `session_info.show` directly.
179
+
180
+ Parameters
181
+ ----------
182
+ file
183
+ Optional path for output.
184
+ """
185
+ import session_info
186
+
187
+ if file is not None:
188
+ from contextlib import redirect_stdout
189
+
190
+ warnings.warn(
191
+ "Passing argument 'file' to print_versions is deprecated, and will be "
192
+ "removed in a future version.",
193
+ FutureWarning,
194
+ )
195
+ with redirect_stdout(file):
196
+ print_versions()
197
+ else:
198
+ session_info.show(
199
+ dependencies=True,
200
+ html=False,
201
+ excludes=[
202
+ "builtins",
203
+ "stdlib_list",
204
+ "importlib_metadata",
205
+ # Special module present if test coverage being calculated
206
+ # https://gitlab.com/joelostblom/session_info/-/issues/10
207
+ "$coverage",
208
+ ],
209
+ )
210
+
211
+
212
+ def print_version_and_date(*, file=None):
213
+ """\
214
+ Useful for starting a notebook so you see when you started working.
215
+
216
+ Parameters
217
+ ----------
218
+ file
219
+ Optional path for output.
220
+ """
221
+ from . import __version__
222
+
223
+ if file is None:
224
+ file = sys.stdout
225
+ print(
226
+ f"Running Scanpy {__version__}, on {datetime.now():%Y-%m-%d %H:%M}.",
227
+ file=file,
228
+ )
229
+
230
+
231
+ def _copy_docs_and_signature(fn):
232
+ return partial(update_wrapper, wrapped=fn, assigned=["__doc__", "__annotations__"])
233
+
234
+
235
+ def error(
236
+ msg: str,
237
+ *,
238
+ time: datetime = None,
239
+ deep: str | None = None,
240
+ extra: dict | None = None,
241
+ ) -> datetime:
242
+ """\
243
+ Log message with specific level and return current time.
244
+
245
+ Parameters
246
+ ----------
247
+ msg
248
+ Message to display.
249
+ time
250
+ A time in the past. If this is passed, the time difference from then
251
+ to now is appended to `msg` as ` (HH:MM:SS)`.
252
+ If `msg` contains `{time_passed}`, the time difference is instead
253
+ inserted at that position.
254
+ deep
255
+ If the current verbosity is higher than the log function’s level,
256
+ this gets displayed as well
257
+ extra
258
+ Additional values you can specify in `msg` like `{time_passed}`.
259
+ """
260
+ from ._settings import settings
261
+
262
+ return settings._root_logger.error(msg, time=time, deep=deep, extra=extra)
263
+
264
+
265
+ @_copy_docs_and_signature(error)
266
+ def warning(msg, *, time=None, deep=None, extra=None) -> datetime:
267
+ from ._settings import settings
268
+
269
+ return settings._root_logger.warning(msg, time=time, deep=deep, extra=extra)
270
+
271
+
272
+ @_copy_docs_and_signature(error)
273
+ def info(msg, *, time=None, deep=None, extra=None) -> datetime:
274
+ from ._settings import settings
275
+
276
+ return settings._root_logger.info(msg, time=time, deep=deep, extra=extra)
277
+
278
+
279
+ @_copy_docs_and_signature(error)
280
+ def hint(msg, *, time=None, deep=None, extra=None) -> datetime:
281
+ from ._settings import settings
282
+
283
+ return settings._root_logger.hint(msg, time=time, deep=deep, extra=extra)
284
+
285
+
286
+ @_copy_docs_and_signature(error)
287
+ def debug(msg, *, time=None, deep=None, extra=None) -> datetime:
288
+ from ._settings import settings
289
+
290
+ return settings._root_logger.debug(msg, time=time, deep=deep, extra=extra)
File without changes
@@ -0,0 +1,73 @@
1
+ """Set the default matplotlib.rcParams."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import matplotlib as mpl
6
+ from cycler import cycler
7
+ from matplotlib import rcParams
8
+
9
+ from . import palettes
10
+
11
+
12
+ def set_rcParams_scanpy(fontsize=14, color_map=None):
13
+ """Set matplotlib.rcParams to Scanpy defaults.
14
+
15
+ Call this through `settings.set_figure_params`.
16
+ """
17
+
18
+ # figure
19
+ rcParams["figure.figsize"] = (4, 4)
20
+ rcParams["figure.subplot.left"] = 0.18
21
+ rcParams["figure.subplot.right"] = 0.96
22
+ rcParams["figure.subplot.bottom"] = 0.15
23
+ rcParams["figure.subplot.top"] = 0.91
24
+
25
+ rcParams["lines.linewidth"] = 1.5 # the line width of the frame
26
+ rcParams["lines.markersize"] = 6
27
+ rcParams["lines.markeredgewidth"] = 1
28
+
29
+ # font
30
+ rcParams["font.sans-serif"] = [
31
+ "Arial",
32
+ "Helvetica",
33
+ "DejaVu Sans",
34
+ "Bitstream Vera Sans",
35
+ "sans-serif",
36
+ ]
37
+ fontsize = fontsize
38
+ rcParams["font.size"] = fontsize
39
+ rcParams["legend.fontsize"] = 0.92 * fontsize
40
+ rcParams["axes.titlesize"] = fontsize
41
+ rcParams["axes.labelsize"] = fontsize
42
+
43
+ # legend
44
+ rcParams["legend.numpoints"] = 1
45
+ rcParams["legend.scatterpoints"] = 1
46
+ rcParams["legend.handlelength"] = 0.5
47
+ rcParams["legend.handletextpad"] = 0.4
48
+
49
+ # color cycle
50
+ rcParams["axes.prop_cycle"] = cycler(color=palettes.default_20)
51
+
52
+ # lines
53
+ rcParams["axes.linewidth"] = 0.8
54
+ rcParams["axes.edgecolor"] = "black"
55
+ rcParams["axes.facecolor"] = "white"
56
+
57
+ # ticks
58
+ rcParams["xtick.color"] = "k"
59
+ rcParams["ytick.color"] = "k"
60
+ rcParams["xtick.labelsize"] = fontsize
61
+ rcParams["ytick.labelsize"] = fontsize
62
+
63
+ # axes grid
64
+ rcParams["axes.grid"] = True
65
+ rcParams["grid.color"] = ".8"
66
+
67
+ # color map
68
+ rcParams["image.cmap"] = rcParams["image.cmap"] if color_map is None else color_map
69
+
70
+
71
+ def set_rcParams_defaults():
72
+ """Reset `matplotlib.rcParams` to defaults."""
73
+ rcParams.update(mpl.rcParamsDefault)
@@ -0,0 +1,221 @@
1
+ """Color palettes in addition to matplotlib's palettes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from matplotlib import cm, colors
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Mapping, Sequence
11
+
12
+ # Colorblindness adjusted vega_10
13
+ # See https://github.com/scverse/scanpy/issues/387
14
+ vega_10 = list(map(colors.to_hex, cm.tab10.colors))
15
+ vega_10_scanpy = vega_10.copy()
16
+ vega_10_scanpy[2] = "#279e68" # green
17
+ vega_10_scanpy[4] = "#aa40fc" # purple
18
+ vega_10_scanpy[8] = "#b5bd61" # kakhi
19
+
20
+ # default matplotlib 2.0 palette
21
+ # see 'category20' on https://github.com/vega/vega/wiki/Scales#scale-range-literals
22
+ vega_20 = list(map(colors.to_hex, cm.tab20.colors))
23
+
24
+ # reorderd, some removed, some added
25
+ vega_20_scanpy = [
26
+ # dark without grey:
27
+ *vega_20[0:14:2],
28
+ *vega_20[16::2],
29
+ # light without grey:
30
+ *vega_20[1:15:2],
31
+ *vega_20[17::2],
32
+ # manual additions:
33
+ "#ad494a",
34
+ "#8c6d31",
35
+ ]
36
+ vega_20_scanpy[2] = vega_10_scanpy[2]
37
+ vega_20_scanpy[4] = vega_10_scanpy[4]
38
+ vega_20_scanpy[7] = vega_10_scanpy[8] # kakhi shifted by missing grey
39
+ # TODO: also replace pale colors if necessary
40
+
41
+ default_20 = vega_20_scanpy
42
+
43
+ # https://graphicdesign.stackexchange.com/questions/3682/where-can-i-find-a-large-palette-set-of-contrasting-colors-for-coloring-many-d
44
+ # update 1
45
+ # orig reference https://research.wu.ac.at/en/publications/escaping-rgbland-selecting-colors-for-statistical-graphics-26
46
+ zeileis_28 = [
47
+ "#023fa5",
48
+ "#7d87b9",
49
+ "#bec1d4",
50
+ "#d6bcc0",
51
+ "#bb7784",
52
+ "#8e063b",
53
+ "#4a6fe3",
54
+ "#8595e1",
55
+ "#b5bbe3",
56
+ "#e6afb9",
57
+ "#e07b91",
58
+ "#d33f6a",
59
+ "#11c638",
60
+ "#8dd593",
61
+ "#c6dec7",
62
+ "#ead3c6",
63
+ "#f0b98d",
64
+ "#ef9708",
65
+ "#0fcfc0",
66
+ "#9cded6",
67
+ "#d5eae7",
68
+ "#f3e1eb",
69
+ "#f6c4e1",
70
+ "#f79cd4",
71
+ # these last ones were added:
72
+ "#7f7f7f",
73
+ "#c7c7c7",
74
+ "#1CE6FF",
75
+ "#336600",
76
+ ]
77
+
78
+ default_28 = zeileis_28
79
+
80
+ # from https://godsnotwheregodsnot.blogspot.com/2012/09/color-distribution-methodology.html
81
+ godsnot_102 = [
82
+ # "#000000", # remove the black, as often, we have black colored annotation
83
+ "#FFFF00",
84
+ "#1CE6FF",
85
+ "#FF34FF",
86
+ "#FF4A46",
87
+ "#008941",
88
+ "#006FA6",
89
+ "#A30059",
90
+ "#FFDBE5",
91
+ "#7A4900",
92
+ "#0000A6",
93
+ "#63FFAC",
94
+ "#B79762",
95
+ "#004D43",
96
+ "#8FB0FF",
97
+ "#997D87",
98
+ "#5A0007",
99
+ "#809693",
100
+ "#6A3A4C",
101
+ "#1B4400",
102
+ "#4FC601",
103
+ "#3B5DFF",
104
+ "#4A3B53",
105
+ "#FF2F80",
106
+ "#61615A",
107
+ "#BA0900",
108
+ "#6B7900",
109
+ "#00C2A0",
110
+ "#FFAA92",
111
+ "#FF90C9",
112
+ "#B903AA",
113
+ "#D16100",
114
+ "#DDEFFF",
115
+ "#000035",
116
+ "#7B4F4B",
117
+ "#A1C299",
118
+ "#300018",
119
+ "#0AA6D8",
120
+ "#013349",
121
+ "#00846F",
122
+ "#372101",
123
+ "#FFB500",
124
+ "#C2FFED",
125
+ "#A079BF",
126
+ "#CC0744",
127
+ "#C0B9B2",
128
+ "#C2FF99",
129
+ "#001E09",
130
+ "#00489C",
131
+ "#6F0062",
132
+ "#0CBD66",
133
+ "#EEC3FF",
134
+ "#456D75",
135
+ "#B77B68",
136
+ "#7A87A1",
137
+ "#788D66",
138
+ "#885578",
139
+ "#FAD09F",
140
+ "#FF8A9A",
141
+ "#D157A0",
142
+ "#BEC459",
143
+ "#456648",
144
+ "#0086ED",
145
+ "#886F4C",
146
+ "#34362D",
147
+ "#B4A8BD",
148
+ "#00A6AA",
149
+ "#452C2C",
150
+ "#636375",
151
+ "#A3C8C9",
152
+ "#FF913F",
153
+ "#938A81",
154
+ "#575329",
155
+ "#00FECF",
156
+ "#B05B6F",
157
+ "#8CD0FF",
158
+ "#3B9700",
159
+ "#04F757",
160
+ "#C8A1A1",
161
+ "#1E6E00",
162
+ "#7900D7",
163
+ "#A77500",
164
+ "#6367A9",
165
+ "#A05837",
166
+ "#6B002C",
167
+ "#772600",
168
+ "#D790FF",
169
+ "#9B9700",
170
+ "#549E79",
171
+ "#FFF69F",
172
+ "#201625",
173
+ "#72418F",
174
+ "#BC23FF",
175
+ "#99ADC0",
176
+ "#3A2465",
177
+ "#922329",
178
+ "#5B4534",
179
+ "#FDE8DC",
180
+ "#404E55",
181
+ "#0089A3",
182
+ "#CB7E98",
183
+ "#A4E804",
184
+ "#324E72",
185
+ ]
186
+
187
+ default_102 = godsnot_102
188
+
189
+
190
+ def _plot_color_cycle(clists: Mapping[str, Sequence[str]]):
191
+ import matplotlib.pyplot as plt
192
+ import numpy as np
193
+ from matplotlib.colors import BoundaryNorm, ListedColormap
194
+
195
+ fig, axes = plt.subplots(nrows=len(clists)) # type: plt.Figure, plt.Axes
196
+ fig.subplots_adjust(top=0.95, bottom=0.01, left=0.3, right=0.99)
197
+ axes[0].set_title("Color Maps/Cycles", fontsize=14)
198
+
199
+ for ax, (name, clist) in zip(axes, clists.items()):
200
+ n = len(clist)
201
+ ax.imshow(
202
+ np.arange(n)[None, :].repeat(2, 0),
203
+ aspect="auto",
204
+ cmap=ListedColormap(clist),
205
+ norm=BoundaryNorm(np.arange(n + 1) - 0.5, n),
206
+ )
207
+ pos = list(ax.get_position().bounds)
208
+ x_text = pos[0] - 0.01
209
+ y_text = pos[1] + pos[3] / 2.0
210
+ fig.text(x_text, y_text, name, va="center", ha="right", fontsize=10)
211
+
212
+ # Turn off all ticks & spines
213
+ for ax in axes:
214
+ ax.set_axis_off()
215
+ fig.show()
216
+
217
+
218
+ if __name__ == "__main__":
219
+ _plot_color_cycle(
220
+ {name: colors for name, colors in globals().items() if isinstance(colors, list)}
221
+ )