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.
Files changed (67) hide show
  1. chatspatial/__init__.py +11 -0
  2. chatspatial/__main__.py +141 -0
  3. chatspatial/cli/__init__.py +7 -0
  4. chatspatial/config.py +53 -0
  5. chatspatial/models/__init__.py +85 -0
  6. chatspatial/models/analysis.py +513 -0
  7. chatspatial/models/data.py +2462 -0
  8. chatspatial/server.py +1763 -0
  9. chatspatial/spatial_mcp_adapter.py +720 -0
  10. chatspatial/tools/__init__.py +3 -0
  11. chatspatial/tools/annotation.py +1903 -0
  12. chatspatial/tools/cell_communication.py +1603 -0
  13. chatspatial/tools/cnv_analysis.py +605 -0
  14. chatspatial/tools/condition_comparison.py +595 -0
  15. chatspatial/tools/deconvolution/__init__.py +402 -0
  16. chatspatial/tools/deconvolution/base.py +318 -0
  17. chatspatial/tools/deconvolution/card.py +244 -0
  18. chatspatial/tools/deconvolution/cell2location.py +326 -0
  19. chatspatial/tools/deconvolution/destvi.py +144 -0
  20. chatspatial/tools/deconvolution/flashdeconv.py +101 -0
  21. chatspatial/tools/deconvolution/rctd.py +317 -0
  22. chatspatial/tools/deconvolution/spotlight.py +216 -0
  23. chatspatial/tools/deconvolution/stereoscope.py +109 -0
  24. chatspatial/tools/deconvolution/tangram.py +135 -0
  25. chatspatial/tools/differential.py +625 -0
  26. chatspatial/tools/embeddings.py +298 -0
  27. chatspatial/tools/enrichment.py +1863 -0
  28. chatspatial/tools/integration.py +807 -0
  29. chatspatial/tools/preprocessing.py +723 -0
  30. chatspatial/tools/spatial_domains.py +808 -0
  31. chatspatial/tools/spatial_genes.py +836 -0
  32. chatspatial/tools/spatial_registration.py +441 -0
  33. chatspatial/tools/spatial_statistics.py +1476 -0
  34. chatspatial/tools/trajectory.py +495 -0
  35. chatspatial/tools/velocity.py +405 -0
  36. chatspatial/tools/visualization/__init__.py +155 -0
  37. chatspatial/tools/visualization/basic.py +393 -0
  38. chatspatial/tools/visualization/cell_comm.py +699 -0
  39. chatspatial/tools/visualization/cnv.py +320 -0
  40. chatspatial/tools/visualization/core.py +684 -0
  41. chatspatial/tools/visualization/deconvolution.py +852 -0
  42. chatspatial/tools/visualization/enrichment.py +660 -0
  43. chatspatial/tools/visualization/integration.py +205 -0
  44. chatspatial/tools/visualization/main.py +164 -0
  45. chatspatial/tools/visualization/multi_gene.py +739 -0
  46. chatspatial/tools/visualization/persistence.py +335 -0
  47. chatspatial/tools/visualization/spatial_stats.py +469 -0
  48. chatspatial/tools/visualization/trajectory.py +639 -0
  49. chatspatial/tools/visualization/velocity.py +411 -0
  50. chatspatial/utils/__init__.py +115 -0
  51. chatspatial/utils/adata_utils.py +1372 -0
  52. chatspatial/utils/compute.py +327 -0
  53. chatspatial/utils/data_loader.py +499 -0
  54. chatspatial/utils/dependency_manager.py +462 -0
  55. chatspatial/utils/device_utils.py +165 -0
  56. chatspatial/utils/exceptions.py +185 -0
  57. chatspatial/utils/image_utils.py +267 -0
  58. chatspatial/utils/mcp_utils.py +137 -0
  59. chatspatial/utils/path_utils.py +243 -0
  60. chatspatial/utils/persistence.py +78 -0
  61. chatspatial/utils/scipy_compat.py +143 -0
  62. chatspatial-1.1.0.dist-info/METADATA +242 -0
  63. chatspatial-1.1.0.dist-info/RECORD +67 -0
  64. chatspatial-1.1.0.dist-info/WHEEL +5 -0
  65. chatspatial-1.1.0.dist-info/entry_points.txt +2 -0
  66. chatspatial-1.1.0.dist-info/licenses/LICENSE +21 -0
  67. 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()