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.
Files changed (40) hide show
  1. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/PKG-INFO +3 -2
  2. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/README.md +1 -1
  3. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/setup.cfg +2 -1
  4. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/_settings.py +126 -0
  5. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/dimreduce.py +2 -2
  6. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/ingest.py +6 -5
  7. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/st.py +5 -3
  8. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/train/logging.py +4 -1
  9. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/features.py +15 -2
  10. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/patches.py +3 -3
  11. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/patchexamples.py +12 -11
  12. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/spatial.py +25 -7
  13. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/umaps.py +8 -1
  14. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/PKG-INFO +3 -2
  15. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/requires.txt +1 -0
  16. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/pyproject.toml +0 -0
  17. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/__init__.py +0 -0
  18. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/cc.py +0 -0
  19. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/data/__init__.py +0 -0
  20. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/data/download.py +0 -0
  21. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/data/patchcollection.py +0 -0
  22. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/data/samples.py +0 -0
  23. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/fingerprints.py +0 -0
  24. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/__init__.py +0 -0
  25. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/nonst.py +0 -0
  26. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/ingest/util.py +0 -0
  27. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/__init__.py +0 -0
  28. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/resnet_vae.py +0 -0
  29. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/resnetlight_decoder.py +0 -0
  30. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/resnetlight_encoder.py +0 -0
  31. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/simple_vae.py +0 -0
  32. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/models/vae.py +0 -0
  33. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/patchfeatures.py +0 -0
  34. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/train/__init__.py +0 -0
  35. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/train/training.py +0 -0
  36. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima/vis/__init__.py +0 -0
  37. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/SOURCES.txt +0 -0
  38. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/dependency_links.txt +0 -0
  39. {vima_spatial-0.2.4 → vima_spatial-0.2.6}/src/vima_spatial.egg-info/top_level.txt +0 -0
  40. {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.4
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.4
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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=True)
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
- plt.show()
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'); plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
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
- plt.show()
99
- return ix
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
- plt.show()
148
- else:
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
- plt.show()
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
- plt.show()
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
- fig.show()
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
- plt.show()
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
- plt.show()
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 = plt.gcf()
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
- plt.show()
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
- plt.show()
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.4
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).
@@ -11,6 +11,7 @@ netcdf4
11
11
  seaborn
12
12
  pandas>=2.2.3
13
13
  scipy
14
+ scikit-misc>=0.3.1
14
15
  cna>=0.2.4
15
16
  tqdm
16
17
  pyarrow