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,243 @@
|
|
|
1
|
+
"""Path handling utilities for ChatSpatial MCP server.
|
|
2
|
+
|
|
3
|
+
This module provides robust path handling that works correctly regardless of
|
|
4
|
+
the current working directory when the MCP server is launched.
|
|
5
|
+
|
|
6
|
+
Key features:
|
|
7
|
+
- Resolves relative paths against project root (not cwd)
|
|
8
|
+
- Automatic fallback to /tmp for permission issues
|
|
9
|
+
- Security checks to prevent writing outside safe directories
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import warnings
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# Project root directory (based on utils module location)
|
|
17
|
+
# This is always correct regardless of cwd
|
|
18
|
+
_PROJECT_ROOT = Path(__file__).parent.parent.resolve()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_project_root() -> Path:
|
|
22
|
+
"""Get ChatSpatial project root directory.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Absolute path to project root, regardless of current working directory.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> root = get_project_root()
|
|
29
|
+
>>> print(root)
|
|
30
|
+
/path/to/chatspatial/chatspatial
|
|
31
|
+
"""
|
|
32
|
+
return _PROJECT_ROOT
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_safe_output_path(
|
|
36
|
+
output_dir: str,
|
|
37
|
+
fallback_to_tmp: bool = True,
|
|
38
|
+
create_if_missing: bool = True,
|
|
39
|
+
) -> Path:
|
|
40
|
+
"""Get safe, writable output directory path.
|
|
41
|
+
|
|
42
|
+
This function handles path resolution robustly:
|
|
43
|
+
- Relative paths are resolved against CURRENT WORKING DIRECTORY (respects user config)
|
|
44
|
+
- Tests write permission before returning
|
|
45
|
+
- Falls back to /tmp/chatspatial/outputs if original path not writable
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
output_dir: User-provided output directory (relative or absolute)
|
|
49
|
+
fallback_to_tmp: If True, fallback to /tmp if output_dir not writable
|
|
50
|
+
create_if_missing: If True, create directory if it doesn't exist
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Absolute path to writable output directory
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
PermissionError: If no writable path can be found (when fallback disabled)
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
>>> # Relative path (resolved against cwd)
|
|
60
|
+
>>> path = get_safe_output_path("./outputs")
|
|
61
|
+
>>> # Returns: <cwd>/outputs
|
|
62
|
+
|
|
63
|
+
>>> # Absolute path
|
|
64
|
+
>>> path = get_safe_output_path("/tmp/my_outputs")
|
|
65
|
+
>>> # Returns: /tmp/my_outputs
|
|
66
|
+
|
|
67
|
+
>>> # Read-only path with fallback
|
|
68
|
+
>>> path = get_safe_output_path("/outputs")
|
|
69
|
+
>>> # Returns: /tmp/chatspatial/outputs (with warning)
|
|
70
|
+
"""
|
|
71
|
+
# Convert to Path object
|
|
72
|
+
user_path = Path(output_dir)
|
|
73
|
+
|
|
74
|
+
# If absolute path, use directly; otherwise resolve against CWD
|
|
75
|
+
if user_path.is_absolute():
|
|
76
|
+
target_path = user_path
|
|
77
|
+
else:
|
|
78
|
+
# For relative paths, resolve against CWD (respects user configuration!)
|
|
79
|
+
# This follows standard Unix/Python conventions
|
|
80
|
+
target_path = Path.cwd() / user_path
|
|
81
|
+
|
|
82
|
+
# Try to create/verify the directory
|
|
83
|
+
try:
|
|
84
|
+
if create_if_missing:
|
|
85
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
|
|
87
|
+
# Test write permission by creating a temporary test file
|
|
88
|
+
test_file = target_path / ".write_test"
|
|
89
|
+
test_file.touch()
|
|
90
|
+
test_file.unlink()
|
|
91
|
+
|
|
92
|
+
return target_path
|
|
93
|
+
|
|
94
|
+
except (OSError, PermissionError) as e:
|
|
95
|
+
# If fallback enabled, try temp directory
|
|
96
|
+
if fallback_to_tmp:
|
|
97
|
+
warnings.warn(
|
|
98
|
+
f"Cannot write to {target_path}: {e}. "
|
|
99
|
+
f"Falling back to /tmp/chatspatial/outputs",
|
|
100
|
+
UserWarning,
|
|
101
|
+
stacklevel=2,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
fallback_path = Path("/tmp/chatspatial/outputs")
|
|
105
|
+
fallback_path.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
return fallback_path
|
|
107
|
+
else:
|
|
108
|
+
raise PermissionError(
|
|
109
|
+
f"Cannot write to output directory: {target_path}. " f"Error: {e}"
|
|
110
|
+
) from e
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def is_safe_output_path(path: Path) -> bool:
|
|
114
|
+
"""Check if output path is safe (within project or /tmp).
|
|
115
|
+
|
|
116
|
+
This provides security by ensuring files are only written to:
|
|
117
|
+
1. Project directory or its subdirectories
|
|
118
|
+
2. /tmp/chatspatial directory
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
path: Path to check
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
True if path is safe for output, False otherwise
|
|
125
|
+
|
|
126
|
+
Examples:
|
|
127
|
+
>>> # Safe paths
|
|
128
|
+
>>> is_safe_output_path(Path("/Users/.../chatspatial/outputs"))
|
|
129
|
+
True
|
|
130
|
+
>>> is_safe_output_path(Path("/tmp/chatspatial/outputs"))
|
|
131
|
+
True
|
|
132
|
+
|
|
133
|
+
>>> # Unsafe paths
|
|
134
|
+
>>> is_safe_output_path(Path("/etc/outputs"))
|
|
135
|
+
False
|
|
136
|
+
>>> is_safe_output_path(Path("/Users/other_user/outputs"))
|
|
137
|
+
False
|
|
138
|
+
"""
|
|
139
|
+
# Normalize path to absolute
|
|
140
|
+
abs_path = path.resolve() if not path.is_absolute() else path
|
|
141
|
+
|
|
142
|
+
project_root = get_project_root()
|
|
143
|
+
tmp_root = Path("/tmp/chatspatial")
|
|
144
|
+
|
|
145
|
+
# Allow paths within project directory
|
|
146
|
+
try:
|
|
147
|
+
abs_path.relative_to(project_root)
|
|
148
|
+
return True
|
|
149
|
+
except ValueError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
# Allow paths within /tmp/chatspatial
|
|
153
|
+
try:
|
|
154
|
+
abs_path.relative_to(tmp_root)
|
|
155
|
+
return True
|
|
156
|
+
except ValueError:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
# Disallow all other paths
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_output_dir_from_config(default: str = "./outputs") -> str:
|
|
164
|
+
"""Get output directory from environment variable or configuration.
|
|
165
|
+
|
|
166
|
+
Priority order:
|
|
167
|
+
1. CHATSPATIAL_OUTPUT_DIR environment variable (highest priority)
|
|
168
|
+
2. Default value (usually "./outputs")
|
|
169
|
+
|
|
170
|
+
This allows users to configure the output directory via:
|
|
171
|
+
- Claude Desktop config: env.CHATSPATIAL_OUTPUT_DIR
|
|
172
|
+
- Shell environment: export CHATSPATIAL_OUTPUT_DIR=/path/to/outputs
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
default: Default output directory if no configuration found
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Output directory path (relative or absolute)
|
|
179
|
+
|
|
180
|
+
Examples:
|
|
181
|
+
>>> # With environment variable set
|
|
182
|
+
>>> os.environ["CHATSPATIAL_OUTPUT_DIR"] = "/tmp/results"
|
|
183
|
+
>>> get_output_dir_from_config()
|
|
184
|
+
'/tmp/results'
|
|
185
|
+
|
|
186
|
+
>>> # Without environment variable
|
|
187
|
+
>>> get_output_dir_from_config()
|
|
188
|
+
'./outputs'
|
|
189
|
+
|
|
190
|
+
Configuration example for Claude Desktop (claude_desktop_config.json):
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"mcpServers": {
|
|
194
|
+
"chatspatial": {
|
|
195
|
+
"command": "python",
|
|
196
|
+
"args": ["-m", "chatspatial"],
|
|
197
|
+
"cwd": "/Users/username/my_project",
|
|
198
|
+
"env": {
|
|
199
|
+
"CHATSPATIAL_OUTPUT_DIR": "./results"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
"""
|
|
206
|
+
# Check environment variable (highest priority)
|
|
207
|
+
if env_dir := os.getenv("CHATSPATIAL_OUTPUT_DIR"):
|
|
208
|
+
return env_dir
|
|
209
|
+
|
|
210
|
+
# Return default
|
|
211
|
+
return default
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def resolve_output_path(
|
|
215
|
+
output_dir: str,
|
|
216
|
+
default_dir: str = "./outputs",
|
|
217
|
+
) -> Path:
|
|
218
|
+
"""Resolve output directory path safely.
|
|
219
|
+
|
|
220
|
+
Resolves relative paths against current working directory,
|
|
221
|
+
following standard Unix/Python conventions.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
output_dir: User-provided output directory
|
|
225
|
+
default_dir: Default directory if output_dir is None
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Resolved absolute path
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
>>> resolve_output_path("./outputs")
|
|
232
|
+
PosixPath('<cwd>/outputs')
|
|
233
|
+
|
|
234
|
+
>>> resolve_output_path("/tmp/test")
|
|
235
|
+
PosixPath('/tmp/test')
|
|
236
|
+
"""
|
|
237
|
+
path = Path(output_dir or default_dir)
|
|
238
|
+
|
|
239
|
+
if path.is_absolute():
|
|
240
|
+
return path
|
|
241
|
+
else:
|
|
242
|
+
# Resolve against CWD (respects user configuration)
|
|
243
|
+
return Path.cwd() / path
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data persistence utilities for spatial transcriptomics data.
|
|
3
|
+
|
|
4
|
+
Handles saving AnnData objects to disk with proper path management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from anndata import AnnData
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_save_path(data_id: str, original_path: str) -> Path:
|
|
16
|
+
"""
|
|
17
|
+
Get save path for adata, supports environment variable configuration.
|
|
18
|
+
|
|
19
|
+
Priority:
|
|
20
|
+
1. CHATSPATIAL_DATA_DIR environment variable
|
|
21
|
+
2. .chatspatial_saved/ directory next to original data (default)
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
data_id: Dataset identifier
|
|
25
|
+
original_path: Original data file path
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Directory path for saving
|
|
29
|
+
"""
|
|
30
|
+
env_dir = os.getenv("CHATSPATIAL_DATA_DIR")
|
|
31
|
+
if env_dir:
|
|
32
|
+
save_dir = Path(env_dir)
|
|
33
|
+
save_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
return save_dir
|
|
35
|
+
|
|
36
|
+
# Default: use directory next to original data
|
|
37
|
+
path_obj = Path(original_path)
|
|
38
|
+
|
|
39
|
+
# Determine parent directory based on whether path looks like a file
|
|
40
|
+
# Check if path has a file extension or ends with a known data format
|
|
41
|
+
if path_obj.suffix in [".h5ad", ".h5", ".csv", ".txt", ".mtx", ".gz"]:
|
|
42
|
+
# It's a file path, use parent directory
|
|
43
|
+
parent_dir = path_obj.parent
|
|
44
|
+
elif path_obj.is_dir():
|
|
45
|
+
# It's an existing directory
|
|
46
|
+
parent_dir = path_obj
|
|
47
|
+
else:
|
|
48
|
+
# Assume it's a file path (even if doesn't exist yet)
|
|
49
|
+
parent_dir = path_obj.parent
|
|
50
|
+
|
|
51
|
+
save_dir = parent_dir / ".chatspatial_saved"
|
|
52
|
+
save_dir.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
return save_dir
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def save_adata(data_id: str, adata: "AnnData", original_path: str) -> Path:
|
|
57
|
+
"""
|
|
58
|
+
Save AnnData object to disk.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
data_id: Dataset identifier
|
|
62
|
+
adata: AnnData object to save
|
|
63
|
+
original_path: Original data file path
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Path where data was saved
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
IOError: If save fails
|
|
70
|
+
"""
|
|
71
|
+
save_dir = get_save_path(data_id, original_path)
|
|
72
|
+
save_path = save_dir / f"{data_id}.h5ad"
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
adata.write_h5ad(save_path, compression="gzip", compression_opts=4)
|
|
76
|
+
return save_path
|
|
77
|
+
except Exception as e:
|
|
78
|
+
raise IOError(f"Failed to save data to {save_path}: {e}") from e
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scipy compatibility layer for deprecated functions.
|
|
3
|
+
|
|
4
|
+
This module provides compatibility shims for scipy functions that have been
|
|
5
|
+
removed or relocated in newer versions, enabling packages like SpatialDE
|
|
6
|
+
that depend on these deprecated APIs to work with modern scipy (>=1.14).
|
|
7
|
+
|
|
8
|
+
Background:
|
|
9
|
+
- scipy.misc.derivative was deprecated in scipy 1.10.0
|
|
10
|
+
- scipy.misc.derivative was removed in scipy 1.14.0
|
|
11
|
+
- SpatialDE 1.1.3 still imports from scipy.misc.derivative
|
|
12
|
+
- This module patches scipy.misc to restore the derivative function
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
Call `patch_scipy_misc_derivative()` before importing SpatialDE:
|
|
16
|
+
|
|
17
|
+
from chatspatial.utils.scipy_compat import patch_scipy_misc_derivative
|
|
18
|
+
patch_scipy_misc_derivative()
|
|
19
|
+
import SpatialDE # Now works with scipy >= 1.14
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
from scipy import misc as scipy_misc
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _derivative_compat(func, x0, dx=1.0, n=1, args=(), order=3):
|
|
27
|
+
"""Compute the nth derivative of a function at a point.
|
|
28
|
+
|
|
29
|
+
This is a compatibility implementation of the deprecated scipy.misc.derivative,
|
|
30
|
+
using central difference formulas.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
func : callable
|
|
35
|
+
Function of which to compute the derivative. Should accept a single
|
|
36
|
+
float argument and return a float.
|
|
37
|
+
x0 : float
|
|
38
|
+
Point at which to evaluate the derivative.
|
|
39
|
+
dx : float, optional
|
|
40
|
+
Spacing for finite difference. Default is 1.0.
|
|
41
|
+
n : int, optional
|
|
42
|
+
Order of the derivative. Default is 1.
|
|
43
|
+
args : tuple, optional
|
|
44
|
+
Extra arguments to pass to func.
|
|
45
|
+
order : int, optional
|
|
46
|
+
Number of points to use for the central difference formula.
|
|
47
|
+
Must be odd. Default is 3.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
float
|
|
52
|
+
The nth derivative of func at x0.
|
|
53
|
+
|
|
54
|
+
Notes
|
|
55
|
+
-----
|
|
56
|
+
This implementation uses central difference formulas. The accuracy
|
|
57
|
+
depends on the `order` parameter and the smoothness of the function.
|
|
58
|
+
|
|
59
|
+
For SpatialDE, only n=1 and n=2 are used with default dx=1.0 and order=3.
|
|
60
|
+
"""
|
|
61
|
+
if order < n + 1:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"'order' ({order}) must be at least the derivative order 'n' ({n}) plus 1"
|
|
64
|
+
)
|
|
65
|
+
if order % 2 == 0:
|
|
66
|
+
raise ValueError(f"'order' ({order}) must be odd")
|
|
67
|
+
|
|
68
|
+
# Central difference weights for various orders and derivative degrees
|
|
69
|
+
# These are the standard finite difference coefficients
|
|
70
|
+
# Reference: https://en.wikipedia.org/wiki/Finite_difference_coefficient
|
|
71
|
+
|
|
72
|
+
# For simplicity, we implement the most common cases used by SpatialDE
|
|
73
|
+
# First derivative using central difference (3-point formula)
|
|
74
|
+
if n == 1 and order >= 3:
|
|
75
|
+
# f'(x) ≈ (f(x+h) - f(x-h)) / (2h)
|
|
76
|
+
return (func(x0 + dx, *args) - func(x0 - dx, *args)) / (2 * dx)
|
|
77
|
+
# Second derivative using central difference (3-point formula)
|
|
78
|
+
elif n == 2 and order >= 3:
|
|
79
|
+
# f''(x) ≈ (f(x+h) - 2f(x) + f(x-h)) / h²
|
|
80
|
+
return (
|
|
81
|
+
func(x0 + dx, *args) - 2 * func(x0, *args) + func(x0 - dx, *args)
|
|
82
|
+
) / (dx**2)
|
|
83
|
+
|
|
84
|
+
# For higher derivatives or orders, use Richardson extrapolation
|
|
85
|
+
# This is a more general but slower approach
|
|
86
|
+
return _derivative_richardson(func, x0, dx, n, args, order)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _derivative_richardson(func, x0, dx, n, args, order):
|
|
90
|
+
"""Compute derivative using Richardson extrapolation.
|
|
91
|
+
|
|
92
|
+
This is a more general implementation that handles arbitrary
|
|
93
|
+
derivative orders using Richardson extrapolation for improved accuracy.
|
|
94
|
+
"""
|
|
95
|
+
# Number of points for central difference
|
|
96
|
+
num_points = order
|
|
97
|
+
|
|
98
|
+
# Generate sample points symmetrically around x0
|
|
99
|
+
half = num_points // 2
|
|
100
|
+
points = np.arange(-half, half + 1) * dx + x0
|
|
101
|
+
|
|
102
|
+
# Evaluate function at all points
|
|
103
|
+
f_values = np.array([func(p, *args) for p in points])
|
|
104
|
+
|
|
105
|
+
# Compute derivative using finite differences
|
|
106
|
+
# For nth derivative, we need to apply the difference operator n times
|
|
107
|
+
result = f_values.copy()
|
|
108
|
+
for _ in range(n):
|
|
109
|
+
result = np.diff(result) / dx
|
|
110
|
+
|
|
111
|
+
# Return the central value
|
|
112
|
+
return result[len(result) // 2]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def patch_scipy_misc_derivative():
|
|
116
|
+
"""Patch scipy.misc to include the deprecated derivative function.
|
|
117
|
+
|
|
118
|
+
This function adds a `derivative` attribute to `scipy.misc` module,
|
|
119
|
+
allowing packages like SpatialDE that import `from scipy.misc import derivative`
|
|
120
|
+
to work with scipy >= 1.14.
|
|
121
|
+
|
|
122
|
+
This patch is idempotent - calling it multiple times has no additional effect.
|
|
123
|
+
|
|
124
|
+
Example
|
|
125
|
+
-------
|
|
126
|
+
>>> from chatspatial.utils.scipy_compat import patch_scipy_misc_derivative
|
|
127
|
+
>>> patch_scipy_misc_derivative()
|
|
128
|
+
>>> import SpatialDE # Now works!
|
|
129
|
+
"""
|
|
130
|
+
if not hasattr(scipy_misc, 'derivative'):
|
|
131
|
+
scipy_misc.derivative = _derivative_compat
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Auto-check: provide helpful message if scipy already has derivative
|
|
135
|
+
def check_scipy_derivative_status():
|
|
136
|
+
"""Check the status of scipy.misc.derivative and return diagnostic info."""
|
|
137
|
+
import scipy
|
|
138
|
+
has_derivative = hasattr(scipy_misc, 'derivative')
|
|
139
|
+
return {
|
|
140
|
+
'scipy_version': scipy.__version__,
|
|
141
|
+
'has_derivative': has_derivative,
|
|
142
|
+
'needs_patch': not has_derivative,
|
|
143
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chatspatial
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: ChatSpatial: Natural language-driven spatial transcriptomics analysis via Model Context Protocol (MCP) integration
|
|
5
|
+
Author-email: Chen Yang <cafferychen777@tamu.edu>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cafferychen777/ChatSpatial
|
|
8
|
+
Project-URL: Documentation, https://chatspatial.readthedocs.io
|
|
9
|
+
Project-URL: Repository, https://github.com/cafferychen777/ChatSpatial
|
|
10
|
+
Project-URL: Issues, https://github.com/cafferychen777/ChatSpatial/issues
|
|
11
|
+
Keywords: spatial-transcriptomics,single-cell,mcp,llm,bioinformatics,scanpy,squidpy
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
21
|
+
Requires-Python: <3.14,>=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: mcp>=1.17.0
|
|
25
|
+
Requires-Dist: numpy>=1.21.0
|
|
26
|
+
Requires-Dist: pandas>=1.3.0
|
|
27
|
+
Requires-Dist: scipy>=1.7.0
|
|
28
|
+
Requires-Dist: scikit-learn>=1.0.0
|
|
29
|
+
Requires-Dist: matplotlib>=3.5.0
|
|
30
|
+
Requires-Dist: seaborn>=0.11.0
|
|
31
|
+
Requires-Dist: Pillow>=8.0.0
|
|
32
|
+
Requires-Dist: scanpy<2.0,>=1.9.0
|
|
33
|
+
Requires-Dist: anndata>=0.11.0
|
|
34
|
+
Requires-Dist: squidpy>=1.2.0
|
|
35
|
+
Requires-Dist: umap-learn>=0.5.0
|
|
36
|
+
Requires-Dist: igraph>=0.9.0
|
|
37
|
+
Requires-Dist: leidenalg>=0.8.0
|
|
38
|
+
Requires-Dist: pydantic<3.0,>=2.0.0
|
|
39
|
+
Requires-Dist: click>=8.0.0
|
|
40
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
41
|
+
Requires-Dist: jinja2>=3.0.0
|
|
42
|
+
Requires-Dist: scvi-tools<2.0,>=1.0.0
|
|
43
|
+
Requires-Dist: mudata>=0.2.0
|
|
44
|
+
Requires-Dist: scvelo>=0.2.5
|
|
45
|
+
Requires-Dist: liana
|
|
46
|
+
Requires-Dist: cellphonedb<6.0,>=5.0.0
|
|
47
|
+
Requires-Dist: ktplotspy>=0.3.0
|
|
48
|
+
Requires-Dist: fastccc>=0.1.0
|
|
49
|
+
Requires-Dist: harmonypy>=0.0.9
|
|
50
|
+
Requires-Dist: bbknn>=1.5.0
|
|
51
|
+
Requires-Dist: esda>=2.4.0
|
|
52
|
+
Requires-Dist: libpysal>=4.6.0
|
|
53
|
+
Requires-Dist: gseapy>=1.0.0
|
|
54
|
+
Requires-Dist: flashdeconv>=0.1.0
|
|
55
|
+
Provides-Extra: full
|
|
56
|
+
Requires-Dist: torch<3.0,>=2.0.0; extra == "full"
|
|
57
|
+
Requires-Dist: SpaGCN<2.0,>=1.2.5; extra == "full"
|
|
58
|
+
Requires-Dist: GraphST>=1.1.0; extra == "full"
|
|
59
|
+
Requires-Dist: cell2location>=0.1.4; extra == "full"
|
|
60
|
+
Requires-Dist: cellrank<3.0,>=2.0.0; extra == "full"
|
|
61
|
+
Requires-Dist: palantir>=1.0.0; extra == "full"
|
|
62
|
+
Requires-Dist: petsc4py<4.0,>=3.18.0; sys_platform != "win32" and extra == "full"
|
|
63
|
+
Requires-Dist: slepc4py<4.0,>=3.18.0; sys_platform != "win32" and extra == "full"
|
|
64
|
+
Requires-Dist: scanorama>=1.7.0; extra == "full"
|
|
65
|
+
Requires-Dist: spatialde>=1.1.3; extra == "full"
|
|
66
|
+
Requires-Dist: scikit-misc>=0.5.0; extra == "full"
|
|
67
|
+
Requires-Dist: tangram-sc>=1.0.0; extra == "full"
|
|
68
|
+
Requires-Dist: singler>=0.4.0; sys_platform != "win32" and extra == "full"
|
|
69
|
+
Requires-Dist: singlecellexperiment>=0.4.0; sys_platform != "win32" and extra == "full"
|
|
70
|
+
Requires-Dist: celldex>=0.3.0; sys_platform != "win32" and extra == "full"
|
|
71
|
+
Requires-Dist: mllmcelltype>=0.1.0; extra == "full"
|
|
72
|
+
Requires-Dist: rpy2<3.7,>=3.5.0; extra == "full"
|
|
73
|
+
Requires-Dist: anndata2ri>=1.3.0; extra == "full"
|
|
74
|
+
Requires-Dist: pysal>=2.6.0; extra == "full"
|
|
75
|
+
Requires-Dist: statsmodels>=0.13.0; extra == "full"
|
|
76
|
+
Requires-Dist: networkx>=2.6.0; extra == "full"
|
|
77
|
+
Requires-Dist: POT>=0.9.0; extra == "full"
|
|
78
|
+
Requires-Dist: paste-bio>=1.0.0; extra == "full"
|
|
79
|
+
Requires-Dist: infercnvpy>=0.4.0; extra == "full"
|
|
80
|
+
Requires-Dist: pydeseq2>=0.4.0; extra == "full"
|
|
81
|
+
Requires-Dist: enrichmap; extra == "full"
|
|
82
|
+
Requires-Dist: statannotations>=0.2.0; extra == "full"
|
|
83
|
+
Requires-Dist: pygam>=0.8.0; extra == "full"
|
|
84
|
+
Requires-Dist: scikit-gstat>=1.0.0; extra == "full"
|
|
85
|
+
Requires-Dist: adjustText>=0.8.0; extra == "full"
|
|
86
|
+
Requires-Dist: splot>=1.1.0; extra == "full"
|
|
87
|
+
Requires-Dist: spatialdata>=0.2.0; extra == "full"
|
|
88
|
+
Provides-Extra: dev
|
|
89
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
90
|
+
Requires-Dist: pytest-asyncio<2.0,>=0.23.0; extra == "dev"
|
|
91
|
+
Requires-Dist: black<26.0,>=22.0.0; extra == "dev"
|
|
92
|
+
Requires-Dist: mypy<2.0,>=1.0.0; extra == "dev"
|
|
93
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
94
|
+
Requires-Dist: isort>=5.0.0; extra == "dev"
|
|
95
|
+
Requires-Dist: pre-commit<5.0,>=3.0.0; extra == "dev"
|
|
96
|
+
Dynamic: license-file
|
|
97
|
+
|
|
98
|
+
<div align="center">
|
|
99
|
+
|
|
100
|
+
# ChatSpatial
|
|
101
|
+
|
|
102
|
+
**MCP server for spatial transcriptomics analysis via natural language**
|
|
103
|
+
|
|
104
|
+
[](https://pypi.org/project/chatspatial/)
|
|
105
|
+
[](https://www.python.org/downloads/)
|
|
106
|
+
[](https://opensource.org/licenses/MIT)
|
|
107
|
+
[](https://cafferychen777.github.io/ChatSpatial/)
|
|
108
|
+
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
<table>
|
|
114
|
+
<tr>
|
|
115
|
+
<td width="50%">
|
|
116
|
+
|
|
117
|
+
### ❌ Before
|
|
118
|
+
```python
|
|
119
|
+
import scanpy as sc
|
|
120
|
+
import squidpy as sq
|
|
121
|
+
adata = sc.read_h5ad("data.h5ad")
|
|
122
|
+
sc.pp.filter_cells(adata, min_genes=200)
|
|
123
|
+
sc.pp.normalize_total(adata)
|
|
124
|
+
sc.pp.log1p(adata)
|
|
125
|
+
sc.pp.highly_variable_genes(adata)
|
|
126
|
+
sc.tl.pca(adata)
|
|
127
|
+
sc.pp.neighbors(adata)
|
|
128
|
+
# ... 40 more lines
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
</td>
|
|
132
|
+
<td width="50%">
|
|
133
|
+
|
|
134
|
+
### ✅ After
|
|
135
|
+
```text
|
|
136
|
+
"Load my Visium data and identify
|
|
137
|
+
spatial domains"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
✓ Loaded 3,456 spots, 18,078 genes
|
|
142
|
+
✓ Identified 7 spatial domains
|
|
143
|
+
✓ Generated visualization
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
</td>
|
|
147
|
+
</tr>
|
|
148
|
+
</table>
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Install
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pip install chatspatial
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Configure
|
|
159
|
+
|
|
160
|
+
**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"mcpServers": {
|
|
165
|
+
"chatspatial": {
|
|
166
|
+
"command": "python",
|
|
167
|
+
"args": ["-m", "chatspatial", "server"]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Claude Code**:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
claude mcp add chatspatial python -- -m chatspatial server
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
> Restart Claude after configuration.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Use
|
|
184
|
+
|
|
185
|
+
Open Claude and chat:
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
Load /path/to/spatial_data.h5ad and show me the tissue structure
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
Identify spatial domains using SpaGCN
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
Find spatially variable genes and create a heatmap
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Capabilities
|
|
202
|
+
|
|
203
|
+
| Category | Methods |
|
|
204
|
+
|----------|---------|
|
|
205
|
+
| **Spatial Domains** | SpaGCN, STAGATE, Leiden |
|
|
206
|
+
| **Deconvolution** | Cell2location, RCTD, Tangram, SPOTlight |
|
|
207
|
+
| **Cell Communication** | LIANA+, CellPhoneDB |
|
|
208
|
+
| **Cell Type Annotation** | Tangram, scANVI, CellAssign, mLLMCelltype |
|
|
209
|
+
| **Trajectory** | CellRank, Palantir, scVelo |
|
|
210
|
+
| **Spatial Statistics** | Moran's I, Getis-Ord Gi*, Ripley's K |
|
|
211
|
+
| **Gene Set Enrichment** | GSEA, ORA, Enrichr |
|
|
212
|
+
|
|
213
|
+
**60+ methods** across 12 categories. **Supports** 10x Visium, Xenium, Slide-seq v2, MERFISH, seqFISH.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Docs
|
|
218
|
+
|
|
219
|
+
- [Installation Guide](INSTALLATION.md) — detailed setup for all platforms
|
|
220
|
+
- [Examples](docs/examples/) — step-by-step workflows
|
|
221
|
+
- [Full Documentation](https://cafferychen777.github.io/ChatSpatial/) — complete reference
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Citation
|
|
226
|
+
|
|
227
|
+
```bibtex
|
|
228
|
+
@software{chatspatial2025,
|
|
229
|
+
title={ChatSpatial: Agentic Workflow for Spatial Transcriptomics},
|
|
230
|
+
author={Chen Yang and Xianyang Zhang and Jun Chen},
|
|
231
|
+
year={2025},
|
|
232
|
+
url={https://github.com/cafferychen777/ChatSpatial}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
<div align="center">
|
|
237
|
+
|
|
238
|
+
**MIT License** · [GitHub](https://github.com/cafferychen777/ChatSpatial) · [Issues](https://github.com/cafferychen777/ChatSpatial/issues)
|
|
239
|
+
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<!-- mcp-name: io.github.cafferychen777/chatspatial -->
|