vima-spatial 0.2.7__tar.gz → 0.2.8__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.7 → vima_spatial-0.2.8}/PKG-INFO +1 -1
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/setup.cfg +1 -1
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/ingest/dimreduce.py +108 -49
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/ingest/ingest.py +1 -1
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima_spatial.egg-info/PKG-INFO +1 -1
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/README.md +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/pyproject.toml +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/__init__.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/_settings.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/cc.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/data/__init__.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/data/download.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/data/patchcollection.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/data/samples.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/fingerprints.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/ingest/__init__.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/ingest/nonst.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/ingest/st.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/ingest/util.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/models/__init__.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/models/resnet_vae.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/models/resnetlight_decoder.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/models/resnetlight_encoder.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/models/simple_vae.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/models/vae.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/patchfeatures.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/train/__init__.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/train/logging.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/train/training.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/vis/__init__.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/vis/features.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/vis/patches.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/vis/patchexamples.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/vis/spatial.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima/vis/umaps.py +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima_spatial.egg-info/SOURCES.txt +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima_spatial.egg-info/dependency_links.txt +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima_spatial.egg-info/requires.txt +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/src/vima_spatial.egg-info/top_level.txt +0 -0
- {vima_spatial-0.2.7 → vima_spatial-0.2.8}/tests/test_ra_regression.py +0 -0
|
@@ -16,10 +16,10 @@ def metapixels_allsamples(normedpixelsdir, masksdir, sids, total_n_metapixels):
|
|
|
16
16
|
"""
|
|
17
17
|
Pool metapixels across all samples for a more robust PCA fit.
|
|
18
18
|
|
|
19
|
-
Loads each sample's normalized pixels,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
Loads each sample's normalized pixels, builds roughly
|
|
20
|
+
``total_n_metapixels // len(sids)`` randomly chosen metapixels from it, and
|
|
21
|
+
standardizes them with the stored per-marker means/stds. Warns if a sample's
|
|
22
|
+
markers differ from the first sample's.
|
|
23
23
|
|
|
24
24
|
Parameters
|
|
25
25
|
----------
|
|
@@ -55,7 +55,7 @@ def metapixels_allsamples(normedpixelsdir, masksdir, sids, total_n_metapixels):
|
|
|
55
55
|
for i, sid in enumerate(settings.progress(sids, name='creating metapixels')):
|
|
56
56
|
da = xr.open_dataarray(f'{normedpixelsdir}/{sid}.nc')
|
|
57
57
|
mask_da = xr.open_dataarray(f'{masksdir}/{sid}.nc')
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
# ensure same markers in same order in all files
|
|
60
60
|
markers = list(da.marker.values)
|
|
61
61
|
if ref_markers is None:
|
|
@@ -68,18 +68,21 @@ def metapixels_allsamples(normedpixelsdir, masksdir, sids, total_n_metapixels):
|
|
|
68
68
|
f'{len(missing)} missing, {len(extra)} extra vs {ref_sid}')
|
|
69
69
|
logger.warning(f'{sid} has different markers ({len(markers)}) '
|
|
70
70
|
f'than {ref_sid} ({len(ref_markers)}): {detail}')
|
|
71
|
-
|
|
72
|
-
means = xr.DataArray(da.attrs['means'], dims='marker')
|
|
73
|
-
stds = xr.DataArray(da.attrs['stds'], dims='marker')
|
|
74
|
-
da = ((da - means) / stds).where(mask_da, 0)
|
|
75
71
|
|
|
76
|
-
|
|
72
|
+
means, stds = da.attrs['means'], da.attrs['stds']
|
|
73
|
+
|
|
74
|
+
# metapixels are built from the un-standardized pixels and standardized
|
|
75
|
+
# afterward: averaging over a window and the affine (x-mean)/std commute,
|
|
76
|
+
# so this is identical to standardizing the full array first but avoids
|
|
77
|
+
# materializing several (y, x, marker)-sized temporaries.
|
|
78
|
+
mp, all_npixels[sid] = metapixels(da, mask_da, n_metapixels=nmp_per_sample)
|
|
77
79
|
da.close(); mask_da.close()
|
|
78
80
|
del da, mask_da
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
|
|
82
|
+
mp -= means
|
|
83
|
+
mp /= stds
|
|
84
|
+
all_metapixels[sid] = pd.DataFrame(data=mp, columns=markers)
|
|
85
|
+
del mp
|
|
83
86
|
|
|
84
87
|
# visualize distribution of num non-empty pixels per metapixel in this sample
|
|
85
88
|
if settings.show_plots():
|
|
@@ -95,33 +98,61 @@ def metapixels_allsamples(normedpixelsdir, masksdir, sids, total_n_metapixels):
|
|
|
95
98
|
|
|
96
99
|
return all_metapixels, all_npixels
|
|
97
100
|
|
|
98
|
-
def metapixels(s, mask, npixels_thresh=0):
|
|
101
|
+
def metapixels(s, mask, npixels_thresh=0, n_metapixels=None, window=5):
|
|
99
102
|
"""
|
|
100
103
|
Pool each pixel with its neighbors into a metapixel.
|
|
101
104
|
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
Averages each marker over a ``window``-by-``window`` window centered on a
|
|
106
|
+
pixel, using only the non-empty (masked) pixels in that window, so each
|
|
104
107
|
metapixel is the average over its non-empty neighbors. Metapixels with at
|
|
105
108
|
most ``npixels_thresh`` contributing pixels are dropped.
|
|
106
109
|
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
n_metapixels
|
|
113
|
+
If given, uniformly sample at most this many metapixel centers and
|
|
114
|
+
compute only those. Since the caller typically keeps a small random
|
|
115
|
+
subset anyway, this avoids convolving the whole (y, x, marker) array,
|
|
116
|
+
which dominates the cost for large marker panels.
|
|
117
|
+
|
|
107
118
|
Returns
|
|
108
119
|
-------
|
|
109
120
|
tuple
|
|
110
|
-
``(
|
|
111
|
-
|
|
121
|
+
``(metapixels, npixels)``: a ``(metapixel, marker)`` float32 array and
|
|
122
|
+
the per-metapixel count of contributing non-empty pixels.
|
|
112
123
|
"""
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
npixels = convolve(mask.
|
|
119
|
-
|
|
120
|
-
#
|
|
121
|
-
|
|
124
|
+
mask = mask.data
|
|
125
|
+
H, W = mask.shape
|
|
126
|
+
|
|
127
|
+
# how many non-empty pixels contribute to each candidate metapixel (cheap: 2D only)
|
|
128
|
+
kernel = np.ones((window, window), np.float32)
|
|
129
|
+
npixels = convolve(mask.astype(np.float32), kernel, mode="constant")
|
|
130
|
+
|
|
131
|
+
# pick the metapixel centers, sampling before doing any work over markers
|
|
132
|
+
centers = np.flatnonzero(npixels.ravel() > npixels_thresh)
|
|
133
|
+
if n_metapixels is not None and len(centers) > n_metapixels:
|
|
134
|
+
centers = centers[np.random.choice(len(centers), n_metapixels, replace=False)]
|
|
135
|
+
npixels = npixels.ravel()[centers]
|
|
136
|
+
|
|
137
|
+
# sum each window by gathering its non-empty pixels, one neighbor offset at a time
|
|
138
|
+
data = s.data.reshape(H * W, -1)
|
|
139
|
+
mask = mask.ravel()
|
|
140
|
+
r, c = np.divmod(centers, W)
|
|
141
|
+
mp = np.zeros((len(centers), data.shape[1]), np.float32)
|
|
142
|
+
rad = window // 2
|
|
143
|
+
for dr in range(-rad, rad + 1):
|
|
144
|
+
rr = r + dr
|
|
145
|
+
for dc in range(-rad, rad + 1):
|
|
146
|
+
cc = c + dc
|
|
147
|
+
neighbor = rr * W + cc
|
|
148
|
+
contributes = (rr >= 0) & (rr < H) & (cc >= 0) & (cc < W)
|
|
149
|
+
contributes &= mask[np.where(contributes, neighbor, 0)]
|
|
150
|
+
i = np.flatnonzero(contributes)
|
|
151
|
+
mp[i] += data[neighbor[i]]
|
|
122
152
|
|
|
123
153
|
# divide each metapixel by the # of non-empty pixels that contributed to it and return
|
|
124
|
-
|
|
154
|
+
mp /= npixels[:, None]
|
|
155
|
+
return mp, npixels
|
|
125
156
|
|
|
126
157
|
# mps should be an array of dataframes containing metapixels
|
|
127
158
|
def pca_metapixels(mps, k):
|
|
@@ -142,17 +173,33 @@ def pca_metapixels(mps, k):
|
|
|
142
173
|
Returns
|
|
143
174
|
-------
|
|
144
175
|
tuple
|
|
145
|
-
``(loadings,
|
|
146
|
-
|
|
176
|
+
``(loadings, allmp)``: the gene-by-component loading matrix and the
|
|
177
|
+
standardized metapixel AnnData.
|
|
147
178
|
"""
|
|
148
179
|
logger.info('merging and standardizing metapixels')
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
180
|
+
mps = list(mps)
|
|
181
|
+
markers = mps[0].columns
|
|
182
|
+
n = sum(len(mp) for mp in mps)
|
|
183
|
+
|
|
184
|
+
# merge into one preallocated float32 matrix and standardize it in place,
|
|
185
|
+
# accumulating the moments in float64. Doing this in pandas instead upcasts
|
|
186
|
+
# the whole matrix to float64 and copies it once per operation, which at
|
|
187
|
+
# these sizes costs more than the PCA itself.
|
|
188
|
+
allmp = np.empty((n, len(markers)), np.float32)
|
|
189
|
+
i = 0
|
|
190
|
+
for mp in mps:
|
|
191
|
+
allmp[i:i+len(mp)] = mp.to_numpy(np.float32, copy=False)
|
|
192
|
+
i += len(mp)
|
|
193
|
+
del mps
|
|
194
|
+
|
|
195
|
+
allmp -= (allmp.sum(axis=0, dtype=np.float64) / n).astype(np.float32)
|
|
196
|
+
stds = np.sqrt(np.einsum('ij,ij->j', allmp, allmp, dtype=np.float64) / n)
|
|
197
|
+
stds[stds == 0] = 1 # constant features stay exactly 0, as the old fillna(0) left them
|
|
198
|
+
allmp /= stds.astype(np.float32)
|
|
199
|
+
|
|
200
|
+
allmp = ad.AnnData(X=allmp,
|
|
201
|
+
obs=pd.DataFrame(index=np.arange(n).astype(str)),
|
|
202
|
+
var=pd.DataFrame(index=markers))
|
|
156
203
|
logger.info(f'Metapixel matrix: {allmp.shape[0]:,} pixels × {allmp.shape[1]} features')
|
|
157
204
|
|
|
158
205
|
logger.info('performing PCA...')
|
|
@@ -176,7 +223,7 @@ def pca_metapixels(mps, k):
|
|
|
176
223
|
plt.xticks(range(len(loadings.columns)), loadings.columns, rotation=90)
|
|
177
224
|
settings.show('pc_loadings')
|
|
178
225
|
|
|
179
|
-
return loadings,
|
|
226
|
+
return loadings, allmp
|
|
180
227
|
|
|
181
228
|
def pca_pixels(normedpixelsdir, masksdir, pcloadings, sids):
|
|
182
229
|
"""
|
|
@@ -193,16 +240,18 @@ def pca_pixels(normedpixelsdir, masksdir, pcloadings, sids):
|
|
|
193
240
|
column giving the source sample.
|
|
194
241
|
"""
|
|
195
242
|
pcs = []
|
|
196
|
-
|
|
243
|
+
sid_codes = []
|
|
244
|
+
# project in float32: a DataFrame (or float64) right-hand side silently
|
|
245
|
+
# promotes the result, doubling both the projection cost and the size of
|
|
246
|
+
# the returned table, which has a row per pixel in the whole dataset
|
|
247
|
+
loadings = np.ascontiguousarray(np.asarray(pcloadings), dtype=np.float32)
|
|
197
248
|
|
|
198
249
|
logger.info('Applying PCA projection to each sample')
|
|
199
|
-
for sid in settings.progress(sids, name='pixels -> PCA space'):
|
|
250
|
+
for code, sid in enumerate(settings.progress(sids, name='pixels -> PCA space')):
|
|
200
251
|
da = xr.open_dataarray(f'{normedpixelsdir}/{sid}.nc')
|
|
201
252
|
mask_da = xr.open_dataarray(f'{masksdir}/{sid}.nc')
|
|
202
253
|
|
|
203
|
-
means =
|
|
204
|
-
stds = xr.DataArray(da.attrs['stds'], dims='marker')
|
|
205
|
-
da = ((da - means) / stds).where(mask_da, 0)
|
|
254
|
+
means, stds = da.attrs['means'], da.attrs['stds']
|
|
206
255
|
|
|
207
256
|
# load raw arrays and close before dtype conversion so we never hold
|
|
208
257
|
# two full (H × W × n_genes) copies simultaneously
|
|
@@ -213,19 +262,29 @@ def pca_pixels(normedpixelsdir, masksdir, pcloadings, sids):
|
|
|
213
262
|
pl = data.astype(np.float32, copy=False)[mask]
|
|
214
263
|
del data, mask; gc.collect()
|
|
215
264
|
|
|
216
|
-
|
|
265
|
+
# standardize the non-empty pixels only, rather than the full
|
|
266
|
+
# (y, x, marker) array; empty pixels are dropped by the mask anyway
|
|
267
|
+
pl -= means
|
|
268
|
+
pl /= stds
|
|
269
|
+
|
|
270
|
+
pl_pca = pl.dot(loadings)
|
|
217
271
|
pcs.append(pl_pca)
|
|
218
|
-
|
|
272
|
+
sid_codes.append(np.full(pl_pca.shape[0], code, dtype=np.int32))
|
|
219
273
|
del pl; gc.collect()
|
|
220
274
|
|
|
221
275
|
# concatenate
|
|
222
276
|
pcs = np.vstack(pcs)
|
|
223
|
-
|
|
277
|
+
sid_codes = np.concatenate(sid_codes)
|
|
224
278
|
|
|
225
279
|
allpixels_pca = pd.DataFrame(
|
|
226
280
|
pcs,
|
|
227
|
-
columns=[f'PC{i}' for i in range(1,
|
|
281
|
+
columns=[f'PC{i}' for i in range(1, loadings.shape[1] + 1)]
|
|
228
282
|
)
|
|
229
|
-
|
|
283
|
+
# categorical rather than an object column: one code per pixel instead of
|
|
284
|
+
# one pointer, over tens of millions of rows
|
|
285
|
+
# drop categories for samples that contributed no pixels, so downstream
|
|
286
|
+
# get_dummies (Harmony) never sees an all-zero batch column
|
|
287
|
+
allpixels_pca['sid'] = pd.Categorical.from_codes(
|
|
288
|
+
sid_codes, categories=list(sids)).remove_unused_categories()
|
|
230
289
|
|
|
231
290
|
return allpixels_pca
|
|
@@ -149,7 +149,7 @@ def pca_pixels(outdir, repname, nmetamarkers=10, npixels_to_plot=50000,
|
|
|
149
149
|
total_n_metapixels=total_n_metapixels)
|
|
150
150
|
|
|
151
151
|
# PCA the metapixels
|
|
152
|
-
loadings,
|
|
152
|
+
loadings, allmp = dimreduce.pca_metapixels(metapixels.values(), nmetamarkers)
|
|
153
153
|
loadings.to_feather(f'{processeddir}/_pcloadings.feather')
|
|
154
154
|
del metapixels, allmp; gc.collect()
|
|
155
155
|
|
|
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
|
|
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
|