spatialcore 0.1.9__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.
- spatialcore/__init__.py +122 -0
- spatialcore/annotation/__init__.py +253 -0
- spatialcore/annotation/acquisition.py +529 -0
- spatialcore/annotation/annotate.py +603 -0
- spatialcore/annotation/cellxgene.py +365 -0
- spatialcore/annotation/confidence.py +802 -0
- spatialcore/annotation/discovery.py +529 -0
- spatialcore/annotation/expression.py +363 -0
- spatialcore/annotation/loading.py +529 -0
- spatialcore/annotation/markers.py +297 -0
- spatialcore/annotation/ontology.py +1282 -0
- spatialcore/annotation/patterns.py +247 -0
- spatialcore/annotation/pipeline.py +620 -0
- spatialcore/annotation/synapse.py +380 -0
- spatialcore/annotation/training.py +1457 -0
- spatialcore/annotation/validation.py +422 -0
- spatialcore/core/__init__.py +34 -0
- spatialcore/core/cache.py +118 -0
- spatialcore/core/logging.py +135 -0
- spatialcore/core/metadata.py +149 -0
- spatialcore/core/utils.py +768 -0
- spatialcore/data/gene_mappings/ensembl_to_hugo_human.tsv +86372 -0
- spatialcore/data/markers/canonical_markers.json +83 -0
- spatialcore/data/ontology_mappings/ontology_index.json +63865 -0
- spatialcore/plotting/__init__.py +109 -0
- spatialcore/plotting/benchmark.py +477 -0
- spatialcore/plotting/celltype.py +329 -0
- spatialcore/plotting/confidence.py +413 -0
- spatialcore/plotting/spatial.py +505 -0
- spatialcore/plotting/utils.py +411 -0
- spatialcore/plotting/validation.py +1342 -0
- spatialcore-0.1.9.dist-info/METADATA +213 -0
- spatialcore-0.1.9.dist-info/RECORD +36 -0
- spatialcore-0.1.9.dist-info/WHEEL +5 -0
- spatialcore-0.1.9.dist-info/licenses/LICENSE +201 -0
- spatialcore-0.1.9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plotting utilities for SpatialCore visualization.
|
|
3
|
+
|
|
4
|
+
This module provides utility functions for:
|
|
5
|
+
1. Color palette generation and loading
|
|
6
|
+
2. Figure setup and configuration
|
|
7
|
+
3. Saving figures in multiple formats
|
|
8
|
+
|
|
9
|
+
These utilities are used by all other plotting modules.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
import matplotlib.pyplot as plt
|
|
18
|
+
from matplotlib.figure import Figure
|
|
19
|
+
from matplotlib.axes import Axes
|
|
20
|
+
|
|
21
|
+
from spatialcore.core.logging import get_logger
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ============================================================================
|
|
27
|
+
# Color Palettes
|
|
28
|
+
# ============================================================================
|
|
29
|
+
|
|
30
|
+
# Default high-contrast palette for dark backgrounds
|
|
31
|
+
DEFAULT_PALETTE = [
|
|
32
|
+
"#F5252F", # Red
|
|
33
|
+
"#FB3FFC", # Magenta
|
|
34
|
+
"#00FFFF", # Cyan
|
|
35
|
+
"#33FF33", # Green
|
|
36
|
+
"#FFB300", # Amber
|
|
37
|
+
"#9966FF", # Purple
|
|
38
|
+
"#FF6B6B", # Coral
|
|
39
|
+
"#3784FE", # Blue
|
|
40
|
+
"#FF8000", # Orange
|
|
41
|
+
"#66CCCC", # Teal
|
|
42
|
+
"#CC66FF", # Lavender
|
|
43
|
+
"#99FF99", # Light green
|
|
44
|
+
"#FF6699", # Pink
|
|
45
|
+
"#E3E1E3", # Light gray
|
|
46
|
+
"#FFB3CC", # Light pink
|
|
47
|
+
"#E6E680", # Yellow-green
|
|
48
|
+
"#CC9966", # Tan
|
|
49
|
+
"#8080FF", # Periwinkle
|
|
50
|
+
"#FF9999", # Salmon
|
|
51
|
+
"#66FF99", # Mint
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Colorblind-safe palette (from ColorBrewer)
|
|
55
|
+
COLORBLIND_PALETTE = [
|
|
56
|
+
"#E69F00", # Orange
|
|
57
|
+
"#56B4E9", # Sky blue
|
|
58
|
+
"#009E73", # Bluish green
|
|
59
|
+
"#F0E442", # Yellow
|
|
60
|
+
"#0072B2", # Blue
|
|
61
|
+
"#D55E00", # Vermillion
|
|
62
|
+
"#CC79A7", # Reddish purple
|
|
63
|
+
"#000000", # Black
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def generate_celltype_palette(
|
|
68
|
+
cell_types: List[str],
|
|
69
|
+
palette: str = "default",
|
|
70
|
+
custom_colors: Optional[Dict[str, str]] = None,
|
|
71
|
+
) -> Dict[str, str]:
|
|
72
|
+
"""
|
|
73
|
+
Generate color palette for cell types.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
cell_types : List[str]
|
|
78
|
+
List of cell type names.
|
|
79
|
+
palette : str, default "default"
|
|
80
|
+
Palette name: "default", "colorblind", "tab20", "Set1", "Set2", "Set3".
|
|
81
|
+
custom_colors : Dict[str, str], optional
|
|
82
|
+
Custom color overrides for specific cell types.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
Dict[str, str]
|
|
87
|
+
Mapping from cell type name to hex color.
|
|
88
|
+
|
|
89
|
+
Examples
|
|
90
|
+
--------
|
|
91
|
+
>>> from spatialcore.plotting.utils import generate_celltype_palette
|
|
92
|
+
>>> colors = generate_celltype_palette(
|
|
93
|
+
... ["T cell", "B cell", "Macrophage"],
|
|
94
|
+
... custom_colors={"T cell": "#FF0000"},
|
|
95
|
+
... )
|
|
96
|
+
"""
|
|
97
|
+
# Get base palette
|
|
98
|
+
if palette == "default":
|
|
99
|
+
base_colors = DEFAULT_PALETTE
|
|
100
|
+
elif palette == "colorblind":
|
|
101
|
+
base_colors = COLORBLIND_PALETTE
|
|
102
|
+
elif palette in ["tab20", "Set1", "Set2", "Set3"]:
|
|
103
|
+
cmap = plt.get_cmap(palette)
|
|
104
|
+
n_colors = cmap.N if hasattr(cmap, "N") else 20
|
|
105
|
+
base_colors = [plt.colors.rgb2hex(cmap(i)) for i in range(n_colors)]
|
|
106
|
+
else:
|
|
107
|
+
logger.warning(f"Unknown palette '{palette}', using default")
|
|
108
|
+
base_colors = DEFAULT_PALETTE
|
|
109
|
+
|
|
110
|
+
if custom_colors is None:
|
|
111
|
+
custom_colors = {}
|
|
112
|
+
|
|
113
|
+
color_map = {}
|
|
114
|
+
color_idx = 0
|
|
115
|
+
|
|
116
|
+
for cell_type in sorted(cell_types):
|
|
117
|
+
if cell_type in custom_colors:
|
|
118
|
+
color_map[cell_type] = custom_colors[cell_type]
|
|
119
|
+
else:
|
|
120
|
+
color_map[cell_type] = base_colors[color_idx % len(base_colors)]
|
|
121
|
+
color_idx += 1
|
|
122
|
+
|
|
123
|
+
return color_map
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def load_celltype_palette(path: Path) -> Dict[str, str]:
|
|
127
|
+
"""
|
|
128
|
+
Load cell type color palette from JSON file.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
path : Path
|
|
133
|
+
Path to JSON file with color mapping.
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
Dict[str, str]
|
|
138
|
+
Mapping from cell type name to hex color.
|
|
139
|
+
"""
|
|
140
|
+
path = Path(path)
|
|
141
|
+
if not path.exists():
|
|
142
|
+
raise FileNotFoundError(f"Palette file not found: {path}")
|
|
143
|
+
|
|
144
|
+
with open(path, "r") as f:
|
|
145
|
+
colors = json.load(f)
|
|
146
|
+
|
|
147
|
+
logger.info(f"Loaded {len(colors)} colors from {path}")
|
|
148
|
+
return colors
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def save_celltype_palette(
|
|
152
|
+
colors: Dict[str, str],
|
|
153
|
+
path: Path,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Save cell type color palette to JSON file.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
colors : Dict[str, str]
|
|
161
|
+
Mapping from cell type name to hex color.
|
|
162
|
+
path : Path
|
|
163
|
+
Output path for JSON file.
|
|
164
|
+
"""
|
|
165
|
+
path = Path(path)
|
|
166
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
167
|
+
|
|
168
|
+
with open(path, "w") as f:
|
|
169
|
+
json.dump(colors, f, indent=2)
|
|
170
|
+
|
|
171
|
+
logger.info(f"Saved {len(colors)} colors to {path}")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ============================================================================
|
|
175
|
+
# Figure Setup
|
|
176
|
+
# ============================================================================
|
|
177
|
+
|
|
178
|
+
def setup_figure(
|
|
179
|
+
figsize: Tuple[float, float] = (8, 6),
|
|
180
|
+
dpi: int = 150,
|
|
181
|
+
style: str = "ticks",
|
|
182
|
+
context: str = "notebook",
|
|
183
|
+
dark_background: bool = False,
|
|
184
|
+
) -> Tuple[Figure, Axes]:
|
|
185
|
+
"""
|
|
186
|
+
Create figure with consistent styling.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
figsize : Tuple[float, float], default (8, 6)
|
|
191
|
+
Figure size in inches (width, height).
|
|
192
|
+
dpi : int, default 150
|
|
193
|
+
Dots per inch for rendering.
|
|
194
|
+
style : str, default "ticks"
|
|
195
|
+
Seaborn style: "ticks", "whitegrid", "darkgrid", "white", "dark".
|
|
196
|
+
context : str, default "notebook"
|
|
197
|
+
Seaborn context: "paper", "notebook", "talk", "poster".
|
|
198
|
+
dark_background : bool, default False
|
|
199
|
+
Use dark background for spatial plots.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
Tuple[Figure, Axes]
|
|
204
|
+
Matplotlib figure and axes.
|
|
205
|
+
|
|
206
|
+
Examples
|
|
207
|
+
--------
|
|
208
|
+
>>> from spatialcore.plotting.utils import setup_figure
|
|
209
|
+
>>> fig, ax = setup_figure(figsize=(10, 8), dpi=300)
|
|
210
|
+
>>> ax.scatter(x, y)
|
|
211
|
+
>>> fig.savefig("plot.png")
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
import seaborn as sns
|
|
215
|
+
sns.set_style(style)
|
|
216
|
+
sns.set_context(context)
|
|
217
|
+
except ImportError:
|
|
218
|
+
pass # Seaborn optional
|
|
219
|
+
|
|
220
|
+
if dark_background:
|
|
221
|
+
plt.style.use("dark_background")
|
|
222
|
+
|
|
223
|
+
fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
|
|
224
|
+
return fig, ax
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def setup_multi_figure(
|
|
228
|
+
nrows: int = 1,
|
|
229
|
+
ncols: int = 1,
|
|
230
|
+
figsize: Optional[Tuple[float, float]] = None,
|
|
231
|
+
dpi: int = 150,
|
|
232
|
+
sharex: bool = False,
|
|
233
|
+
sharey: bool = False,
|
|
234
|
+
) -> Tuple[Figure, np.ndarray]:
|
|
235
|
+
"""
|
|
236
|
+
Create multi-panel figure.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
nrows : int, default 1
|
|
241
|
+
Number of subplot rows.
|
|
242
|
+
ncols : int, default 1
|
|
243
|
+
Number of subplot columns.
|
|
244
|
+
figsize : Tuple[float, float], optional
|
|
245
|
+
Figure size. If None, auto-calculated from panel count.
|
|
246
|
+
dpi : int, default 150
|
|
247
|
+
Dots per inch.
|
|
248
|
+
sharex : bool, default False
|
|
249
|
+
Share x-axis across subplots.
|
|
250
|
+
sharey : bool, default False
|
|
251
|
+
Share y-axis across subplots.
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
Tuple[Figure, np.ndarray]
|
|
256
|
+
Figure and array of axes.
|
|
257
|
+
"""
|
|
258
|
+
if figsize is None:
|
|
259
|
+
figsize = (4 * ncols, 4 * nrows)
|
|
260
|
+
|
|
261
|
+
fig, axes = plt.subplots(
|
|
262
|
+
nrows=nrows,
|
|
263
|
+
ncols=ncols,
|
|
264
|
+
figsize=figsize,
|
|
265
|
+
dpi=dpi,
|
|
266
|
+
sharex=sharex,
|
|
267
|
+
sharey=sharey,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return fig, np.atleast_2d(axes) if nrows == 1 or ncols == 1 else axes
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# ============================================================================
|
|
274
|
+
# Figure Saving
|
|
275
|
+
# ============================================================================
|
|
276
|
+
|
|
277
|
+
def save_figure(
|
|
278
|
+
fig: Figure,
|
|
279
|
+
path: Union[str, Path],
|
|
280
|
+
formats: List[str] = None,
|
|
281
|
+
dpi: int = 300,
|
|
282
|
+
bbox_inches: str = "tight",
|
|
283
|
+
transparent: bool = False,
|
|
284
|
+
) -> List[Path]:
|
|
285
|
+
"""
|
|
286
|
+
Save figure in multiple formats.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
fig : Figure
|
|
291
|
+
Matplotlib figure to save.
|
|
292
|
+
path : str or Path
|
|
293
|
+
Base output path (without extension).
|
|
294
|
+
formats : List[str], optional
|
|
295
|
+
Output formats. Default: ["png"].
|
|
296
|
+
dpi : int, default 300
|
|
297
|
+
Resolution for raster formats.
|
|
298
|
+
bbox_inches : str, default "tight"
|
|
299
|
+
Bounding box setting.
|
|
300
|
+
transparent : bool, default False
|
|
301
|
+
Transparent background.
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
List[Path]
|
|
306
|
+
Paths to saved files.
|
|
307
|
+
|
|
308
|
+
Examples
|
|
309
|
+
--------
|
|
310
|
+
>>> from spatialcore.plotting.utils import save_figure
|
|
311
|
+
>>> paths = save_figure(fig, "output/my_plot", formats=["png", "pdf", "svg"])
|
|
312
|
+
"""
|
|
313
|
+
if formats is None:
|
|
314
|
+
formats = ["png"]
|
|
315
|
+
|
|
316
|
+
path = Path(path)
|
|
317
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
318
|
+
|
|
319
|
+
saved_paths = []
|
|
320
|
+
for fmt in formats:
|
|
321
|
+
output_path = path.with_suffix(f".{fmt}")
|
|
322
|
+
fig.savefig(
|
|
323
|
+
output_path,
|
|
324
|
+
format=fmt,
|
|
325
|
+
dpi=dpi,
|
|
326
|
+
bbox_inches=bbox_inches,
|
|
327
|
+
transparent=transparent,
|
|
328
|
+
)
|
|
329
|
+
saved_paths.append(output_path)
|
|
330
|
+
logger.debug(f"Saved figure to: {output_path}")
|
|
331
|
+
|
|
332
|
+
logger.info(f"Saved figure to {len(saved_paths)} formats: {path}")
|
|
333
|
+
return saved_paths
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def close_figure(fig: Figure) -> None:
|
|
337
|
+
"""
|
|
338
|
+
Close figure to free memory.
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
fig : Figure
|
|
343
|
+
Matplotlib figure to close.
|
|
344
|
+
"""
|
|
345
|
+
plt.close(fig)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# ============================================================================
|
|
349
|
+
# Axis Formatting
|
|
350
|
+
# ============================================================================
|
|
351
|
+
|
|
352
|
+
def format_axis_labels(
|
|
353
|
+
ax: Axes,
|
|
354
|
+
xlabel: Optional[str] = None,
|
|
355
|
+
ylabel: Optional[str] = None,
|
|
356
|
+
title: Optional[str] = None,
|
|
357
|
+
fontsize: int = 12,
|
|
358
|
+
) -> Axes:
|
|
359
|
+
"""
|
|
360
|
+
Format axis labels and title.
|
|
361
|
+
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
ax : Axes
|
|
365
|
+
Matplotlib axes.
|
|
366
|
+
xlabel : str, optional
|
|
367
|
+
X-axis label.
|
|
368
|
+
ylabel : str, optional
|
|
369
|
+
Y-axis label.
|
|
370
|
+
title : str, optional
|
|
371
|
+
Plot title.
|
|
372
|
+
fontsize : int, default 12
|
|
373
|
+
Font size for labels.
|
|
374
|
+
|
|
375
|
+
Returns
|
|
376
|
+
-------
|
|
377
|
+
Axes
|
|
378
|
+
Modified axes.
|
|
379
|
+
"""
|
|
380
|
+
if xlabel:
|
|
381
|
+
ax.set_xlabel(xlabel, fontsize=fontsize)
|
|
382
|
+
if ylabel:
|
|
383
|
+
ax.set_ylabel(ylabel, fontsize=fontsize)
|
|
384
|
+
if title:
|
|
385
|
+
ax.set_title(title, fontsize=fontsize + 2)
|
|
386
|
+
return ax
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def despine(ax: Axes, top: bool = True, right: bool = True) -> Axes:
|
|
390
|
+
"""
|
|
391
|
+
Remove spines from axes.
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
ax : Axes
|
|
396
|
+
Matplotlib axes.
|
|
397
|
+
top : bool, default True
|
|
398
|
+
Remove top spine.
|
|
399
|
+
right : bool, default True
|
|
400
|
+
Remove right spine.
|
|
401
|
+
|
|
402
|
+
Returns
|
|
403
|
+
-------
|
|
404
|
+
Axes
|
|
405
|
+
Modified axes.
|
|
406
|
+
"""
|
|
407
|
+
if top:
|
|
408
|
+
ax.spines["top"].set_visible(False)
|
|
409
|
+
if right:
|
|
410
|
+
ax.spines["right"].set_visible(False)
|
|
411
|
+
return ax
|