spatial-aim 1.0.0__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.
- aim/__init__.py +47 -0
- aim/__main__.py +6 -0
- aim/adata_schema.py +80 -0
- aim/aggregation.py +118 -0
- aim/aim_config.py +76 -0
- aim/analysis/__init__.py +13 -0
- aim/analysis/analysis.py +72 -0
- aim/analysis/biology.py +205 -0
- aim/analysis/confidence.py +49 -0
- aim/analysis/ksweep.py +184 -0
- aim/analysis/loading.py +70 -0
- aim/analysis/onehot.py +41 -0
- aim/analysis/reconstruction.py +131 -0
- aim/analysis/topology.py +142 -0
- aim/analysis/utils.py +16 -0
- aim/cli.py +371 -0
- aim/clustering.py +242 -0
- aim/data/__init__.py +0 -0
- aim/data/prepare/__init__.py +0 -0
- aim/data/prepare/build_imaging_pairs.py +397 -0
- aim/data/prepare/build_xenium_breast_pair.py +310 -0
- aim/data/prepare/convertTsvToCsv.py +39 -0
- aim/data/prepare/convert_csv_to_h5ad.py +217 -0
- aim/data/prepare/createOnlyHVGDataset.py +69 -0
- aim/data/prepare/createSmallerDataset.py +102 -0
- aim/data/prepare/normalize_gene_names.py +56 -0
- aim/data/prepare/perturb_sc_noise.py +107 -0
- aim/data/prepare/register_st_patches.py +180 -0
- aim/data/prepare/split_st_into_islands.py +551 -0
- aim/data/prepare/split_st_into_patches.py +240 -0
- aim/data/prepare/subset_st_hvg.py +43 -0
- aim/data/validate/__init__.py +35 -0
- aim/data/validate/common.py +141 -0
- aim/data/validate/runner.py +335 -0
- aim/data/validate/validate_pair.py +53 -0
- aim/data/validate/validate_sc.py +103 -0
- aim/data/validate/validate_st.py +74 -0
- aim/gui/__init__.py +8 -0
- aim/gui/__main__.py +77 -0
- aim/gui/app.py +1784 -0
- aim/gui/compute.py +106 -0
- aim/gui/data_access.py +292 -0
- aim/gui/render.py +1679 -0
- aim/gui/scaffold.py +129 -0
- aim/gui/widgets.py +675 -0
- aim/io.py +196 -0
- aim/mapping/__init__.py +14 -0
- aim/mapping/base.py +67 -0
- aim/mapping/confidence.py +51 -0
- aim/mapping/nearest_centroid.py +33 -0
- aim/mapping/reference.py +156 -0
- aim/mapping/wann.py +208 -0
- aim/metrics/__init__.py +26 -0
- aim/metrics/biology.py +69 -0
- aim/metrics/cossim.py +127 -0
- aim/metrics/kselection.py +272 -0
- aim/metrics/onehot.py +54 -0
- aim/metrics/reconstruction.py +73 -0
- aim/metrics/topology.py +62 -0
- aim/reference_aligners/__init__.py +0 -0
- aim/reference_aligners/environment_dot.yml +23 -0
- aim/reference_aligners/environment_tacco.yml +28 -0
- aim/reference_aligners/environment_tangram.yml +16 -0
- aim/reference_aligners/registry.py +350 -0
- aim/reference_aligners/run_dot.R +172 -0
- aim/reference_aligners/run_dot.py +116 -0
- aim/reference_aligners/run_tacco.py +143 -0
- aim/reference_aligners/run_tangram.py +210 -0
- aim/sweep.py +261 -0
- aim/tree.py +86 -0
- spatial_aim-1.0.0.dist-info/METADATA +1005 -0
- spatial_aim-1.0.0.dist-info/RECORD +76 -0
- spatial_aim-1.0.0.dist-info/WHEEL +5 -0
- spatial_aim-1.0.0.dist-info/entry_points.txt +2 -0
- spatial_aim-1.0.0.dist-info/licenses/LICENSE +674 -0
- spatial_aim-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ingest three imaging-based spatial <-> scRNA pairs harvested from the Li et al. 2022
|
|
3
|
+
Nature Methods benchmark into the database:
|
|
4
|
+
|
|
5
|
+
ob dataset 12 mouse olfactory bulb seqFISH+ (Eng 2019) x Drop-seq (GSE148360)
|
|
6
|
+
mtg dataset 13 human middle temporal gyrus ISS (SpaceTx) x Allen Human MTG Smart-seq (Hodge 2019)
|
|
7
|
+
cortex dataset 14 mouse cortex seqFISH+ (Eng 2019) x Allen Mouse V1/ALM Smart-seq (Tasic 2018)
|
|
8
|
+
|
|
9
|
+
Produces scRNA/<name>.h5ad + ST/<name>.h5ad (database h5ad contract: raw counts,
|
|
10
|
+
uppercase unique var, obs['cellType'] on scRNA, obsm['spatial'] on ST) and appends a
|
|
11
|
+
row to scRNA/index.csv, ST/index.csv and pairs.csv (originals backed up to *.bak).
|
|
12
|
+
|
|
13
|
+
seqFISH/seqFISH+ spatial data is tiled into multiple fields of view with per-FOV local
|
|
14
|
+
coordinates; we grid-offset the FOVs so the section is one non-overlapping spatial map
|
|
15
|
+
and keep the FOV id in obs so it can be re-split later.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
python -m data_preparation.build_imaging_pairs --pair ob --dl-dir <scratch/dl> [--data-root ...] [--dry-run]
|
|
19
|
+
python -m data_preparation.build_imaging_pairs --pair mtg --dl-dir <scratch/dl> [...]
|
|
20
|
+
python -m data_preparation.build_imaging_pairs --pair cortex --dl-dir <scratch/dl> [...]
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import csv
|
|
25
|
+
import logging
|
|
26
|
+
import shutil
|
|
27
|
+
import sys
|
|
28
|
+
import zipfile
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
import anndata as ad
|
|
32
|
+
import numpy as np
|
|
33
|
+
import pandas as pd
|
|
34
|
+
import scipy.sparse as sp
|
|
35
|
+
|
|
36
|
+
from aim.data_preparation.build_xenium_breast_pair import (
|
|
37
|
+
_uppercase_and_collapse,
|
|
38
|
+
_append_row,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
DEFAULT_DATA_ROOT = Path(r"/01_Datasets")
|
|
44
|
+
|
|
45
|
+
META = {
|
|
46
|
+
"ob": dict(
|
|
47
|
+
did="12",
|
|
48
|
+
name="12_mouse-olfactory-bulb",
|
|
49
|
+
tissue="Mouse olfactory bulb",
|
|
50
|
+
sc_autor="Brann et al",
|
|
51
|
+
sc_tech="Drop-seq",
|
|
52
|
+
sc_quelle="https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE148360",
|
|
53
|
+
sc_link="https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE148360",
|
|
54
|
+
st_autor="Eng et al",
|
|
55
|
+
st_tech="seqFISH+",
|
|
56
|
+
st_quelle="https://www.nature.com/articles/s41586-019-1049-y",
|
|
57
|
+
st_link="https://github.com/CaiGroup/seqFISH-PLUS",
|
|
58
|
+
featured="Li2022",
|
|
59
|
+
match="-",
|
|
60
|
+
),
|
|
61
|
+
"mtg": dict(
|
|
62
|
+
did="12",
|
|
63
|
+
name="12_human-mtg",
|
|
64
|
+
tissue="Human middle temporal gyrus",
|
|
65
|
+
sc_autor="Hodge et al",
|
|
66
|
+
sc_tech="Smart-seq",
|
|
67
|
+
sc_quelle="https://www.nature.com/articles/s41586-019-1506-7",
|
|
68
|
+
sc_link="https://portal.brain-map.org/atlases-and-data/rnaseq/human-mtg-smart-seq",
|
|
69
|
+
st_autor="SpaceTx consortium",
|
|
70
|
+
st_tech="ISS",
|
|
71
|
+
st_quelle="https://www.nature.com/articles/s41592-022-01480-9",
|
|
72
|
+
st_link="https://github.com/spacetx-spacejam/data",
|
|
73
|
+
featured="Li2022",
|
|
74
|
+
match="same_consortium",
|
|
75
|
+
),
|
|
76
|
+
"cortex": dict(
|
|
77
|
+
did="14",
|
|
78
|
+
name="14_mouse-cortex",
|
|
79
|
+
tissue="Mouse cortex (VISp/ALM)",
|
|
80
|
+
sc_autor="Tasic et al",
|
|
81
|
+
sc_tech="Smart-seq",
|
|
82
|
+
sc_quelle="https://www.nature.com/articles/s41586-018-0654-5",
|
|
83
|
+
sc_link="https://portal.brain-map.org/atlases-and-data/rnaseq/mouse-v1-and-alm-smart-seq",
|
|
84
|
+
st_autor="Eng et al",
|
|
85
|
+
st_tech="seqFISH+",
|
|
86
|
+
st_quelle="https://www.nature.com/articles/s41586-019-1049-y",
|
|
87
|
+
st_link="https://github.com/CaiGroup/seqFISH-PLUS",
|
|
88
|
+
featured="Li2022",
|
|
89
|
+
match="-",
|
|
90
|
+
),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# --------------------------------------------------------------------------- #
|
|
95
|
+
# helpers
|
|
96
|
+
# --------------------------------------------------------------------------- #
|
|
97
|
+
def _grid_offset_fov(coords: np.ndarray, fov: np.ndarray) -> np.ndarray:
|
|
98
|
+
"""Lay per-FOV local coordinates onto a padded grid so tiles do not overlap."""
|
|
99
|
+
coords = coords.astype(float)
|
|
100
|
+
fovs = np.unique(fov)
|
|
101
|
+
ncol = int(np.ceil(np.sqrt(len(fovs))))
|
|
102
|
+
tile_w = (coords[:, 0].max() - coords[:, 0].min()) * 1.1
|
|
103
|
+
tile_h = (coords[:, 1].max() - coords[:, 1].min()) * 1.1
|
|
104
|
+
out = coords.copy()
|
|
105
|
+
for k, f in enumerate(fovs):
|
|
106
|
+
r, c = divmod(k, ncol)
|
|
107
|
+
m = fov == f
|
|
108
|
+
out[m, 0] = coords[m, 0] - coords[m, 0].min() + c * tile_w
|
|
109
|
+
out[m, 1] = coords[m, 1] - coords[m, 1].min() + r * tile_h
|
|
110
|
+
return out.astype(np.float32)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _finalize_sc(adata: ad.AnnData) -> ad.AnnData:
|
|
114
|
+
adata.X = sp.csr_matrix(adata.X).astype(np.float32)
|
|
115
|
+
adata = _uppercase_and_collapse(adata)
|
|
116
|
+
adata.obs_names = [str(b) for b in adata.obs_names]
|
|
117
|
+
adata.obs_names_make_unique()
|
|
118
|
+
return adata
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _load_allen(zip_paths: list[Path], region_tags: list[str]) -> ad.AnnData:
|
|
122
|
+
"""Load Allen Smart-seq region zip(s): exon+intron summed, cells x gene-symbols,
|
|
123
|
+
obs['cellType']=cluster. Detects whether the matrix row key is symbol or entrez.
|
|
124
|
+
|
|
125
|
+
Memory-safe: extracts the 4 members to disk, then streams exon+intron in gene
|
|
126
|
+
chunks summing into one preallocated int32 array (avoids pandas int64 blow-up)."""
|
|
127
|
+
parts = []
|
|
128
|
+
for zp, tag in zip(zip_paths, region_tags):
|
|
129
|
+
logger.info(" reading Allen zip %s", zp.name)
|
|
130
|
+
tmp = zp.parent / f"allen_tmp_{tag}"
|
|
131
|
+
tmp.mkdir(exist_ok=True)
|
|
132
|
+
with zipfile.ZipFile(zp) as z:
|
|
133
|
+
names = z.namelist()
|
|
134
|
+
pick = lambda suf: next(n for n in names if n.endswith(suf)) # noqa: E731
|
|
135
|
+
for suf in (
|
|
136
|
+
"genes-rows.csv",
|
|
137
|
+
"samples-columns.csv",
|
|
138
|
+
"exon-matrix.csv",
|
|
139
|
+
"intron-matrix.csv",
|
|
140
|
+
):
|
|
141
|
+
out = tmp / suf
|
|
142
|
+
if not out.exists() or out.stat().st_size == 0:
|
|
143
|
+
with z.open(pick(suf)) as src, open(out, "wb") as dst:
|
|
144
|
+
shutil.copyfileobj(src, dst, length=1 << 24)
|
|
145
|
+
|
|
146
|
+
grows = pd.read_csv(tmp / "genes-rows.csv")
|
|
147
|
+
scols = pd.read_csv(tmp / "samples-columns.csv")
|
|
148
|
+
cells = pd.read_csv(
|
|
149
|
+
tmp / "exon-matrix.csv", index_col=0, nrows=0
|
|
150
|
+
).columns.to_numpy()
|
|
151
|
+
n_genes, n_cells = len(grows), len(cells)
|
|
152
|
+
X = np.empty((n_genes, n_cells), dtype=np.int32)
|
|
153
|
+
gene_key = np.empty(n_genes, dtype=object)
|
|
154
|
+
pos = 0
|
|
155
|
+
ex_it = pd.read_csv(
|
|
156
|
+
tmp / "exon-matrix.csv", index_col=0, dtype=np.int32, chunksize=4000
|
|
157
|
+
)
|
|
158
|
+
in_it = pd.read_csv(
|
|
159
|
+
tmp / "intron-matrix.csv", index_col=0, dtype=np.int32, chunksize=4000
|
|
160
|
+
)
|
|
161
|
+
for ce, ci in zip(ex_it, in_it):
|
|
162
|
+
k = ce.shape[0]
|
|
163
|
+
X[pos : pos + k] = ce.to_numpy() + ci.to_numpy()
|
|
164
|
+
gene_key[pos : pos + k] = ce.index.to_numpy().astype(str)
|
|
165
|
+
pos += k
|
|
166
|
+
X, gene_key = X[:pos], gene_key[:pos]
|
|
167
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
168
|
+
|
|
169
|
+
# map matrix row key -> gene symbol
|
|
170
|
+
sym_col = "gene_symbol" if "gene_symbol" in grows.columns else "gene"
|
|
171
|
+
best, key_col = -1, None
|
|
172
|
+
for c in grows.columns:
|
|
173
|
+
ov = np.isin(gene_key, grows[c].astype(str).to_numpy()).mean()
|
|
174
|
+
if ov > best:
|
|
175
|
+
best, key_col = ov, c
|
|
176
|
+
logger.info(
|
|
177
|
+
" gene key column=%s (match=%.2f) -> symbol column=%s",
|
|
178
|
+
key_col,
|
|
179
|
+
best,
|
|
180
|
+
sym_col,
|
|
181
|
+
)
|
|
182
|
+
id2sym = dict(zip(grows[key_col].astype(str), grows[sym_col].astype(str)))
|
|
183
|
+
var_syms = [id2sym.get(str(g), str(g)) for g in gene_key]
|
|
184
|
+
|
|
185
|
+
scols = scols.set_index(scols.columns[0])
|
|
186
|
+
clusters = (
|
|
187
|
+
scols.reindex([str(c) for c in cells])["cluster"].astype(str).to_numpy()
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
a = ad.AnnData(
|
|
191
|
+
X=sp.csr_matrix(X.T).astype(np.float32),
|
|
192
|
+
obs=pd.DataFrame(
|
|
193
|
+
{"cellType": clusters, "region": tag},
|
|
194
|
+
index=[f"{tag}_{c}" for c in cells],
|
|
195
|
+
),
|
|
196
|
+
var=pd.DataFrame(index=var_syms),
|
|
197
|
+
)
|
|
198
|
+
parts.append(a)
|
|
199
|
+
|
|
200
|
+
if len(parts) > 1:
|
|
201
|
+
adata = ad.concat(parts, join="outer")
|
|
202
|
+
adata.X = sp.csr_matrix(np.nan_to_num(adata.X.toarray())).astype(np.float32)
|
|
203
|
+
else:
|
|
204
|
+
adata = parts[0]
|
|
205
|
+
# drop cells without a real cluster label
|
|
206
|
+
bad = {"nan", "NA", "", "none", "low quality", "Low Quality"}
|
|
207
|
+
keep = ~pd.Series(adata.obs["cellType"].astype(str).to_numpy()).isin(bad).to_numpy()
|
|
208
|
+
adata = adata[keep].copy()
|
|
209
|
+
return adata
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# --------------------------------------------------------------------------- #
|
|
213
|
+
# per-pair builders
|
|
214
|
+
# --------------------------------------------------------------------------- #
|
|
215
|
+
def build_ob(dl: Path) -> tuple[ad.AnnData, ad.AnnData]:
|
|
216
|
+
sd = dl / "seqfish" / "sourcedata"
|
|
217
|
+
counts = pd.read_csv(sd / "ob_counts.csv") # cells x genes
|
|
218
|
+
cen = pd.read_csv(sd / "ob_cellcentroids.csv") # FoV, Cell ID, X, Y
|
|
219
|
+
ann = pd.read_csv(
|
|
220
|
+
dl / "seqfish_ann" / "OB_cell_type_annotations.csv"
|
|
221
|
+
) # index, louvain
|
|
222
|
+
st = ad.AnnData(
|
|
223
|
+
X=sp.csr_matrix(counts.to_numpy(dtype=np.float32)),
|
|
224
|
+
var=pd.DataFrame(index=counts.columns.astype(str)),
|
|
225
|
+
)
|
|
226
|
+
st.obs_names = [f"ob_{i}" for i in range(st.n_obs)]
|
|
227
|
+
st.obs["fov"] = cen["Field of View"].to_numpy()
|
|
228
|
+
st.obs["louvain"] = ann["louvain"].to_numpy()
|
|
229
|
+
st.obsm["spatial"] = _grid_offset_fov(
|
|
230
|
+
cen[["X", "Y"]].to_numpy(), cen["Field of View"].to_numpy()
|
|
231
|
+
)
|
|
232
|
+
st = _uppercase_and_collapse(st)
|
|
233
|
+
|
|
234
|
+
logger.info(" reading OB scRNA (genes x cells)")
|
|
235
|
+
gep = pd.read_csv(dl / "ob_scrna_counts.csv.gz", index_col=0) # genes x cells
|
|
236
|
+
meta = pd.read_csv(dl / "meta_datta.csv.gz", index_col=0) # barcode -> Cell_class
|
|
237
|
+
keep = [b for b in gep.columns if b in meta.index]
|
|
238
|
+
sc = ad.AnnData(
|
|
239
|
+
X=sp.csr_matrix(gep[keep].T.to_numpy(dtype=np.float32)),
|
|
240
|
+
var=pd.DataFrame(index=gep.index.astype(str)),
|
|
241
|
+
)
|
|
242
|
+
sc.obs_names = keep
|
|
243
|
+
sc.obs["cellType"] = meta.reindex(keep)["Cell_class"].astype(str).to_numpy()
|
|
244
|
+
sc.obs["louvain"] = meta.reindex(keep)["seurat_clusters"].astype(str).to_numpy()
|
|
245
|
+
sc = _finalize_sc(sc)
|
|
246
|
+
return sc, st
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def build_cortex(dl: Path) -> tuple[ad.AnnData, ad.AnnData]:
|
|
250
|
+
sd = dl / "seqfish" / "sourcedata"
|
|
251
|
+
counts = pd.read_csv(sd / "cortex_svz_counts.csv")
|
|
252
|
+
cen = pd.read_csv(sd / "cortex_svz_cellcentroids.csv")
|
|
253
|
+
ann = pd.read_csv(dl / "seqfish_ann" / "cortex_svz_cell_type_annotations.csv")
|
|
254
|
+
mask = (cen["Region"] == "Cortex").to_numpy()
|
|
255
|
+
st = ad.AnnData(
|
|
256
|
+
X=sp.csr_matrix(counts.to_numpy(dtype=np.float32)[mask]),
|
|
257
|
+
var=pd.DataFrame(index=counts.columns.astype(str)),
|
|
258
|
+
)
|
|
259
|
+
cen, louv = cen[mask].reset_index(drop=True), ann["louvain"].to_numpy()[mask]
|
|
260
|
+
st.obs_names = [f"cortex_{i}" for i in range(st.n_obs)]
|
|
261
|
+
st.obs["fov"] = cen["Field of View"].to_numpy()
|
|
262
|
+
st.obs["louvain"] = louv
|
|
263
|
+
st.obsm["spatial"] = _grid_offset_fov(
|
|
264
|
+
cen[["X", "Y"]].to_numpy(), cen["Field of View"].to_numpy()
|
|
265
|
+
)
|
|
266
|
+
st = _uppercase_and_collapse(st)
|
|
267
|
+
|
|
268
|
+
sc = (
|
|
269
|
+
_load_allen([dl / "dl_visp", dl / "dl_alm"], ["VISp", "ALM"])
|
|
270
|
+
if (dl / "dl_visp").exists()
|
|
271
|
+
else _load_allen([dl / "visp.zip", dl / "alm.zip"], ["VISp", "ALM"])
|
|
272
|
+
)
|
|
273
|
+
sc = _finalize_sc(sc)
|
|
274
|
+
return sc, st
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def build_mtg(dl: Path) -> tuple[ad.AnnData, ad.AnnData]:
|
|
278
|
+
df = pd.read_csv(dl / "iss_mtg_540.csv") # CellID, genes..., centroidX/Y
|
|
279
|
+
gene_cols = [c for c in df.columns if c not in ("CellID", "centroidX", "centroidY")]
|
|
280
|
+
st = ad.AnnData(
|
|
281
|
+
X=sp.csr_matrix(df[gene_cols].to_numpy(dtype=np.float32)),
|
|
282
|
+
var=pd.DataFrame(index=[g.upper() for g in gene_cols]),
|
|
283
|
+
)
|
|
284
|
+
st.obs_names = [f"iss_{c}" for c in df["CellID"].to_numpy()]
|
|
285
|
+
st.obsm["spatial"] = df[["centroidX", "centroidY"]].to_numpy(dtype=np.float32)
|
|
286
|
+
st = _uppercase_and_collapse(st)
|
|
287
|
+
|
|
288
|
+
sc = _load_allen([dl / "human_mtg.zip"], ["MTG"])
|
|
289
|
+
sc = _finalize_sc(sc)
|
|
290
|
+
return sc, st
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
BUILDERS = {"ob": build_ob, "mtg": build_mtg, "cortex": build_cortex}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# --------------------------------------------------------------------------- #
|
|
297
|
+
def main() -> None:
|
|
298
|
+
logging.basicConfig(
|
|
299
|
+
stream=sys.stdout,
|
|
300
|
+
level=logging.INFO,
|
|
301
|
+
format="%(asctime)s %(levelname)s %(message)s",
|
|
302
|
+
)
|
|
303
|
+
p = argparse.ArgumentParser(
|
|
304
|
+
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
|
|
305
|
+
)
|
|
306
|
+
p.add_argument("--pair", choices=list(BUILDERS), required=True)
|
|
307
|
+
p.add_argument(
|
|
308
|
+
"--dl-dir", type=Path, required=True, help="scratch dir with downloaded inputs"
|
|
309
|
+
)
|
|
310
|
+
p.add_argument("--data-root", type=Path, default=DEFAULT_DATA_ROOT)
|
|
311
|
+
p.add_argument("--dry-run", action="store_true")
|
|
312
|
+
args = p.parse_args()
|
|
313
|
+
|
|
314
|
+
m = META[args.pair]
|
|
315
|
+
sc, st = BUILDERS[args.pair](args.dl_dir)
|
|
316
|
+
shared = sc.var_names.intersection(st.var_names)
|
|
317
|
+
n_types = int(sc.obs["cellType"].nunique())
|
|
318
|
+
logger.info(
|
|
319
|
+
"%s: sc=%d x %d (%d types), st=%d x %d, shared=%d",
|
|
320
|
+
m["name"],
|
|
321
|
+
sc.n_obs,
|
|
322
|
+
sc.n_vars,
|
|
323
|
+
n_types,
|
|
324
|
+
st.n_obs,
|
|
325
|
+
st.n_vars,
|
|
326
|
+
len(shared),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
sc_out = args.data_root / "scRNA" / f"{m['name']}.h5ad"
|
|
330
|
+
st_out = args.data_root / "ST" / f"{m['name']}.h5ad"
|
|
331
|
+
if args.dry_run:
|
|
332
|
+
logger.info("[dry-run] would write %s and %s", sc_out, st_out)
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
sc.write_h5ad(sc_out)
|
|
336
|
+
logger.info("wrote %s", sc_out)
|
|
337
|
+
st.write_h5ad(st_out)
|
|
338
|
+
logger.info("wrote %s", st_out)
|
|
339
|
+
|
|
340
|
+
_append_row(
|
|
341
|
+
args.data_root / "scRNA" / "index.csv",
|
|
342
|
+
{
|
|
343
|
+
"Name": m["name"],
|
|
344
|
+
"Tissue": m["tissue"],
|
|
345
|
+
"Quelle": m["sc_quelle"],
|
|
346
|
+
"Autor": m["sc_autor"],
|
|
347
|
+
"Tech": m["sc_tech"],
|
|
348
|
+
"FeaturedIn": m["featured"],
|
|
349
|
+
"CellCount": sc.n_obs,
|
|
350
|
+
"GeneCount": sc.n_vars,
|
|
351
|
+
"CellTypeKey0": "cellType",
|
|
352
|
+
"NumberCellTypes0": n_types,
|
|
353
|
+
"Links": m["sc_link"],
|
|
354
|
+
},
|
|
355
|
+
False,
|
|
356
|
+
)
|
|
357
|
+
_append_row(
|
|
358
|
+
args.data_root / "ST" / "index.csv",
|
|
359
|
+
{
|
|
360
|
+
"Name": m["name"],
|
|
361
|
+
"Tissue": m["tissue"],
|
|
362
|
+
"Quelle": m["st_quelle"],
|
|
363
|
+
"Autor": m["st_autor"],
|
|
364
|
+
"Tech": m["st_tech"],
|
|
365
|
+
"FeaturedIn": m["featured"],
|
|
366
|
+
"SpotCount": st.n_obs,
|
|
367
|
+
"GeneCount": st.n_vars,
|
|
368
|
+
"Links": m["st_link"],
|
|
369
|
+
},
|
|
370
|
+
False,
|
|
371
|
+
)
|
|
372
|
+
pairs_path = args.data_root / "pairs.csv"
|
|
373
|
+
with open(pairs_path, newline="") as f:
|
|
374
|
+
existing = list(csv.DictReader(f))
|
|
375
|
+
next_id = (
|
|
376
|
+
max(
|
|
377
|
+
(int(r["PairID"]) for r in existing if r.get("PairID", "").isdigit()),
|
|
378
|
+
default=-1,
|
|
379
|
+
)
|
|
380
|
+
+ 1
|
|
381
|
+
)
|
|
382
|
+
_append_row(
|
|
383
|
+
pairs_path,
|
|
384
|
+
{
|
|
385
|
+
"PairID": next_id,
|
|
386
|
+
"scName": m["name"],
|
|
387
|
+
"stName": m["name"],
|
|
388
|
+
"NumberSharedGenes": len(shared),
|
|
389
|
+
"ExperimentMatch": m["match"],
|
|
390
|
+
},
|
|
391
|
+
False,
|
|
392
|
+
)
|
|
393
|
+
logger.info("Done %s.", m["name"])
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if __name__ == "__main__":
|
|
397
|
+
main()
|