chatspatial 1.1.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.
- chatspatial/__init__.py +11 -0
- chatspatial/__main__.py +141 -0
- chatspatial/cli/__init__.py +7 -0
- chatspatial/config.py +53 -0
- chatspatial/models/__init__.py +85 -0
- chatspatial/models/analysis.py +513 -0
- chatspatial/models/data.py +2462 -0
- chatspatial/server.py +1763 -0
- chatspatial/spatial_mcp_adapter.py +720 -0
- chatspatial/tools/__init__.py +3 -0
- chatspatial/tools/annotation.py +1903 -0
- chatspatial/tools/cell_communication.py +1603 -0
- chatspatial/tools/cnv_analysis.py +605 -0
- chatspatial/tools/condition_comparison.py +595 -0
- chatspatial/tools/deconvolution/__init__.py +402 -0
- chatspatial/tools/deconvolution/base.py +318 -0
- chatspatial/tools/deconvolution/card.py +244 -0
- chatspatial/tools/deconvolution/cell2location.py +326 -0
- chatspatial/tools/deconvolution/destvi.py +144 -0
- chatspatial/tools/deconvolution/flashdeconv.py +101 -0
- chatspatial/tools/deconvolution/rctd.py +317 -0
- chatspatial/tools/deconvolution/spotlight.py +216 -0
- chatspatial/tools/deconvolution/stereoscope.py +109 -0
- chatspatial/tools/deconvolution/tangram.py +135 -0
- chatspatial/tools/differential.py +625 -0
- chatspatial/tools/embeddings.py +298 -0
- chatspatial/tools/enrichment.py +1863 -0
- chatspatial/tools/integration.py +807 -0
- chatspatial/tools/preprocessing.py +723 -0
- chatspatial/tools/spatial_domains.py +808 -0
- chatspatial/tools/spatial_genes.py +836 -0
- chatspatial/tools/spatial_registration.py +441 -0
- chatspatial/tools/spatial_statistics.py +1476 -0
- chatspatial/tools/trajectory.py +495 -0
- chatspatial/tools/velocity.py +405 -0
- chatspatial/tools/visualization/__init__.py +155 -0
- chatspatial/tools/visualization/basic.py +393 -0
- chatspatial/tools/visualization/cell_comm.py +699 -0
- chatspatial/tools/visualization/cnv.py +320 -0
- chatspatial/tools/visualization/core.py +684 -0
- chatspatial/tools/visualization/deconvolution.py +852 -0
- chatspatial/tools/visualization/enrichment.py +660 -0
- chatspatial/tools/visualization/integration.py +205 -0
- chatspatial/tools/visualization/main.py +164 -0
- chatspatial/tools/visualization/multi_gene.py +739 -0
- chatspatial/tools/visualization/persistence.py +335 -0
- chatspatial/tools/visualization/spatial_stats.py +469 -0
- chatspatial/tools/visualization/trajectory.py +639 -0
- chatspatial/tools/visualization/velocity.py +411 -0
- chatspatial/utils/__init__.py +115 -0
- chatspatial/utils/adata_utils.py +1372 -0
- chatspatial/utils/compute.py +327 -0
- chatspatial/utils/data_loader.py +499 -0
- chatspatial/utils/dependency_manager.py +462 -0
- chatspatial/utils/device_utils.py +165 -0
- chatspatial/utils/exceptions.py +185 -0
- chatspatial/utils/image_utils.py +267 -0
- chatspatial/utils/mcp_utils.py +137 -0
- chatspatial/utils/path_utils.py +243 -0
- chatspatial/utils/persistence.py +78 -0
- chatspatial/utils/scipy_compat.py +143 -0
- chatspatial-1.1.0.dist-info/METADATA +242 -0
- chatspatial-1.1.0.dist-info/RECORD +67 -0
- chatspatial-1.1.0.dist-info/WHEEL +5 -0
- chatspatial-1.1.0.dist-info/entry_points.txt +2 -0
- chatspatial-1.1.0.dist-info/licenses/LICENSE +21 -0
- chatspatial-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compute utilities for ChatSpatial.
|
|
3
|
+
|
|
4
|
+
This module provides lazy computation functions that ensure required
|
|
5
|
+
computations are available before analysis. These functions follow the
|
|
6
|
+
"ensure" pattern: check if computation exists, compute if missing.
|
|
7
|
+
|
|
8
|
+
Design Principles:
|
|
9
|
+
1. Single Responsibility: Each function ensures one computation
|
|
10
|
+
2. Idempotent: Safe to call multiple times
|
|
11
|
+
3. Transparent: Returns whether computation was performed
|
|
12
|
+
4. Composable: Functions can depend on each other
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
# In analysis tools, use these to ensure prerequisites
|
|
16
|
+
computed = ensure_pca(adata)
|
|
17
|
+
|
|
18
|
+
# Or use the async version with context
|
|
19
|
+
await ensure_pca_async(adata, ctx)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import TYPE_CHECKING, Literal, Optional
|
|
23
|
+
|
|
24
|
+
import scanpy as sc
|
|
25
|
+
|
|
26
|
+
from .adata_utils import ensure_categorical
|
|
27
|
+
from .exceptions import DataNotFoundError
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
import anndata as ad
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# =============================================================================
|
|
34
|
+
# Core Computation Functions
|
|
35
|
+
# =============================================================================
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def ensure_pca(
|
|
39
|
+
adata: "ad.AnnData",
|
|
40
|
+
n_comps: int = 30,
|
|
41
|
+
use_highly_variable: bool = True,
|
|
42
|
+
random_state: int = 0,
|
|
43
|
+
) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Ensure PCA is computed on the dataset.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
adata: AnnData object (modified in-place)
|
|
49
|
+
n_comps: Number of principal components
|
|
50
|
+
use_highly_variable: Use only HVG if available
|
|
51
|
+
random_state: Random seed for reproducibility
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
True if PCA was computed, False if already existed
|
|
55
|
+
"""
|
|
56
|
+
if "X_pca" in adata.obsm:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
# Adjust n_comps if necessary
|
|
60
|
+
max_comps = min(adata.n_obs, adata.n_vars) - 1
|
|
61
|
+
n_comps = min(n_comps, max_comps)
|
|
62
|
+
|
|
63
|
+
sc.tl.pca(
|
|
64
|
+
adata,
|
|
65
|
+
n_comps=n_comps,
|
|
66
|
+
use_highly_variable=use_highly_variable and "highly_variable" in adata.var,
|
|
67
|
+
random_state=random_state,
|
|
68
|
+
)
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def ensure_neighbors(
|
|
73
|
+
adata: "ad.AnnData",
|
|
74
|
+
n_neighbors: int = 15,
|
|
75
|
+
n_pcs: Optional[int] = None,
|
|
76
|
+
use_rep: str = "X_pca",
|
|
77
|
+
random_state: int = 0,
|
|
78
|
+
) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Ensure neighborhood graph is computed.
|
|
81
|
+
|
|
82
|
+
Automatically ensures PCA is available first.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
adata: AnnData object (modified in-place)
|
|
86
|
+
n_neighbors: Number of neighbors for k-NN graph
|
|
87
|
+
n_pcs: Number of PCs to use (None = auto)
|
|
88
|
+
use_rep: Representation to use (default: X_pca)
|
|
89
|
+
random_state: Random seed
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if neighbors was computed, False if already existed
|
|
93
|
+
"""
|
|
94
|
+
if "neighbors" in adata.uns and "connectivities" in adata.obsp:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
# Ensure PCA exists if using X_pca
|
|
98
|
+
if use_rep == "X_pca":
|
|
99
|
+
ensure_pca(adata)
|
|
100
|
+
|
|
101
|
+
sc.pp.neighbors(
|
|
102
|
+
adata,
|
|
103
|
+
n_neighbors=n_neighbors,
|
|
104
|
+
n_pcs=n_pcs,
|
|
105
|
+
use_rep=use_rep,
|
|
106
|
+
random_state=random_state,
|
|
107
|
+
)
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def ensure_umap(
|
|
112
|
+
adata: "ad.AnnData",
|
|
113
|
+
min_dist: float = 0.5,
|
|
114
|
+
spread: float = 1.0,
|
|
115
|
+
random_state: int = 0,
|
|
116
|
+
) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Ensure UMAP embedding is computed.
|
|
119
|
+
|
|
120
|
+
Automatically ensures neighbors are available first.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
adata: AnnData object (modified in-place)
|
|
124
|
+
min_dist: Minimum distance parameter for UMAP
|
|
125
|
+
spread: Spread parameter for UMAP
|
|
126
|
+
random_state: Random seed
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if UMAP was computed, False if already existed
|
|
130
|
+
"""
|
|
131
|
+
if "X_umap" in adata.obsm:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
ensure_neighbors(adata)
|
|
135
|
+
|
|
136
|
+
sc.tl.umap(
|
|
137
|
+
adata,
|
|
138
|
+
min_dist=min_dist,
|
|
139
|
+
spread=spread,
|
|
140
|
+
random_state=random_state,
|
|
141
|
+
)
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def ensure_leiden(
|
|
146
|
+
adata: "ad.AnnData",
|
|
147
|
+
resolution: float = 1.0,
|
|
148
|
+
key_added: str = "leiden",
|
|
149
|
+
random_state: int = 0,
|
|
150
|
+
) -> bool:
|
|
151
|
+
"""
|
|
152
|
+
Ensure Leiden clustering is computed.
|
|
153
|
+
|
|
154
|
+
Automatically ensures neighbors are available first.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
adata: AnnData object (modified in-place)
|
|
158
|
+
resolution: Clustering resolution (higher = more clusters)
|
|
159
|
+
key_added: Key for storing results in adata.obs
|
|
160
|
+
random_state: Random seed
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
True if clustering was computed, False if already existed
|
|
164
|
+
"""
|
|
165
|
+
if key_added in adata.obs:
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
ensure_neighbors(adata)
|
|
169
|
+
|
|
170
|
+
sc.tl.leiden(
|
|
171
|
+
adata,
|
|
172
|
+
resolution=resolution,
|
|
173
|
+
key_added=key_added,
|
|
174
|
+
random_state=random_state,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
ensure_categorical(adata, key_added)
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def ensure_louvain(
|
|
182
|
+
adata: "ad.AnnData",
|
|
183
|
+
resolution: float = 1.0,
|
|
184
|
+
key_added: str = "louvain",
|
|
185
|
+
random_state: int = 0,
|
|
186
|
+
) -> bool:
|
|
187
|
+
"""
|
|
188
|
+
Ensure Louvain clustering is computed.
|
|
189
|
+
|
|
190
|
+
Automatically ensures neighbors are available first.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
adata: AnnData object (modified in-place)
|
|
194
|
+
resolution: Clustering resolution
|
|
195
|
+
key_added: Key for storing results in adata.obs
|
|
196
|
+
random_state: Random seed
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
True if clustering was computed, False if already existed
|
|
200
|
+
"""
|
|
201
|
+
if key_added in adata.obs:
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
ensure_neighbors(adata)
|
|
205
|
+
|
|
206
|
+
sc.tl.louvain(
|
|
207
|
+
adata,
|
|
208
|
+
resolution=resolution,
|
|
209
|
+
key_added=key_added,
|
|
210
|
+
random_state=random_state,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
ensure_categorical(adata, key_added)
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def ensure_diffmap(
|
|
218
|
+
adata: "ad.AnnData",
|
|
219
|
+
n_comps: int = 15,
|
|
220
|
+
) -> bool:
|
|
221
|
+
"""
|
|
222
|
+
Ensure diffusion map is computed (for trajectory analysis).
|
|
223
|
+
|
|
224
|
+
Automatically ensures neighbors are available first.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
adata: AnnData object (modified in-place)
|
|
228
|
+
n_comps: Number of diffusion components
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
True if diffmap was computed, False if already existed
|
|
232
|
+
"""
|
|
233
|
+
if "X_diffmap" in adata.obsm:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
ensure_neighbors(adata)
|
|
237
|
+
|
|
238
|
+
sc.tl.diffmap(adata, n_comps=n_comps)
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def ensure_spatial_neighbors(
|
|
243
|
+
adata: "ad.AnnData",
|
|
244
|
+
coord_type: Literal["grid", "generic"] = "generic",
|
|
245
|
+
n_neighs: int = 6,
|
|
246
|
+
n_rings: int = 1,
|
|
247
|
+
spatial_key: str = "spatial",
|
|
248
|
+
) -> bool:
|
|
249
|
+
"""
|
|
250
|
+
Ensure spatial neighborhood graph is computed.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
adata: AnnData object (modified in-place)
|
|
254
|
+
coord_type: Type of coordinate system ('grid' for Visium, 'generic' for others)
|
|
255
|
+
n_neighs: Number of neighbors (for generic coord_type)
|
|
256
|
+
n_rings: Number of rings (for grid coord_type)
|
|
257
|
+
spatial_key: Key for spatial coordinates in obsm
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
True if spatial neighbors was computed, False if already existed
|
|
261
|
+
"""
|
|
262
|
+
if "spatial_connectivities" in adata.obsp:
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
if spatial_key not in adata.obsm:
|
|
266
|
+
raise DataNotFoundError(
|
|
267
|
+
f"Spatial coordinates not found in adata.obsm['{spatial_key}']"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
import squidpy as sq
|
|
271
|
+
|
|
272
|
+
if coord_type == "grid":
|
|
273
|
+
sq.gr.spatial_neighbors(adata, coord_type="grid", n_rings=n_rings)
|
|
274
|
+
else:
|
|
275
|
+
sq.gr.spatial_neighbors(adata, coord_type="generic", n_neighs=n_neighs)
|
|
276
|
+
|
|
277
|
+
return True
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# =============================================================================
|
|
281
|
+
# Async Wrapper for Spatial Neighbors (used by spatial_statistics.py)
|
|
282
|
+
# =============================================================================
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
async def ensure_spatial_neighbors_async(
|
|
286
|
+
adata: "ad.AnnData",
|
|
287
|
+
ctx, # ToolContext # noqa: ARG001
|
|
288
|
+
coord_type: Literal["grid", "generic"] = "generic",
|
|
289
|
+
n_neighs: int = 6,
|
|
290
|
+
) -> bool:
|
|
291
|
+
"""Async version of ensure_spatial_neighbors with context logging."""
|
|
292
|
+
return ensure_spatial_neighbors(adata, coord_type, n_neighs)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# =============================================================================
|
|
296
|
+
# Validation Functions (Check-only, no computation)
|
|
297
|
+
# =============================================================================
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def has_pca(adata: "ad.AnnData") -> bool:
|
|
301
|
+
"""Check if PCA is available."""
|
|
302
|
+
return "X_pca" in adata.obsm
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def has_neighbors(adata: "ad.AnnData") -> bool:
|
|
306
|
+
"""Check if neighborhood graph is available."""
|
|
307
|
+
return "neighbors" in adata.uns and "connectivities" in adata.obsp
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def has_umap(adata: "ad.AnnData") -> bool:
|
|
311
|
+
"""Check if UMAP embedding is available."""
|
|
312
|
+
return "X_umap" in adata.obsm
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def has_clustering(adata: "ad.AnnData", key: str = "leiden") -> bool:
|
|
316
|
+
"""Check if clustering results are available."""
|
|
317
|
+
return key in adata.obs
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def has_spatial_neighbors(adata: "ad.AnnData") -> bool:
|
|
321
|
+
"""Check if spatial neighborhood graph is available."""
|
|
322
|
+
return "spatial_connectivities" in adata.obsp
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def has_hvg(adata: "ad.AnnData") -> bool:
|
|
326
|
+
"""Check if highly variable genes are marked."""
|
|
327
|
+
return "highly_variable" in adata.var and adata.var["highly_variable"].any()
|