vima-spatial 0.2.4__tar.gz → 0.2.6__tar.gz
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.
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/PKG-INFO +3 -2
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/README.md +1 -1
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/setup.cfg +2 -1
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/_settings.py +126 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/dimreduce.py +2 -2
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/ingest.py +6 -5
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/st.py +5 -3
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/train/logging.py +4 -1
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/features.py +15 -2
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/patches.py +3 -3
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/patchexamples.py +12 -11
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/spatial.py +25 -7
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/umaps.py +8 -1
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/PKG-INFO +3 -2
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/requires.txt +1 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/pyproject.toml +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/__init__.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/cc.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/data/__init__.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/data/download.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/data/patchcollection.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/data/samples.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/fingerprints.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/__init__.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/nonst.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/util.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/__init__.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/resnet_vae.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/resnetlight_decoder.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/resnetlight_encoder.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/simple_vae.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/vae.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/patchfeatures.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/train/__init__.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/train/training.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/__init__.py +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/SOURCES.txt +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/dependency_links.txt +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/top_level.txt +0 -0
- {vima_spatial-0.2.4 → vima_spatial-0.2.6}/tests/test_ra_regression.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vima-spatial
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: variational inference-based microniche analysis
|
|
5
5
|
Home-page: https://github.com/yakirr/vima
|
|
6
6
|
Author: Yakir Reshef
|
|
@@ -25,6 +25,7 @@ Requires-Dist: netcdf4
|
|
|
25
25
|
Requires-Dist: seaborn
|
|
26
26
|
Requires-Dist: pandas>=2.2.3
|
|
27
27
|
Requires-Dist: scipy
|
|
28
|
+
Requires-Dist: scikit-misc>=0.3.1
|
|
28
29
|
Requires-Dist: cna>=0.2.4
|
|
29
30
|
Requires-Dist: tqdm
|
|
30
31
|
Requires-Dist: pyarrow
|
|
@@ -49,4 +50,4 @@ To see how to apply `vima` to a stain-based modality like CODEX, immunohistochem
|
|
|
49
50
|
## citation
|
|
50
51
|
If you use `vima`, please cite:
|
|
51
52
|
|
|
52
|
-
[Y. Reshef, et al. Powerful and accurate case-control analysis of spatial molecular data. bioRxiv. https://doi.org/10.1101/2025.02.07.637149v1](https://www.biorxiv.org/content/10.1101/2025.02.07.637149v2).
|
|
53
|
+
[Y. Reshef, et al. Powerful and accurate case-control analysis of spatial molecular data. bioRxiv (In press at _Nature Methods_). https://doi.org/10.1101/2025.02.07.637149v1](https://www.biorxiv.org/content/10.1101/2025.02.07.637149v2).
|
|
@@ -16,4 +16,4 @@ To see how to apply `vima` to a stain-based modality like CODEX, immunohistochem
|
|
|
16
16
|
## citation
|
|
17
17
|
If you use `vima`, please cite:
|
|
18
18
|
|
|
19
|
-
[Y. Reshef, et al. Powerful and accurate case-control analysis of spatial molecular data. bioRxiv. https://doi.org/10.1101/2025.02.07.637149v1](https://www.biorxiv.org/content/10.1101/2025.02.07.637149v2).
|
|
19
|
+
[Y. Reshef, et al. Powerful and accurate case-control analysis of spatial molecular data. bioRxiv (In press at _Nature Methods_). https://doi.org/10.1101/2025.02.07.637149v1](https://www.biorxiv.org/content/10.1101/2025.02.07.637149v2).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = vima-spatial
|
|
3
|
-
version = 0.2.
|
|
3
|
+
version = 0.2.6
|
|
4
4
|
author = Yakir Reshef
|
|
5
5
|
author_email = yreshef@broadinstitute.org
|
|
6
6
|
description = variational inference-based microniche analysis
|
|
@@ -35,6 +35,7 @@ install_requires =
|
|
|
35
35
|
seaborn
|
|
36
36
|
pandas>=2.2.3
|
|
37
37
|
scipy
|
|
38
|
+
scikit-misc>=0.3.1
|
|
38
39
|
cna>=0.2.4
|
|
39
40
|
tqdm
|
|
40
41
|
pyarrow
|
|
@@ -30,6 +30,19 @@ Three independent knobs:
|
|
|
30
30
|
to verbosity (bars stay on even at ``"minimal"``); set ``False`` for batch or
|
|
31
31
|
cluster runs.
|
|
32
32
|
|
|
33
|
+
Plots that *are* drawn go through ``settings.show()``, which displays them when a
|
|
34
|
+
display is available and otherwise saves them as PNGs. This matters on a cluster:
|
|
35
|
+
with no display matplotlib falls back to the Agg backend, where ``plt.show()``
|
|
36
|
+
silently discards the figure and leaves it open, so the plots are lost and the
|
|
37
|
+
figures accumulate. Two knobs control the saving:
|
|
38
|
+
|
|
39
|
+
* ``settings.save_plots`` -- ``"auto"`` (default) saves only when no display is
|
|
40
|
+
available, so notebooks are unaffected; ``True`` always saves (never displays),
|
|
41
|
+
``False`` never saves.
|
|
42
|
+
* ``settings.plot_dir`` -- directory for saved plots, created on demand; defaults
|
|
43
|
+
to ``"figs"``. Set to ``None`` to discard plots instead (a warning is issued
|
|
44
|
+
once).
|
|
45
|
+
|
|
33
46
|
Guidelines for package code:
|
|
34
47
|
|
|
35
48
|
* ``logger.info(...)`` -- normal progress messages (visible at ``default``).
|
|
@@ -39,8 +52,12 @@ Guidelines for package code:
|
|
|
39
52
|
* ``settings.progress(iterable, name=...)`` -- wrap any loop needing a bar.
|
|
40
53
|
* ``if settings.show_plots(...):`` -- guard a diagnostic plot. Pass ``"verbose"``
|
|
41
54
|
for the detailed plots; the default level guards the standard ones.
|
|
55
|
+
* ``settings.show(name)`` -- finish a figure. Never call ``plt.show()`` directly:
|
|
56
|
+
that displays or discards, with no way to save.
|
|
42
57
|
"""
|
|
43
58
|
|
|
59
|
+
import os
|
|
60
|
+
import re
|
|
44
61
|
import sys
|
|
45
62
|
import logging
|
|
46
63
|
from enum import IntEnum
|
|
@@ -135,6 +152,12 @@ class Settings:
|
|
|
135
152
|
logger.propagate = False
|
|
136
153
|
|
|
137
154
|
self.progress_bars = True
|
|
155
|
+
self.plot_dir = "figs"
|
|
156
|
+
self.save_plots = "auto"
|
|
157
|
+
self._plot_count = 0
|
|
158
|
+
self._last_figure = None
|
|
159
|
+
self._warned_plots_discarded = False
|
|
160
|
+
self._warned_save_failed = False
|
|
138
161
|
self._verbosity = None
|
|
139
162
|
# ``diagnostic_plots`` tracks ``verbosity`` until the user sets it.
|
|
140
163
|
self._diagnostic_plots = None
|
|
@@ -174,6 +197,109 @@ class Settings:
|
|
|
174
197
|
"""
|
|
175
198
|
return self._diagnostic_plots >= Verbosity.parse(level)
|
|
176
199
|
|
|
200
|
+
def _display_available(self):
|
|
201
|
+
"""Whether the active matplotlib backend can actually show a figure.
|
|
202
|
+
|
|
203
|
+
``module://`` backends are the ones supplied by an embedding host --
|
|
204
|
+
``matplotlib_inline`` in Jupyter, ``ipympl`` for widgets -- which draw
|
|
205
|
+
the figure into the host rather than into a window; they display fine
|
|
206
|
+
but are not in matplotlib's ``interactive_bk`` list, so they need their
|
|
207
|
+
own check. Everything else is a display only if matplotlib calls it
|
|
208
|
+
interactive; a headless terminal falls back to Agg, which is not.
|
|
209
|
+
"""
|
|
210
|
+
import matplotlib
|
|
211
|
+
|
|
212
|
+
backend = matplotlib.get_backend()
|
|
213
|
+
if backend.startswith("module://"):
|
|
214
|
+
return True
|
|
215
|
+
return backend.lower() in {b.lower() for b in matplotlib.rcsetup.interactive_bk}
|
|
216
|
+
|
|
217
|
+
def _saving_plots(self):
|
|
218
|
+
"""Whether :meth:`show` should save rather than display."""
|
|
219
|
+
if self.save_plots == "auto":
|
|
220
|
+
return not self._display_available()
|
|
221
|
+
return bool(self.save_plots)
|
|
222
|
+
|
|
223
|
+
def show(self, name=None, fig=None, overwrite=False):
|
|
224
|
+
"""Display the current figure, or save it when there is no display.
|
|
225
|
+
|
|
226
|
+
Central replacement for ``plt.show()`` in package code. With a display
|
|
227
|
+
available this is exactly ``plt.show()``. Without one -- a plain
|
|
228
|
+
terminal on a cluster, where ``plt.show()`` silently discards the
|
|
229
|
+
figure *and* leaves it open, so figures pile up -- the figure is
|
|
230
|
+
written to ``settings.plot_dir`` as a PNG and closed.
|
|
231
|
+
|
|
232
|
+
``name`` labels the file, defaulting to the name of the calling
|
|
233
|
+
function. Files are numbered in the order they are produced, so a run's
|
|
234
|
+
plots sort chronologically; pass ``overwrite=True`` to use ``name``
|
|
235
|
+
alone as the filename and replace the file on every call (for a plot
|
|
236
|
+
redrawn repeatedly, like the per-epoch training summary). ``fig`` is the
|
|
237
|
+
figure to save, defaulting to the current one -- pass it explicitly when
|
|
238
|
+
drawing onto a figure that an earlier :meth:`show` may already have
|
|
239
|
+
closed, since ``plt.gcf()`` would then hand back a blank one.
|
|
240
|
+
|
|
241
|
+
Returns the path written, or ``None`` if the figure was displayed or
|
|
242
|
+
discarded.
|
|
243
|
+
"""
|
|
244
|
+
import matplotlib.pyplot as plt
|
|
245
|
+
|
|
246
|
+
if fig is None:
|
|
247
|
+
fig = plt.gcf()
|
|
248
|
+
self._last_figure = fig
|
|
249
|
+
|
|
250
|
+
if not self._saving_plots():
|
|
251
|
+
plt.show()
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
if self.plot_dir is None:
|
|
255
|
+
if not self._warned_plots_discarded:
|
|
256
|
+
logger.warning(
|
|
257
|
+
"no display available and vima.settings.plot_dir is None, so "
|
|
258
|
+
"plots are being discarded; set vima.settings.plot_dir to save "
|
|
259
|
+
"them as images instead"
|
|
260
|
+
)
|
|
261
|
+
self._warned_plots_discarded = True
|
|
262
|
+
plt.close(fig)
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
if name is None:
|
|
266
|
+
name = sys._getframe(1).f_code.co_name
|
|
267
|
+
name = re.sub(r"[^0-9a-zA-Z]+", "_", str(name)).strip("_").lower() or "plot"
|
|
268
|
+
if overwrite:
|
|
269
|
+
filename = f"{name}.png"
|
|
270
|
+
else:
|
|
271
|
+
self._plot_count += 1
|
|
272
|
+
filename = f"{self._plot_count:03d}_{name}.png"
|
|
273
|
+
|
|
274
|
+
path = os.path.join(self.plot_dir, filename)
|
|
275
|
+
try:
|
|
276
|
+
os.makedirs(self.plot_dir, exist_ok=True)
|
|
277
|
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
|
278
|
+
except OSError as e:
|
|
279
|
+
# A diagnostic plot is never worth aborting a long run for.
|
|
280
|
+
if not self._warned_save_failed:
|
|
281
|
+
logger.warning(f"could not save plot to {path}: {e}")
|
|
282
|
+
self._warned_save_failed = True
|
|
283
|
+
plt.close(fig)
|
|
284
|
+
return None
|
|
285
|
+
plt.close(fig)
|
|
286
|
+
logger.info(f"saved plot to {path}")
|
|
287
|
+
return path
|
|
288
|
+
|
|
289
|
+
def current_figure(self):
|
|
290
|
+
"""The figure a follow-up call should draw on.
|
|
291
|
+
|
|
292
|
+
Normally ``plt.gcf()``. When plots are being saved instead of shown,
|
|
293
|
+
:meth:`show` closes each figure, so ``plt.gcf()`` would hand back a
|
|
294
|
+
fresh blank one; with no figure open, return the last figure
|
|
295
|
+
:meth:`show` handled instead.
|
|
296
|
+
"""
|
|
297
|
+
import matplotlib.pyplot as plt
|
|
298
|
+
|
|
299
|
+
if not plt.get_fignums() and self._last_figure is not None:
|
|
300
|
+
return self._last_figure
|
|
301
|
+
return plt.gcf()
|
|
302
|
+
|
|
177
303
|
def progress(self, iterable=None, name=None, total=None, ncols=100, desc=None, **kwargs):
|
|
178
304
|
"""tqdm wrapper honoring ``settings.progress_bars``.
|
|
179
305
|
|
|
@@ -91,7 +91,7 @@ def metapixels_allsamples(normedpixelsdir, masksdir, sids, total_n_metapixels):
|
|
|
91
91
|
plt.xlabel('# non-empty pixels per metapixel')
|
|
92
92
|
plt.ylabel('Frequency')
|
|
93
93
|
plt.tight_layout()
|
|
94
|
-
|
|
94
|
+
settings.show('metapixel_occupancy_cdf')
|
|
95
95
|
|
|
96
96
|
return all_metapixels, all_npixels
|
|
97
97
|
|
|
@@ -174,7 +174,7 @@ def pca_metapixels(mps, k):
|
|
|
174
174
|
plt.imshow(loadings, cmap='seismic', vmin=-0.5, vmax=0.5)
|
|
175
175
|
plt.yticks(range(len(loadings)), loadings.index)
|
|
176
176
|
plt.xticks(range(len(loadings.columns)), loadings.columns, rotation=90)
|
|
177
|
-
|
|
177
|
+
settings.show('pc_loadings')
|
|
178
178
|
|
|
179
179
|
return loadings, C, allmp
|
|
180
180
|
|
|
@@ -65,9 +65,10 @@ def visualize_pixels(pixels, ntoplot, input, colorby):
|
|
|
65
65
|
if settings.show_plots('verbose'):
|
|
66
66
|
sns.scatterplot(x='PC1', y='PC2', hue=metavar, data=toplot, palette='Set1', s=1, legend=False)
|
|
67
67
|
plt.title(metavar)
|
|
68
|
-
|
|
68
|
+
settings.show(f'pc1_pc2_by_{metavar}')
|
|
69
69
|
sc.pl.umap(toplot_ad, color=metavar, legend_loc=None, frameon=False,
|
|
70
|
-
title=f'pixels UMAPed using {input}, colored by {metavar}')
|
|
70
|
+
title=f'pixels UMAPed using {input}, colored by {metavar}', show=False)
|
|
71
|
+
settings.show(f'pixel_umap_by_{metavar}')
|
|
71
72
|
|
|
72
73
|
# print LISI ratio
|
|
73
74
|
n_unique = toplot_ad.obs[metavar].nunique()
|
|
@@ -250,7 +251,7 @@ def sanity_checks(outdir, repname, npcs=1, nskip=3):
|
|
|
250
251
|
s = da.astype(np.float32)
|
|
251
252
|
da.close(); del da
|
|
252
253
|
s.plot(col='marker', col_wrap=5, vmin=-10, vmax=10, cmap='seismic')
|
|
253
|
-
|
|
254
|
+
settings.show(f'all_pcs_{sids[0]}')
|
|
254
255
|
del s
|
|
255
256
|
|
|
256
257
|
logger.info('histogram of each pc')
|
|
@@ -270,7 +271,7 @@ def sanity_checks(outdir, repname, npcs=1, nskip=3):
|
|
|
270
271
|
plt.subplot(int(np.ceil(nmms/4)), 4, i+1)
|
|
271
272
|
plt.hist(harmpixels[:,i], bins=1000)
|
|
272
273
|
plt.tight_layout()
|
|
273
|
-
|
|
274
|
+
settings.show('pc_histograms')
|
|
274
275
|
del harmpixels
|
|
275
276
|
gc.collect()
|
|
276
277
|
|
|
@@ -290,4 +291,4 @@ def sanity_checks(outdir, repname, npcs=1, nskip=3):
|
|
|
290
291
|
ax.set_title(sid)
|
|
291
292
|
del s; gc.collect()
|
|
292
293
|
plt.tight_layout()
|
|
293
|
-
|
|
294
|
+
settings.show(f'pc{i}_by_sample')
|
|
@@ -115,7 +115,8 @@ def get_sumstats(load, filepaths, target_sum, x_col, y_col, gene_col, n_top_gene
|
|
|
115
115
|
hvgs = hvgs + list(set(genes_to_add) & set(pl.var_names))
|
|
116
116
|
|
|
117
117
|
if settings.show_plots():
|
|
118
|
-
sc.pl.highly_variable_genes(pl, log=True, show=
|
|
118
|
+
sc.pl.highly_variable_genes(pl, log=True, show=False)
|
|
119
|
+
settings.show(f'hvgs_{sid}')
|
|
119
120
|
|
|
120
121
|
if settings.show_plots('verbose'):
|
|
121
122
|
top8 = (
|
|
@@ -144,7 +145,7 @@ def get_sumstats(load, filepaths, target_sum, x_col, y_col, gene_col, n_top_gene
|
|
|
144
145
|
ax.set_aspect('equal')
|
|
145
146
|
ax.axis('off')
|
|
146
147
|
plt.tight_layout()
|
|
147
|
-
|
|
148
|
+
settings.show(f'top_hvgs_{sid}')
|
|
148
149
|
plt.close(fig)
|
|
149
150
|
|
|
150
151
|
union_hvgs.update(hvgs)
|
|
@@ -220,7 +221,8 @@ def transcriptlist_to_normedpixelmatrix(sid, data, x_col, y_col, gene_col, pixel
|
|
|
220
221
|
pl = pl[(pl[markers].sum(axis=1) >= min_ntranscripts_per_pixel) ]#& (pl[list(set(markers) & set(genes))].sum(axis=1) > 0)]
|
|
221
222
|
if settings.show_plots():
|
|
222
223
|
plt.scatter(pl.pixel_x, pl.pixel_y, c=pl[markers].sum(axis=1), s=0.1, alpha=0.8, vmin=0, vmax=100)
|
|
223
|
-
plt.gca().set_aspect('equal'); plt.title('transcript density (gray = failed qc)'); plt.axis('off')
|
|
224
|
+
plt.gca().set_aspect('equal'); plt.title('transcript density (gray = failed qc)'); plt.axis('off')
|
|
225
|
+
settings.show(f'transcript_density_{sid}')
|
|
224
226
|
logger.info(f'\t{len(pl)} pixels after QC.')
|
|
225
227
|
|
|
226
228
|
logger.info('\tLog-normalizing...')
|
|
@@ -4,6 +4,7 @@ import time
|
|
|
4
4
|
import matplotlib.pyplot as plt
|
|
5
5
|
from IPython import display
|
|
6
6
|
from .. import vis as v
|
|
7
|
+
from .._settings import settings
|
|
7
8
|
|
|
8
9
|
class LossLogger:
|
|
9
10
|
"""Accumulate and report per-model training/validation losses; returned by `train`."""
|
|
@@ -118,7 +119,9 @@ class LossLogger:
|
|
|
118
119
|
plt.title('Reconstruction error across validation patches')
|
|
119
120
|
plt.xlabel('Reconstruction error'); plt.ylabel('#Patches')
|
|
120
121
|
plt.gca().spines[['top', 'right']].set_visible(False)
|
|
121
|
-
|
|
122
|
+
# Redrawn every epoch, so keep overwriting one file rather than
|
|
123
|
+
# accumulating one per epoch.
|
|
124
|
+
settings.show('training_progress', overwrite=True)
|
|
122
125
|
|
|
123
126
|
if self.detailed:
|
|
124
127
|
ix = np.argsort(vrlosses)
|
|
@@ -2,6 +2,7 @@ import matplotlib.pyplot as plt
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import seaborn as sns
|
|
5
|
+
from .._settings import settings
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
def _select_features(features, group_a, group_b, n_top, n_bottom, markers):
|
|
@@ -49,6 +50,11 @@ def plot_features(
|
|
|
49
50
|
``[label_a, label_b]`` for the legend.
|
|
50
51
|
kind
|
|
51
52
|
'violin' (default), 'box', or 'swarm'.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
matplotlib.figure.Figure
|
|
57
|
+
The figure that was drawn.
|
|
52
58
|
"""
|
|
53
59
|
if ax is None:
|
|
54
60
|
ax = plt.gca()
|
|
@@ -70,7 +76,8 @@ def plot_features(
|
|
|
70
76
|
plot_kwargs.update(kwargs)
|
|
71
77
|
plot_fn(data=df, x='marker', y='value', hue='status', order=toplot, ax=ax, **plot_kwargs)
|
|
72
78
|
if show:
|
|
73
|
-
|
|
79
|
+
settings.show()
|
|
80
|
+
return ax.figure
|
|
74
81
|
|
|
75
82
|
|
|
76
83
|
def plot_features_by_sample(
|
|
@@ -114,6 +121,11 @@ def plot_features_by_sample(
|
|
|
114
121
|
``[label_a, label_b]`` for the legend.
|
|
115
122
|
connect
|
|
116
123
|
Draw lines linking each sample's two group means.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
matplotlib.figure.Figure
|
|
128
|
+
The figure that was drawn.
|
|
117
129
|
"""
|
|
118
130
|
if ax is None:
|
|
119
131
|
ax = plt.gca()
|
|
@@ -147,4 +159,5 @@ def plot_features_by_sample(
|
|
|
147
159
|
color='gray', alpha=0.4, lw=0.8, zorder=0)
|
|
148
160
|
|
|
149
161
|
if show:
|
|
150
|
-
|
|
162
|
+
settings.show()
|
|
163
|
+
return ax.figure
|
|
@@ -30,7 +30,7 @@ def _plot_separate(patches, markers, vmin, vmax, cmap='seismic', show=True):
|
|
|
30
30
|
axes[k, 0].set_ylabel(marker, fontsize=9)
|
|
31
31
|
plt.tight_layout()
|
|
32
32
|
if show:
|
|
33
|
-
|
|
33
|
+
settings.show()
|
|
34
34
|
return fig
|
|
35
35
|
|
|
36
36
|
|
|
@@ -94,7 +94,7 @@ def _plot_composite(patches, markers, colors, vmin, vmax, features=None, nx=5, n
|
|
|
94
94
|
if subfig is None:
|
|
95
95
|
plt.tight_layout(rect=[0, 0.08, 1, 1])
|
|
96
96
|
if show:
|
|
97
|
-
|
|
97
|
+
settings.show()
|
|
98
98
|
return fig
|
|
99
99
|
|
|
100
100
|
|
|
@@ -441,5 +441,5 @@ def show_patches_cells(patchmeta, cells, x_col, y_col, celltype_col,
|
|
|
441
441
|
plt.tight_layout(rect=[0, bottom_margin, 1, 1])
|
|
442
442
|
|
|
443
443
|
if show:
|
|
444
|
-
|
|
444
|
+
settings.show()
|
|
445
445
|
return fig
|
|
@@ -4,6 +4,7 @@ import torch
|
|
|
4
4
|
import scanpy as sc
|
|
5
5
|
from scipy.optimize import linear_sum_assignment
|
|
6
6
|
from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar
|
|
7
|
+
from .._settings import settings
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def scaler(minimum=0, maximum=255):
|
|
@@ -52,7 +53,8 @@ def plot_with_reconstruction(model, examples, show=True, channels=[0,1,2], pmin=
|
|
|
52
53
|
|
|
53
54
|
if show:
|
|
54
55
|
plt.tight_layout()
|
|
55
|
-
|
|
56
|
+
settings.show()
|
|
57
|
+
return fig
|
|
56
58
|
|
|
57
59
|
|
|
58
60
|
def plot_patches_separatechannels(examples, choose=None, vmax=10, vmin=None, channels=[0,1,2], channelnames=None):
|
|
@@ -74,7 +76,8 @@ def plot_patches_separatechannels(examples, choose=None, vmax=10, vmin=None, cha
|
|
|
74
76
|
plt.gca().text(-5, 20, channelnames[j], va='center', ha='right', rotation=90)
|
|
75
77
|
|
|
76
78
|
plt.tight_layout()
|
|
77
|
-
|
|
79
|
+
settings.show()
|
|
80
|
+
return fig
|
|
78
81
|
|
|
79
82
|
|
|
80
83
|
# colormaps consists of tuples of the form [channel, color, scaler]
|
|
@@ -83,8 +86,6 @@ def plot_patches_overlaychannels(examples, colormaps, nx=5, ny=5, show=True, see
|
|
|
83
86
|
if seed is not None: np.random.seed(seed)
|
|
84
87
|
ix = np.random.choice(range(len(examples)), size=nx*ny, replace=False)
|
|
85
88
|
examples = examples[ix]
|
|
86
|
-
else:
|
|
87
|
-
ix = range(len(examples))
|
|
88
89
|
|
|
89
90
|
images = apply_colormap(examples, colormaps)
|
|
90
91
|
|
|
@@ -95,8 +96,8 @@ def plot_patches_overlaychannels(examples, colormaps, nx=5, ny=5, show=True, see
|
|
|
95
96
|
plt.axis('off')
|
|
96
97
|
plt.tight_layout()
|
|
97
98
|
if show:
|
|
98
|
-
|
|
99
|
-
return
|
|
99
|
+
settings.show()
|
|
100
|
+
return fig
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
def plot_patches_overlaychannels_linsum(patches, latents, colormaps, nx=5, ny=5, show=True, seed=None,
|
|
@@ -144,9 +145,8 @@ def plot_patches_overlaychannels_linsum(patches, latents, colormaps, nx=5, ny=5,
|
|
|
144
145
|
axs[-1,-1].add_artist(scalebar)
|
|
145
146
|
|
|
146
147
|
if show:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return fig
|
|
148
|
+
settings.show()
|
|
149
|
+
return fig
|
|
150
150
|
|
|
151
151
|
|
|
152
152
|
def plot_patches_overlaychannels_sorted(examples, colormaps, labels=None, nx=5, ny=5, show=True):
|
|
@@ -161,7 +161,8 @@ def plot_patches_overlaychannels_sorted(examples, colormaps, labels=None, nx=5,
|
|
|
161
161
|
plt.axis('off')
|
|
162
162
|
plt.tight_layout()
|
|
163
163
|
if show:
|
|
164
|
-
|
|
164
|
+
settings.show()
|
|
165
|
+
return fig
|
|
165
166
|
|
|
166
167
|
|
|
167
168
|
# each color channel should be a tuple of the form (channel, scaler)
|
|
@@ -178,4 +179,4 @@ def plot_patches_fourcolors(examples, nx=5, ny=5,
|
|
|
178
179
|
if yellow[0] is not None:
|
|
179
180
|
colormaps.append([yellow[0], [1,1,0], yellow[1]])
|
|
180
181
|
|
|
181
|
-
plot_patches_overlaychannels(examples, colormaps, nx=nx, ny=ny, show=show)
|
|
182
|
+
return plot_patches_overlaychannels(examples, colormaps, nx=nx, ny=ny, show=show)
|
|
@@ -20,6 +20,11 @@ def plot_sample_with_patches(s, marker, patchmeta, remove_margin=False, ax=None,
|
|
|
20
20
|
Patch metadata; patches belonging to this sample are outlined.
|
|
21
21
|
remove_margin
|
|
22
22
|
Crop to a margin around the outlined patches.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
matplotlib.figure.Figure
|
|
27
|
+
The figure that was drawn.
|
|
23
28
|
"""
|
|
24
29
|
if ax is None: ax = plt.gca()
|
|
25
30
|
|
|
@@ -48,7 +53,8 @@ def plot_sample_with_patches(s, marker, patchmeta, remove_margin=False, ax=None,
|
|
|
48
53
|
ax.set_ylim(y_max, y_min)
|
|
49
54
|
|
|
50
55
|
if show:
|
|
51
|
-
|
|
56
|
+
settings.show()
|
|
57
|
+
return ax.figure
|
|
52
58
|
|
|
53
59
|
|
|
54
60
|
def plot_samples_with_patches(samples, marker, patchmeta, ncols=5, **kwargs):
|
|
@@ -63,6 +69,11 @@ def plot_samples_with_patches(samples, marker, patchmeta, ncols=5, **kwargs):
|
|
|
63
69
|
Marker to display.
|
|
64
70
|
patchmeta
|
|
65
71
|
Patch metadata; patches are outlined on their sample.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
matplotlib.figure.Figure
|
|
76
|
+
The figure that was drawn.
|
|
66
77
|
"""
|
|
67
78
|
nrows = int(np.ceil(len(samples) / ncols))
|
|
68
79
|
fig, axs = plt.subplots(nrows, ncols, figsize=(3*ncols, 3*nrows))
|
|
@@ -70,7 +81,8 @@ def plot_samples_with_patches(samples, marker, patchmeta, ncols=5, **kwargs):
|
|
|
70
81
|
plot_sample_with_patches(s, marker, patchmeta, ax=ax, show=False, **kwargs)
|
|
71
82
|
ax.set_title(s.sid)
|
|
72
83
|
fig.tight_layout()
|
|
73
|
-
|
|
84
|
+
settings.show(fig=fig)
|
|
85
|
+
return fig
|
|
74
86
|
|
|
75
87
|
|
|
76
88
|
def plot_npatches_per_sample(samples, patchmeta):
|
|
@@ -79,17 +91,23 @@ def plot_npatches_per_sample(samples, patchmeta):
|
|
|
79
91
|
|
|
80
92
|
Samples in `samples` with no patches in `patchmeta` are shown with a count
|
|
81
93
|
of zero.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
matplotlib.figure.Figure
|
|
98
|
+
The figure that was drawn.
|
|
82
99
|
"""
|
|
83
100
|
res = patchmeta.sid.value_counts()
|
|
84
101
|
empty = [sid for sid in samples.keys() if sid not in patchmeta.sid.unique()]
|
|
85
102
|
for sid in empty:
|
|
86
103
|
res.loc[sid] = 0
|
|
87
104
|
|
|
88
|
-
plt.figure(figsize=(15,2))
|
|
105
|
+
fig = plt.figure(figsize=(15,2))
|
|
89
106
|
plt.bar(x=res.index, height=res)
|
|
90
107
|
plt.tick_params(axis='x', rotation=90)
|
|
91
108
|
plt.gca().spines[['top', 'right']].set_visible(False)
|
|
92
|
-
|
|
109
|
+
settings.show()
|
|
110
|
+
return fig
|
|
93
111
|
|
|
94
112
|
|
|
95
113
|
def _adjust_resolution(mypatches):
|
|
@@ -185,7 +203,7 @@ def spatialplot(patchmeta, values, sids=None, cmap='viridis', vmin=None, vmax=No
|
|
|
185
203
|
fig._vima_sid_to_ax = sid_to_ax
|
|
186
204
|
fig.tight_layout()
|
|
187
205
|
if show:
|
|
188
|
-
|
|
206
|
+
settings.show()
|
|
189
207
|
return fig
|
|
190
208
|
|
|
191
209
|
|
|
@@ -205,7 +223,7 @@ def annotate_spatialplot(patchmeta, highlight, color, thickness=3, show=True, fi
|
|
|
205
223
|
Figure returned by `spatialplot`; defaults to the current figure.
|
|
206
224
|
"""
|
|
207
225
|
if fig is None:
|
|
208
|
-
fig =
|
|
226
|
+
fig = settings.current_figure()
|
|
209
227
|
sid_to_ax = fig._vima_sid_to_ax
|
|
210
228
|
for sid, ax in sid_to_ax.items():
|
|
211
229
|
mypatches = patchmeta[patchmeta.sid == sid]
|
|
@@ -234,5 +252,5 @@ def annotate_spatialplot(patchmeta, highlight, color, thickness=3, show=True, fi
|
|
|
234
252
|
ax.plot(np.append(cnt[:, 0], cnt[0, 0]), np.append(cnt[:, 1], cnt[0, 1]), color=color, linewidth=thickness)
|
|
235
253
|
|
|
236
254
|
if show:
|
|
237
|
-
|
|
255
|
+
settings.show(fig=fig)
|
|
238
256
|
return fig
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import matplotlib.pyplot as plt
|
|
2
2
|
import scanpy as sc
|
|
3
|
+
from .._settings import settings
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
def plot_association(D, key='mncoef', fdr_thresh=0.1, ax=None, show=True, **kwargs):
|
|
@@ -17,6 +18,11 @@ def plot_association(D, key='mncoef', fdr_thresh=0.1, ax=None, show=True, **kwar
|
|
|
17
18
|
Coefficient column in ``D.obs`` (with matching ``{key}_fdr``).
|
|
18
19
|
fdr_thresh
|
|
19
20
|
FDR cutoff for calling a microniche significant.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
matplotlib.figure.Figure
|
|
25
|
+
The figure that was drawn.
|
|
20
26
|
"""
|
|
21
27
|
if ax is None:
|
|
22
28
|
ax = plt.gca()
|
|
@@ -34,4 +40,5 @@ def plot_association(D, key='mncoef', fdr_thresh=0.1, ax=None, show=True, **kwar
|
|
|
34
40
|
plt.title(f'No significant microniches at FDR {fdr_thresh*100:.0f}%')
|
|
35
41
|
|
|
36
42
|
if show:
|
|
37
|
-
|
|
43
|
+
settings.show()
|
|
44
|
+
return ax.figure
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vima-spatial
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: variational inference-based microniche analysis
|
|
5
5
|
Home-page: https://github.com/yakirr/vima
|
|
6
6
|
Author: Yakir Reshef
|
|
@@ -25,6 +25,7 @@ Requires-Dist: netcdf4
|
|
|
25
25
|
Requires-Dist: seaborn
|
|
26
26
|
Requires-Dist: pandas>=2.2.3
|
|
27
27
|
Requires-Dist: scipy
|
|
28
|
+
Requires-Dist: scikit-misc>=0.3.1
|
|
28
29
|
Requires-Dist: cna>=0.2.4
|
|
29
30
|
Requires-Dist: tqdm
|
|
30
31
|
Requires-Dist: pyarrow
|
|
@@ -49,4 +50,4 @@ To see how to apply `vima` to a stain-based modality like CODEX, immunohistochem
|
|
|
49
50
|
## citation
|
|
50
51
|
If you use `vima`, please cite:
|
|
51
52
|
|
|
52
|
-
[Y. Reshef, et al. Powerful and accurate case-control analysis of spatial molecular data. bioRxiv. https://doi.org/10.1101/2025.02.07.637149v1](https://www.biorxiv.org/content/10.1101/2025.02.07.637149v2).
|
|
53
|
+
[Y. Reshef, et al. Powerful and accurate case-control analysis of spatial molecular data. bioRxiv (In press at _Nature Methods_). https://doi.org/10.1101/2025.02.07.637149v1](https://www.biorxiv.org/content/10.1101/2025.02.07.637149v2).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|