zmap-tools 0.2.1__tar.gz → 0.2.2.2__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.
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/PKG-INFO +15 -11
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/pyproject.toml +17 -11
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/predict/knn_backend.py +93 -15
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/predict/predict.py +142 -37
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap_tools.egg-info/PKG-INFO +15 -11
- zmap_tools-0.2.2.2/src/zmap_tools.egg-info/requires.txt +17 -0
- zmap_tools-0.2.1/src/zmap_tools.egg-info/requires.txt +0 -13
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/LICENSE +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/README.md +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/setup.cfg +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/__init__.py +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/dotplot/__init__.py +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/dotplot/dotplot_gene.py +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/dotplot/dotplot_group.py +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/predict/__init__.py +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/reference/__init__.py +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/reference/load_h5ad.py +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap/reference/markers.py +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap_tools.egg-info/SOURCES.txt +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap_tools.egg-info/dependency_links.txt +0 -0
- {zmap_tools-0.2.1 → zmap_tools-0.2.2.2}/src/zmap_tools.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zmap-tools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2.2
|
|
4
4
|
Summary: API for working with ZMAP (Zebrafish Multi-Atlas Project)
|
|
5
5
|
Author-email: Daniel Wagner <Daniel.Wagner@ucsf.edu>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -47,19 +47,23 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
47
47
|
Requires-Python: >=3.10
|
|
48
48
|
Description-Content-Type: text/markdown
|
|
49
49
|
License-File: LICENSE
|
|
50
|
-
Requires-Dist: anndata
|
|
51
|
-
Requires-Dist: scanpy
|
|
52
|
-
Requires-Dist: numpy
|
|
53
|
-
Requires-Dist:
|
|
54
|
-
Requires-Dist:
|
|
50
|
+
Requires-Dist: anndata==0.12.18
|
|
51
|
+
Requires-Dist: scanpy==1.12.1
|
|
52
|
+
Requires-Dist: numpy==2.0.2
|
|
53
|
+
Requires-Dist: numba==0.60.0
|
|
54
|
+
Requires-Dist: llvmlite==0.43.0
|
|
55
|
+
Requires-Dist: pandas<3.0,>=2.0
|
|
56
|
+
Requires-Dist: matplotlib<4.0,>=3.7
|
|
55
57
|
Requires-Dist: requests>=2.31
|
|
56
|
-
Requires-Dist: scipy
|
|
57
|
-
Requires-Dist: scikit-learn
|
|
58
|
-
Requires-Dist: faiss-cpu
|
|
59
|
-
Requires-Dist: seaborn
|
|
58
|
+
Requires-Dist: scipy==1.16.3
|
|
59
|
+
Requires-Dist: scikit-learn==1.6.1
|
|
60
|
+
Requires-Dist: faiss-cpu==1.14.3
|
|
61
|
+
Requires-Dist: seaborn==0.13.2
|
|
60
62
|
Requires-Dist: tqdm>=4.66
|
|
61
63
|
Requires-Dist: adjustText>=0.8
|
|
62
|
-
Requires-Dist: symphonypy
|
|
64
|
+
Requires-Dist: symphonypy==0.2.4
|
|
65
|
+
Requires-Dist: igraph==1.0.0
|
|
66
|
+
Requires-Dist: leidenalg==0.12.0
|
|
63
67
|
Dynamic: license-file
|
|
64
68
|
|
|
65
69
|
# zmap-tools
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "zmap-tools"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2.2"
|
|
8
8
|
description = "API for working with ZMAP (Zebrafish Multi-Atlas Project)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -27,19 +27,25 @@ classifiers = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
dependencies = [
|
|
30
|
-
"anndata
|
|
31
|
-
"scanpy
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
30
|
+
"anndata==0.12.18",
|
|
31
|
+
"scanpy==1.12.1",
|
|
32
|
+
|
|
33
|
+
"numpy==2.0.2",
|
|
34
|
+
"numba==0.60.0",
|
|
35
|
+
"llvmlite==0.43.0",
|
|
36
|
+
|
|
37
|
+
"pandas>=2.0,<3.0",
|
|
38
|
+
"matplotlib>=3.7,<4.0",
|
|
35
39
|
"requests>=2.31",
|
|
36
|
-
"scipy
|
|
37
|
-
"scikit-learn
|
|
38
|
-
"faiss-cpu
|
|
39
|
-
"seaborn
|
|
40
|
+
"scipy==1.16.3",
|
|
41
|
+
"scikit-learn==1.6.1",
|
|
42
|
+
"faiss-cpu==1.14.3",
|
|
43
|
+
"seaborn==0.13.2",
|
|
40
44
|
"tqdm>=4.66",
|
|
41
45
|
"adjustText>=0.8",
|
|
42
|
-
"symphonypy
|
|
46
|
+
"symphonypy==0.2.4",
|
|
47
|
+
"igraph==1.0.0",
|
|
48
|
+
"leidenalg==0.12.0",
|
|
43
49
|
]
|
|
44
50
|
|
|
45
51
|
[project.urls]
|
|
@@ -18,10 +18,12 @@ _FAISS_INDEX_CACHE_ORDER: list[tuple[str, str, int, int]] = []
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def _as_float32_c(x: np.ndarray) -> np.ndarray:
|
|
21
|
+
"""Cast array to float32 and ensure C-contiguous memory layout (required by FAISS)."""
|
|
21
22
|
return np.ascontiguousarray(np.asarray(x, dtype=np.float32))
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def _l2_normalize(x: np.ndarray) -> np.ndarray:
|
|
26
|
+
"""Row-wise L2-normalize an array to unit vectors (used for cosine similarity via inner product)."""
|
|
25
27
|
x = _as_float32_c(x)
|
|
26
28
|
norms = np.linalg.norm(x, axis=1, keepdims=True)
|
|
27
29
|
norms = np.where(norms == 0, 1.0, norms)
|
|
@@ -29,6 +31,7 @@ def _l2_normalize(x: np.ndarray) -> np.ndarray:
|
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
def _parse_cuda_device(device: str) -> int:
|
|
34
|
+
"""Parse a device string like 'cuda:1' into an integer GPU index. Returns 0 on failure."""
|
|
32
35
|
d = str(device).strip().lower()
|
|
33
36
|
if ":" in d:
|
|
34
37
|
try:
|
|
@@ -44,6 +47,17 @@ def _search_sklearn(
|
|
|
44
47
|
n_neighbors: int,
|
|
45
48
|
metric: str,
|
|
46
49
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
50
|
+
"""
|
|
51
|
+
kNN search using scikit-learn's NearestNeighbors.
|
|
52
|
+
|
|
53
|
+
Used as the fallback when FAISS is unavailable or fails. Slower than FAISS
|
|
54
|
+
on large references but supports any sklearn-compatible metric.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
neighbor_indices : np.ndarray, shape (n_query, n_neighbors)
|
|
59
|
+
distances : np.ndarray, shape (n_query, n_neighbors)
|
|
60
|
+
"""
|
|
47
61
|
nn = NearestNeighbors(n_neighbors=n_neighbors, metric=metric)
|
|
48
62
|
nn.fit(ref)
|
|
49
63
|
distances, neighbor_indices = nn.kneighbors(query, return_distance=True)
|
|
@@ -59,6 +73,44 @@ def _search_faiss(
|
|
|
59
73
|
nprobe: int | None = None,
|
|
60
74
|
cache_key: str | None = None,
|
|
61
75
|
) -> tuple[np.ndarray, np.ndarray, dict[str, Any]]:
|
|
76
|
+
"""
|
|
77
|
+
kNN search using FAISS with optional GPU acceleration and index caching.
|
|
78
|
+
|
|
79
|
+
Builds an IVF-Flat index by default for fast approximate search on large references.
|
|
80
|
+
Falls back to an exact Flat index if IVF training fails. Cosine similarity is
|
|
81
|
+
implemented as inner product after L2-normalization; euclidean uses L2 distance.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
ref : np.ndarray, shape (n_ref, d)
|
|
86
|
+
Reference embedding matrix.
|
|
87
|
+
query : np.ndarray, shape (n_query, d)
|
|
88
|
+
Query embedding matrix.
|
|
89
|
+
n_neighbors : int
|
|
90
|
+
Number of nearest neighbors to return.
|
|
91
|
+
metric : {'euclidean', 'cosine'}
|
|
92
|
+
Distance metric.
|
|
93
|
+
device : str
|
|
94
|
+
Target device — 'cpu', 'auto', 'cuda', or 'cuda:<id>'.
|
|
95
|
+
nprobe : int, optional
|
|
96
|
+
Number of IVF cells to probe during search. Higher values increase recall
|
|
97
|
+
at the cost of speed. Defaults to DEFAULT_FAISS_NPROBE (16).
|
|
98
|
+
cache_key : str, optional
|
|
99
|
+
If provided, the built FAISS index is cached in-memory under this key and
|
|
100
|
+
reused on subsequent calls with matching (key, metric, n_ref, d). Up to
|
|
101
|
+
MAX_FAISS_INDEX_CACHE (16) indices are retained (LRU eviction).
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
indices : np.ndarray, shape (n_query, n_neighbors), dtype int64
|
|
106
|
+
Indices into ref for each neighbor. Entries are -1 (set to NaN in distances)
|
|
107
|
+
when FAISS cannot fill all k neighbors (rare with Flat indices).
|
|
108
|
+
distances : np.ndarray, shape (n_query, n_neighbors), dtype float32
|
|
109
|
+
Euclidean distances or cosine-distance proxies (1 - cosine_similarity).
|
|
110
|
+
meta : dict
|
|
111
|
+
Diagnostic info: backend_used, device_used, index_type, nlist, nprobe,
|
|
112
|
+
cache_key_used, cache_hit.
|
|
113
|
+
"""
|
|
62
114
|
try:
|
|
63
115
|
import faiss # type: ignore
|
|
64
116
|
except Exception as e:
|
|
@@ -233,21 +285,47 @@ def knn_search(
|
|
|
233
285
|
cache_key: str | None = None,
|
|
234
286
|
) -> tuple[np.ndarray, np.ndarray, dict[str, Any]]:
|
|
235
287
|
"""
|
|
236
|
-
Unified kNN search
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
288
|
+
Unified kNN search supporting FAISS and scikit-learn backends.
|
|
289
|
+
|
|
290
|
+
Tries FAISS first (fast, approximate) and falls back to sklearn (exact) if
|
|
291
|
+
FAISS is unavailable or raises. Both backends return consistent output shapes
|
|
292
|
+
and distance semantics.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
ref : np.ndarray, shape (n_ref, d)
|
|
297
|
+
Reference embedding matrix.
|
|
298
|
+
query : np.ndarray, shape (n_query, d)
|
|
299
|
+
Query embedding matrix.
|
|
300
|
+
n_neighbors : int
|
|
301
|
+
Number of nearest neighbors to return per query point.
|
|
302
|
+
metric : {'cosine', 'euclidean'}, default 'cosine'
|
|
303
|
+
Distance metric. Cosine is computed as 1 - cosine_similarity.
|
|
304
|
+
backend : {'auto', 'faiss', 'sklearn'}, default 'auto'
|
|
305
|
+
Search backend. 'auto' tries FAISS first and falls back to sklearn.
|
|
306
|
+
'faiss' also falls back to sklearn with a warning if FAISS fails.
|
|
307
|
+
'sklearn' skips FAISS entirely.
|
|
308
|
+
device : {'auto', 'cpu', 'cuda', 'cuda:<id>'}, default 'auto'
|
|
309
|
+
Device for FAISS index. Ignored by the sklearn backend. 'auto' uses
|
|
310
|
+
GPU if FAISS GPU support is available, otherwise CPU.
|
|
311
|
+
nprobe : int, optional
|
|
312
|
+
Number of IVF partitions to probe during FAISS search. Higher values
|
|
313
|
+
improve recall at the cost of speed. Defaults to DEFAULT_FAISS_NPROBE.
|
|
314
|
+
Ignored by the sklearn backend and FAISS Flat indices.
|
|
315
|
+
cache_key : str, optional
|
|
316
|
+
If provided, the FAISS index built for `ref` is cached in-memory under
|
|
317
|
+
this key and reused on subsequent calls with the same (key, metric,
|
|
318
|
+
n_ref, d). Useful when the same reference is queried repeatedly.
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
indices : np.ndarray, shape (n_query, n_neighbors), dtype int64
|
|
323
|
+
Row indices into `ref` for each neighbor, ordered by ascending distance.
|
|
324
|
+
distances : np.ndarray, shape (n_query, n_neighbors), dtype float32
|
|
325
|
+
Corresponding distances. NaN where FAISS returns invalid indices (-1).
|
|
326
|
+
meta : dict
|
|
327
|
+
Diagnostic info including backend_used, device_used, index_type, and
|
|
328
|
+
cache_hit. Useful for verifying which backend and device were actually used.
|
|
251
329
|
"""
|
|
252
330
|
metric = str(metric).lower()
|
|
253
331
|
backend_req = str(backend).lower()
|
|
@@ -232,6 +232,7 @@ def preprocess_adata_query(
|
|
|
232
232
|
adata.X = np.log1p(X_tpm)
|
|
233
233
|
|
|
234
234
|
# ---- 5. bookkeeping ----
|
|
235
|
+
adata.uns["log1p"] = {"base": None}
|
|
235
236
|
adata.uns.setdefault("ZMAP_preprocessing", {})
|
|
236
237
|
adata.uns["ZMAP_preprocessing"]["query"] = {
|
|
237
238
|
"counts_source": counts_source,
|
|
@@ -991,7 +992,7 @@ def predict_labels_kNN(
|
|
|
991
992
|
cache.get('query_basis') == query_basis and
|
|
992
993
|
cache.get('metric') == metric and
|
|
993
994
|
cache.get('n_neighbors') == n_neighbors and
|
|
994
|
-
cache.get('mask_digest') == _mask_digest and
|
|
995
|
+
list(cache.get('mask_digest', [])) == list(_mask_digest) and
|
|
995
996
|
cache.get('tissue_mode', 'none') == expected_cache_mode and
|
|
996
997
|
cache.get('knn_backend_requested', 'auto') == knn_backend and
|
|
997
998
|
cache.get('knn_device_requested', 'auto') == knn_device and
|
|
@@ -2229,6 +2230,17 @@ def aggregate_by_cluster(
|
|
|
2229
2230
|
|
|
2230
2231
|
df = pd.DataFrame(records)
|
|
2231
2232
|
|
|
2233
|
+
# Ensure object columns are h5ad-serializable in uns:
|
|
2234
|
+
# - numeric columns demoted to object by pd.NA → coerce back to float (pd.NA → np.nan)
|
|
2235
|
+
# - string columns with pd.NA → fillna("") and cast to str
|
|
2236
|
+
_numeric_cols = {"top_fraction", "mean_prob", "margin", "second_fraction"}
|
|
2237
|
+
for col in df.columns:
|
|
2238
|
+
if df[col].dtype == object:
|
|
2239
|
+
if col in _numeric_cols:
|
|
2240
|
+
df[col] = pd.to_numeric(df[col], errors="coerce")
|
|
2241
|
+
else:
|
|
2242
|
+
df[col] = df[col].fillna("").astype(str)
|
|
2243
|
+
|
|
2232
2244
|
if save_csv:
|
|
2233
2245
|
os.makedirs(output_dir, exist_ok=True)
|
|
2234
2246
|
out_path = os.path.join(output_dir, f"{label_space}_cluster_summary.csv")
|
|
@@ -2564,6 +2576,7 @@ def plot_embedding_with_ondata_labels(
|
|
|
2564
2576
|
# ---- data/keys ----
|
|
2565
2577
|
color_key: str = "ZMAP_Tissue_predicted",
|
|
2566
2578
|
basis: str = "X_umap",
|
|
2579
|
+
ref_basis: str | None = None,
|
|
2567
2580
|
filter_na: bool = True,
|
|
2568
2581
|
|
|
2569
2582
|
# ---- palette handling ----
|
|
@@ -2579,12 +2592,14 @@ def plot_embedding_with_ondata_labels(
|
|
|
2579
2592
|
# ---- figure style ----
|
|
2580
2593
|
figsize: tuple[float, float] = (6, 6),
|
|
2581
2594
|
dpi: int = 200,
|
|
2595
|
+
title_fontsize: float = 10,
|
|
2582
2596
|
|
|
2583
2597
|
# ---- point style ----
|
|
2584
2598
|
ref_size: float = 2,
|
|
2585
2599
|
ref_alpha: float = 0.3,
|
|
2586
2600
|
test_size: float = 2,
|
|
2587
2601
|
test_alpha: float = 1.0,
|
|
2602
|
+
show_ref: bool = True,
|
|
2588
2603
|
cmap: str = "jet",
|
|
2589
2604
|
frameon: bool = False,
|
|
2590
2605
|
sort_order: bool = True,
|
|
@@ -2625,9 +2640,10 @@ def plot_embedding_with_ondata_labels(
|
|
|
2625
2640
|
Plot a query dataset overlaid on the reference embedding, with on-data labels
|
|
2626
2641
|
and an optional vertical time distribution strip.
|
|
2627
2642
|
|
|
2628
|
-
Renders two layers: (1) the full reference embedding
|
|
2629
|
-
background for spatial context
|
|
2630
|
-
|
|
2643
|
+
Renders up to two layers: (1) optionally, the full reference embedding
|
|
2644
|
+
as a faint grey background for spatial context (controlled by
|
|
2645
|
+
``show_ref``), and (2) the query cells colored by a predicted label
|
|
2646
|
+
column. Labels are drawn directly on the embedding using
|
|
2631
2647
|
``adjustText`` to minimize overlap. A vertical colorbar histogram of
|
|
2632
2648
|
predicted developmental time (``ZMAP_time_id``) can optionally be added
|
|
2633
2649
|
as a strip on the right side of the figure.
|
|
@@ -2642,7 +2658,12 @@ def plot_embedding_with_ondata_labels(
|
|
|
2642
2658
|
Column in ``adata_test.obs`` containing the categorical labels to color
|
|
2643
2659
|
and annotate. Typically a ``_predicted`` column from ``predict_labels_kNN``.
|
|
2644
2660
|
basis : str, default ``"X_umap"``
|
|
2645
|
-
``obsm`` key used for the 2D embedding coordinates in
|
|
2661
|
+
``obsm`` key used for the 2D embedding coordinates in the query dataset.
|
|
2662
|
+
ref_basis : str or None, default ``None``
|
|
2663
|
+
``obsm`` key for the reference background embedding. When ``None``,
|
|
2664
|
+
falls back to ``basis``. Useful when the query embedding lives in a
|
|
2665
|
+
different key than the reference (e.g. ``basis="X_umap_zmap"`` for
|
|
2666
|
+
the query while the reference uses ``"X_umap"``).
|
|
2646
2667
|
filter_na : bool, default ``True``
|
|
2647
2668
|
Drop query cells with ``NaN`` in ``color_key`` before plotting.
|
|
2648
2669
|
palette : dict or None, default ``None``
|
|
@@ -2674,6 +2695,11 @@ def plot_embedding_with_ondata_labels(
|
|
|
2674
2695
|
Scatter point size for query (projected) cells.
|
|
2675
2696
|
test_alpha : float, default ``1.0``
|
|
2676
2697
|
Opacity of query overlay points.
|
|
2698
|
+
show_ref : bool, default ``True``
|
|
2699
|
+
If ``True``, draw the reference embedding as a faint grey background
|
|
2700
|
+
for spatial context. If ``False``, only the query cells are plotted
|
|
2701
|
+
(useful when ``do_ingest=False``). When ``False``, ``adata_ref``
|
|
2702
|
+
may be ``None``.
|
|
2677
2703
|
cmap : str, default ``"jet"``
|
|
2678
2704
|
Colormap used for the reference background scatter.
|
|
2679
2705
|
legend_loc : str, default ``"on data"``
|
|
@@ -2730,7 +2756,7 @@ def plot_embedding_with_ondata_labels(
|
|
|
2730
2756
|
# Only attempt sync if there's no _color_map dict already available
|
|
2731
2757
|
# (annotate_with_zmap populates _color_map from reference colormaps)
|
|
2732
2758
|
cmap_dict_key = f"{base_obs}_color_map"
|
|
2733
|
-
if cmap_dict_key not in adata_test_plot.uns:
|
|
2759
|
+
if cmap_dict_key not in adata_test_plot.uns and adata_ref is not None:
|
|
2734
2760
|
try:
|
|
2735
2761
|
sync_zmap_colors(adata_ref, obs_key=base_obs)
|
|
2736
2762
|
sync_zmap_colors(adata_test_plot, obs_key=color_key, ref_adata=adata_ref, ref_obs_key=base_obs)
|
|
@@ -2795,6 +2821,9 @@ def plot_embedding_with_ondata_labels(
|
|
|
2795
2821
|
ax_umap = None
|
|
2796
2822
|
ax_strip = None
|
|
2797
2823
|
|
|
2824
|
+
# ---- Resolve ref_basis: defaults to basis if not provided ----
|
|
2825
|
+
ref_basis_use = ref_basis if ref_basis is not None else basis
|
|
2826
|
+
|
|
2798
2827
|
# ---- figure + axes layout ----
|
|
2799
2828
|
with plt.rc_context({'figure.figsize': figsize, 'figure.dpi': dpi}):
|
|
2800
2829
|
if has_time:
|
|
@@ -2812,15 +2841,18 @@ def plot_embedding_with_ondata_labels(
|
|
|
2812
2841
|
fig, ax_umap = plt.subplots()
|
|
2813
2842
|
ax_strip = None
|
|
2814
2843
|
|
|
2815
|
-
# ---- reference embedding in UMAP axis ----
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2844
|
+
# ---- reference embedding in UMAP axis (optional) ----
|
|
2845
|
+
if show_ref:
|
|
2846
|
+
ax = sc.pl.embedding(
|
|
2847
|
+
adata_ref,
|
|
2848
|
+
basis=ref_basis_use,
|
|
2849
|
+
show=False,
|
|
2850
|
+
s=ref_size,
|
|
2851
|
+
ax=ax_umap,
|
|
2852
|
+
**ref_kwargs,
|
|
2853
|
+
)
|
|
2854
|
+
else:
|
|
2855
|
+
ax = ax_umap
|
|
2824
2856
|
|
|
2825
2857
|
# ---- query overlay in UMAP axis ----
|
|
2826
2858
|
ax = sc.pl.embedding(
|
|
@@ -2840,6 +2872,7 @@ def plot_embedding_with_ondata_labels(
|
|
|
2840
2872
|
palette=palette,
|
|
2841
2873
|
**test_kwargs,
|
|
2842
2874
|
)
|
|
2875
|
+
ax.title.set_fontsize(title_fontsize)
|
|
2843
2876
|
|
|
2844
2877
|
# --- on-data label styling (only when show_labels=True) ---
|
|
2845
2878
|
if show_labels:
|
|
@@ -3117,20 +3150,26 @@ def map_query_labels(
|
|
|
3117
3150
|
fig = None
|
|
3118
3151
|
if show_plot or save_plots:
|
|
3119
3152
|
plt.rcParams["axes.grid"] = False
|
|
3120
|
-
|
|
3153
|
+
n_rows, n_cols = plot_table.shape
|
|
3154
|
+
tick_fontsize = 8
|
|
3155
|
+
cell_size = 0.25 # inches per cell — fits 8pt labels without crowding
|
|
3156
|
+
fig_w = min(28, max(figsize, n_cols * cell_size))
|
|
3157
|
+
fig_h = min(28, max(figsize, n_rows * cell_size))
|
|
3158
|
+
cb_shrink = min(0.8, 3.0 / fig_h) # keep colorbar ~3 inches tall
|
|
3159
|
+
fig, ax = plt.subplots(figsize=(fig_w, fig_h))
|
|
3121
3160
|
|
|
3122
3161
|
im = ax.imshow(plot_table.to_numpy(), cmap=cmap, vmin=vmin, vmax=vmax)
|
|
3123
3162
|
|
|
3124
3163
|
ax.set_aspect("equal")
|
|
3125
3164
|
ax.set_xticks(np.arange(plot_table.shape[1]))
|
|
3126
3165
|
ax.set_yticks(np.arange(plot_table.shape[0]))
|
|
3127
|
-
ax.set_xticklabels(plot_table.columns, rotation=90)
|
|
3128
|
-
ax.set_yticklabels(plot_table.index)
|
|
3166
|
+
ax.set_xticklabels(plot_table.columns, rotation=90, fontsize=tick_fontsize)
|
|
3167
|
+
ax.set_yticklabels(plot_table.index, fontsize=tick_fontsize)
|
|
3129
3168
|
ax.set_title(title)
|
|
3130
3169
|
ax.set_xlabel(obs_A)
|
|
3131
3170
|
ax.set_ylabel(obs_B)
|
|
3132
3171
|
|
|
3133
|
-
cb = fig.colorbar(im, ax=ax, shrink=
|
|
3172
|
+
cb = fig.colorbar(im, ax=ax, shrink=cb_shrink)
|
|
3134
3173
|
cb.ax.set_ylabel(colorbar_label)
|
|
3135
3174
|
|
|
3136
3175
|
if overlay_values:
|
|
@@ -3228,7 +3267,7 @@ def annotate_with_zmap(
|
|
|
3228
3267
|
predict_kwargs: Mapping[str, Any] | None = None,
|
|
3229
3268
|
|
|
3230
3269
|
# --- output controls ---
|
|
3231
|
-
verbosity: int =
|
|
3270
|
+
verbosity: int = 3, # 0=silent, 1=progress, 2=summary+UMAP+QC, 3=full
|
|
3232
3271
|
debug: bool = False, # if True, re-raise exceptions instead of catching
|
|
3233
3272
|
print_summary: bool | None = None, # deprecated → use verbosity
|
|
3234
3273
|
show_plots: bool | None = None, # deprecated → use verbosity
|
|
@@ -3324,7 +3363,7 @@ def annotate_with_zmap(
|
|
|
3324
3363
|
Extra keyword arguments forwarded to ``predict_labels_kNN``.
|
|
3325
3364
|
For common options, prefer the top-level ``tissue_aware`` and
|
|
3326
3365
|
``evaluate`` parameters instead of passing dicts manually.
|
|
3327
|
-
verbosity : int, default ``
|
|
3366
|
+
verbosity : int, default ``3``
|
|
3328
3367
|
Controls how much output is printed and displayed inline:
|
|
3329
3368
|
|
|
3330
3369
|
- ``0`` — silent (no print, no inline plots).
|
|
@@ -3354,7 +3393,7 @@ def annotate_with_zmap(
|
|
|
3354
3393
|
- ``.obs[f"{label_space}_predicted"]`` — transferred cell labels.
|
|
3355
3394
|
- ``.obs[f"{label_space}_prob"]`` — label confidence (0–1).
|
|
3356
3395
|
- ``.obs["ZMAP_time_id_predicted"]`` — predicted time (hpf).
|
|
3357
|
-
- ``.obsm["
|
|
3396
|
+
- ``.obsm["X_umap_zmap"]`` — projected UMAP coordinates (if ingested).
|
|
3358
3397
|
- ``.uns['zmap_labels']['_last_space']`` — most recent label_space.
|
|
3359
3398
|
- ``.uns['zmap_labels'][label_space]['_run_config']`` — stored run parameters
|
|
3360
3399
|
for zero-arg on-demand plot accessors.
|
|
@@ -3517,9 +3556,29 @@ def annotate_with_zmap(
|
|
|
3517
3556
|
if do_ingest:
|
|
3518
3557
|
if v >= 1:
|
|
3519
3558
|
_zlog("Ingesting query into reference UMAP...")
|
|
3559
|
+
|
|
3560
|
+
# Stash pre-existing X_umap so symphony doesn't clobber it
|
|
3561
|
+
_had_existing_umap = "X_umap" in adata_query.obsm
|
|
3562
|
+
if _had_existing_umap:
|
|
3563
|
+
_existing_umap = adata_query.obsm["X_umap"].copy()
|
|
3564
|
+
|
|
3520
3565
|
with warnings.catch_warnings():
|
|
3521
3566
|
warnings.simplefilter("ignore", FutureWarning)
|
|
3522
3567
|
sp.tl.ingest(adata_query=adata_query, adata_ref=adata_ref)
|
|
3568
|
+
|
|
3569
|
+
# Move ingested coords to X_umap_zmap (non-destructive)
|
|
3570
|
+
adata_query.obsm["X_umap_zmap"] = adata_query.obsm["X_umap"].copy()
|
|
3571
|
+
|
|
3572
|
+
# Restore original X_umap if one existed, otherwise remove
|
|
3573
|
+
if _had_existing_umap:
|
|
3574
|
+
adata_query.obsm["X_umap"] = _existing_umap
|
|
3575
|
+
if v >= 1:
|
|
3576
|
+
_zlog("Restored pre-existing X_umap; ingested coords in X_umap_zmap.")
|
|
3577
|
+
else:
|
|
3578
|
+
del adata_query.obsm["X_umap"]
|
|
3579
|
+
if v >= 1:
|
|
3580
|
+
_zlog("Ingested coords stored in X_umap_zmap.")
|
|
3581
|
+
|
|
3523
3582
|
if v >= 1:
|
|
3524
3583
|
_zlog("Ingestion complete.")
|
|
3525
3584
|
|
|
@@ -3593,15 +3652,26 @@ def annotate_with_zmap(
|
|
|
3593
3652
|
adata_query.uns.setdefault("zmap_labels", {})
|
|
3594
3653
|
adata_query.uns["zmap_labels"]["_last_space"] = space
|
|
3595
3654
|
adata_query.uns["zmap_labels"].setdefault(space, {})
|
|
3655
|
+
# Determine embedding basis and whether to show reference background
|
|
3656
|
+
if do_ingest:
|
|
3657
|
+
_plot_basis = "X_umap_zmap"
|
|
3658
|
+
_show_ref = True
|
|
3659
|
+
else:
|
|
3660
|
+
# No ingest → query lives in its own UMAP space
|
|
3661
|
+
_plot_basis = "X_umap" if "X_umap" in adata_query.obsm else None
|
|
3662
|
+
_show_ref = False
|
|
3663
|
+
|
|
3596
3664
|
adata_query.uns["zmap_labels"][space]["_run_config"] = {
|
|
3597
3665
|
"label_space": space,
|
|
3598
3666
|
"ref_label_col": ref_label_col,
|
|
3599
3667
|
"query_label_col": query_label_col,
|
|
3600
3668
|
"time_col": time_col_actual,
|
|
3601
3669
|
"output_dir": space_dir,
|
|
3670
|
+
"basis": _plot_basis,
|
|
3671
|
+
"show_ref": _show_ref,
|
|
3602
3672
|
}
|
|
3603
|
-
# Store reference UMAP for on-demand plot_embedding (
|
|
3604
|
-
if "X_umap" in adata_ref.obsm:
|
|
3673
|
+
# Store reference UMAP for on-demand plot_embedding (only when ingested)
|
|
3674
|
+
if do_ingest and "X_umap" in adata_ref.obsm:
|
|
3605
3675
|
adata_query.uns["zmap_labels"][space]["_ref_umap"] = (
|
|
3606
3676
|
np.asarray(adata_ref.obsm["X_umap"]).copy()
|
|
3607
3677
|
)
|
|
@@ -3668,6 +3738,7 @@ def annotate_with_zmap(
|
|
|
3668
3738
|
|
|
3669
3739
|
# ------------------------------------------------------------------
|
|
3670
3740
|
# 8. Copy colormap dict from reference → query (order-independent)
|
|
3741
|
+
# Also copy reference color palettes and order lists
|
|
3671
3742
|
# ------------------------------------------------------------------
|
|
3672
3743
|
cmap_src_key = _COLORMAP_UNS_KEY.get(ref_label_col)
|
|
3673
3744
|
if cmap_src_key and cmap_src_key in adata_ref.uns:
|
|
@@ -3687,19 +3758,35 @@ def annotate_with_zmap(
|
|
|
3687
3758
|
"arrays — palette may be incorrect if query categories differ from reference.",
|
|
3688
3759
|
stacklevel=2,
|
|
3689
3760
|
)
|
|
3761
|
+
|
|
3762
|
+
# Standard palettes
|
|
3763
|
+
keys = ["ZMAP_CellType_order","ZMAP_Tissue_order","ZMAP_colormap_C12","ZMAP_colormap_C79",
|
|
3764
|
+
"ZMAP_colormap_G7","ZMAP_colormap_T28","ZMAP_colormap_T7"]
|
|
3765
|
+
for k in keys:
|
|
3766
|
+
if k in adata_ref.uns:
|
|
3767
|
+
adata_query.uns[k] = adata_ref.uns[k]
|
|
3690
3768
|
|
|
3691
3769
|
# ------------------------------------------------------------------
|
|
3692
3770
|
# 9. UMAP overlay figure (v >= 2)
|
|
3693
3771
|
# ------------------------------------------------------------------
|
|
3694
|
-
if
|
|
3772
|
+
if _plot_basis is None:
|
|
3773
|
+
if v >= 1:
|
|
3774
|
+
_zlog(
|
|
3775
|
+
"Skipping UMAP overlay: no embedding found "
|
|
3776
|
+
"(do_ingest=False and no pre-existing X_umap)."
|
|
3777
|
+
)
|
|
3778
|
+
elif v >= 2:
|
|
3695
3779
|
try:
|
|
3696
3780
|
if v >= 1:
|
|
3697
3781
|
_zlog("Plotting UMAP overlay with predicted labels...")
|
|
3698
3782
|
plot_embedding_with_ondata_labels(
|
|
3699
|
-
adata_ref,
|
|
3783
|
+
adata_ref if _show_ref else None,
|
|
3700
3784
|
adata_query,
|
|
3701
3785
|
color_key=f"{space}_predicted",
|
|
3702
3786
|
time_key=time_col_actual,
|
|
3787
|
+
basis=_plot_basis,
|
|
3788
|
+
ref_basis="X_umap",
|
|
3789
|
+
show_ref=_show_ref,
|
|
3703
3790
|
show=True,
|
|
3704
3791
|
save=save_outputs,
|
|
3705
3792
|
output_dir=space_dir,
|
|
@@ -3714,10 +3801,13 @@ def annotate_with_zmap(
|
|
|
3714
3801
|
# Still save the figure even if not showing it
|
|
3715
3802
|
try:
|
|
3716
3803
|
plot_embedding_with_ondata_labels(
|
|
3717
|
-
adata_ref,
|
|
3804
|
+
adata_ref if _show_ref else None,
|
|
3718
3805
|
adata_query,
|
|
3719
3806
|
color_key=f"{space}_predicted",
|
|
3720
3807
|
time_key=time_col_actual,
|
|
3808
|
+
basis=_plot_basis,
|
|
3809
|
+
ref_basis="X_umap",
|
|
3810
|
+
show_ref=_show_ref,
|
|
3721
3811
|
show=False,
|
|
3722
3812
|
save=True,
|
|
3723
3813
|
output_dir=space_dir,
|
|
@@ -3985,6 +4075,7 @@ def validate_markers(
|
|
|
3985
4075
|
groupby=groupby,
|
|
3986
4076
|
method=method,
|
|
3987
4077
|
corr_method=corr_method,
|
|
4078
|
+
use_raw=False,
|
|
3988
4079
|
)
|
|
3989
4080
|
|
|
3990
4081
|
# ---- Extract and filter DE results per group ----
|
|
@@ -4271,19 +4362,30 @@ def plot_embedding(
|
|
|
4271
4362
|
label_space, config = _resolve_config(adata_query, label_space)
|
|
4272
4363
|
space_store = adata_query.uns.get("zmap_labels", {}).get(label_space, {})
|
|
4273
4364
|
|
|
4274
|
-
|
|
4275
|
-
|
|
4365
|
+
# Resolve basis and show_ref from stored config
|
|
4366
|
+
plot_basis = config.get("basis", "X_umap_zmap")
|
|
4367
|
+
show_ref = config.get("show_ref", True)
|
|
4368
|
+
|
|
4369
|
+
if plot_basis is None:
|
|
4276
4370
|
raise KeyError(
|
|
4277
|
-
f"No
|
|
4278
|
-
|
|
4279
|
-
"Run annotate_with_zmap first."
|
|
4371
|
+
f"No embedding basis stored for label_space='{label_space}'. "
|
|
4372
|
+
"The original run had do_ingest=False and no pre-existing X_umap."
|
|
4280
4373
|
)
|
|
4281
4374
|
|
|
4282
|
-
# Build
|
|
4283
|
-
adata_ref_mini =
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4375
|
+
# Build reference shell only when needed
|
|
4376
|
+
adata_ref_mini = None
|
|
4377
|
+
if show_ref:
|
|
4378
|
+
ref_umap = space_store.get("_ref_umap")
|
|
4379
|
+
if ref_umap is None:
|
|
4380
|
+
raise KeyError(
|
|
4381
|
+
f"No stored reference UMAP found at "
|
|
4382
|
+
f"adata.uns['zmap_labels']['{label_space}']['_ref_umap']. "
|
|
4383
|
+
"Run annotate_with_zmap first."
|
|
4384
|
+
)
|
|
4385
|
+
adata_ref_mini = ad.AnnData(
|
|
4386
|
+
obs=pd.DataFrame(index=[f"ref_{i}" for i in range(ref_umap.shape[0])]),
|
|
4387
|
+
)
|
|
4388
|
+
adata_ref_mini.obsm["X_umap"] = ref_umap
|
|
4287
4389
|
|
|
4288
4390
|
color_key = f"{label_space}_predicted"
|
|
4289
4391
|
time_key = config.get("time_col", "ZMAP_time_id_predicted")
|
|
@@ -4294,6 +4396,9 @@ def plot_embedding(
|
|
|
4294
4396
|
adata_query,
|
|
4295
4397
|
color_key=color_key,
|
|
4296
4398
|
time_key=time_key,
|
|
4399
|
+
basis=plot_basis,
|
|
4400
|
+
ref_basis="X_umap",
|
|
4401
|
+
show_ref=show_ref,
|
|
4297
4402
|
show=True,
|
|
4298
4403
|
save=save,
|
|
4299
4404
|
show_labels=show_labels,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zmap-tools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2.2
|
|
4
4
|
Summary: API for working with ZMAP (Zebrafish Multi-Atlas Project)
|
|
5
5
|
Author-email: Daniel Wagner <Daniel.Wagner@ucsf.edu>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -47,19 +47,23 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
47
47
|
Requires-Python: >=3.10
|
|
48
48
|
Description-Content-Type: text/markdown
|
|
49
49
|
License-File: LICENSE
|
|
50
|
-
Requires-Dist: anndata
|
|
51
|
-
Requires-Dist: scanpy
|
|
52
|
-
Requires-Dist: numpy
|
|
53
|
-
Requires-Dist:
|
|
54
|
-
Requires-Dist:
|
|
50
|
+
Requires-Dist: anndata==0.12.18
|
|
51
|
+
Requires-Dist: scanpy==1.12.1
|
|
52
|
+
Requires-Dist: numpy==2.0.2
|
|
53
|
+
Requires-Dist: numba==0.60.0
|
|
54
|
+
Requires-Dist: llvmlite==0.43.0
|
|
55
|
+
Requires-Dist: pandas<3.0,>=2.0
|
|
56
|
+
Requires-Dist: matplotlib<4.0,>=3.7
|
|
55
57
|
Requires-Dist: requests>=2.31
|
|
56
|
-
Requires-Dist: scipy
|
|
57
|
-
Requires-Dist: scikit-learn
|
|
58
|
-
Requires-Dist: faiss-cpu
|
|
59
|
-
Requires-Dist: seaborn
|
|
58
|
+
Requires-Dist: scipy==1.16.3
|
|
59
|
+
Requires-Dist: scikit-learn==1.6.1
|
|
60
|
+
Requires-Dist: faiss-cpu==1.14.3
|
|
61
|
+
Requires-Dist: seaborn==0.13.2
|
|
60
62
|
Requires-Dist: tqdm>=4.66
|
|
61
63
|
Requires-Dist: adjustText>=0.8
|
|
62
|
-
Requires-Dist: symphonypy
|
|
64
|
+
Requires-Dist: symphonypy==0.2.4
|
|
65
|
+
Requires-Dist: igraph==1.0.0
|
|
66
|
+
Requires-Dist: leidenalg==0.12.0
|
|
63
67
|
Dynamic: license-file
|
|
64
68
|
|
|
65
69
|
# zmap-tools
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
anndata==0.12.18
|
|
2
|
+
scanpy==1.12.1
|
|
3
|
+
numpy==2.0.2
|
|
4
|
+
numba==0.60.0
|
|
5
|
+
llvmlite==0.43.0
|
|
6
|
+
pandas<3.0,>=2.0
|
|
7
|
+
matplotlib<4.0,>=3.7
|
|
8
|
+
requests>=2.31
|
|
9
|
+
scipy==1.16.3
|
|
10
|
+
scikit-learn==1.6.1
|
|
11
|
+
faiss-cpu==1.14.3
|
|
12
|
+
seaborn==0.13.2
|
|
13
|
+
tqdm>=4.66
|
|
14
|
+
adjustText>=0.8
|
|
15
|
+
symphonypy==0.2.4
|
|
16
|
+
igraph==1.0.0
|
|
17
|
+
leidenalg==0.12.0
|
|
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
|