scmcp-shared 0.1.0__py3-none-any.whl → 0.2.1__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.
- scmcp_shared/__init__.py +1 -1
- scmcp_shared/schema/__init__.py +1 -1
- scmcp_shared/schema/base.py +11 -0
- scmcp_shared/schema/io.py +5 -8
- scmcp_shared/schema/pl.py +3 -3
- scmcp_shared/schema/pp.py +14 -70
- scmcp_shared/schema/tl.py +69 -18
- scmcp_shared/schema/util.py +18 -11
- scmcp_shared/server/__init__.py +51 -1
- scmcp_shared/server/io.py +28 -29
- scmcp_shared/server/pl.py +324 -0
- scmcp_shared/server/pp.py +368 -0
- scmcp_shared/server/tl.py +432 -0
- scmcp_shared/server/util.py +251 -0
- scmcp_shared/util.py +79 -49
- {scmcp_shared-0.1.0.dist-info → scmcp_shared-0.2.1.dist-info}/METADATA +2 -2
- scmcp_shared-0.2.1.dist-info/RECORD +20 -0
- scmcp_shared-0.1.0.dist-info/RECORD +0 -15
- {scmcp_shared-0.1.0.dist-info → scmcp_shared-0.2.1.dist-info}/WHEEL +0 -0
- {scmcp_shared-0.1.0.dist-info → scmcp_shared-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,432 @@
|
|
1
|
+
from fastmcp import FastMCP, Context
|
2
|
+
import os
|
3
|
+
import scanpy as sc
|
4
|
+
from fastmcp.exceptions import ToolError
|
5
|
+
from ..schema.tl import *
|
6
|
+
from scmcp_shared.util import filter_args, add_op_log, forward_request, get_ads, generate_msg
|
7
|
+
from scmcp_shared.logging_config import setup_logger
|
8
|
+
logger = setup_logger()
|
9
|
+
|
10
|
+
tl_mcp = FastMCP("ScanpyMCP-TL-Server")
|
11
|
+
|
12
|
+
|
13
|
+
@tl_mcp.tool()
|
14
|
+
async def tsne(
|
15
|
+
request: TSNEModel = TSNEModel()
|
16
|
+
):
|
17
|
+
"""t-distributed stochastic neighborhood embedding (t-SNE) for visualization"""
|
18
|
+
|
19
|
+
try:
|
20
|
+
result = await forward_request("tl_tsne", request)
|
21
|
+
if result is not None:
|
22
|
+
return result
|
23
|
+
func_kwargs = filter_args(request, sc.tl.tsne)
|
24
|
+
ads = get_ads()
|
25
|
+
adata = ads.get_adata(request=request)
|
26
|
+
sc.tl.tsne(adata, **func_kwargs)
|
27
|
+
add_op_log(adata, sc.tl.tsne, func_kwargs)
|
28
|
+
return generate_msg(request, adata, ads)
|
29
|
+
except ToolError as e:
|
30
|
+
raise ToolError(e)
|
31
|
+
except Exception as e:
|
32
|
+
if hasattr(e, '__context__') and e.__context__:
|
33
|
+
raise ToolError(e.__context__)
|
34
|
+
else:
|
35
|
+
raise ToolError(e)
|
36
|
+
|
37
|
+
|
38
|
+
@tl_mcp.tool()
|
39
|
+
async def umap(
|
40
|
+
request: UMAPModel = UMAPModel()
|
41
|
+
):
|
42
|
+
"""Uniform Manifold Approximation and Projection (UMAP) for visualization"""
|
43
|
+
|
44
|
+
try:
|
45
|
+
result = await forward_request("tl_umap", request)
|
46
|
+
if result is not None:
|
47
|
+
return result
|
48
|
+
func_kwargs = filter_args(request, sc.tl.umap)
|
49
|
+
ads = get_ads()
|
50
|
+
adata = ads.get_adata(request=request)
|
51
|
+
sc.tl.umap(adata, **func_kwargs)
|
52
|
+
add_op_log(adata, sc.tl.umap, func_kwargs)
|
53
|
+
return [generate_msg(request, adata, ads)]
|
54
|
+
except ToolError as e:
|
55
|
+
raise ToolError(e)
|
56
|
+
except Exception as e:
|
57
|
+
if hasattr(e, '__context__') and e.__context__:
|
58
|
+
raise ToolError(e.__context__)
|
59
|
+
else:
|
60
|
+
raise ToolError(e)
|
61
|
+
|
62
|
+
@tl_mcp.tool()
|
63
|
+
async def draw_graph(
|
64
|
+
request: DrawGraphModel = DrawGraphModel()
|
65
|
+
):
|
66
|
+
"""Force-directed graph drawing"""
|
67
|
+
|
68
|
+
try:
|
69
|
+
result = await forward_request("tl_draw_graph", request)
|
70
|
+
if result is not None:
|
71
|
+
return result
|
72
|
+
func_kwargs = filter_args(request, sc.tl.draw_graph)
|
73
|
+
ads = get_ads()
|
74
|
+
adata = ads.get_adata(request=request)
|
75
|
+
sc.tl.draw_graph(adata, **func_kwargs)
|
76
|
+
add_op_log(adata, sc.tl.draw_graph, func_kwargs)
|
77
|
+
return [generate_msg(request, adata, ads)]
|
78
|
+
except ToolError as e:
|
79
|
+
raise ToolError(e)
|
80
|
+
except Exception as e:
|
81
|
+
if hasattr(e, '__context__') and e.__context__:
|
82
|
+
raise ToolError(e.__context__)
|
83
|
+
else:
|
84
|
+
raise ToolError(e)
|
85
|
+
|
86
|
+
@tl_mcp.tool()
|
87
|
+
async def diffmap(
|
88
|
+
request: DiffMapModel = DiffMapModel()
|
89
|
+
):
|
90
|
+
"""Diffusion Maps for dimensionality reduction"""
|
91
|
+
|
92
|
+
try:
|
93
|
+
result = await forward_request("tl_diffmap", request)
|
94
|
+
if result is not None:
|
95
|
+
return result
|
96
|
+
func_kwargs = filter_args(request, sc.tl.diffmap)
|
97
|
+
ads = get_ads()
|
98
|
+
adata = ads.get_adata(request=request)
|
99
|
+
sc.tl.diffmap(adata, **func_kwargs)
|
100
|
+
adata.obsm["X_diffmap"] = adata.obsm["X_diffmap"][:,1:]
|
101
|
+
add_op_log(adata, sc.tl.diffmap, func_kwargs)
|
102
|
+
return [generate_msg(request, adata, ads)]
|
103
|
+
except ToolError as e:
|
104
|
+
raise ToolError(e)
|
105
|
+
except Exception as e:
|
106
|
+
if hasattr(e, '__context__') and e.__context__:
|
107
|
+
raise ToolError(e.__context__)
|
108
|
+
else:
|
109
|
+
raise ToolError(e)
|
110
|
+
|
111
|
+
@tl_mcp.tool()
|
112
|
+
async def embedding_density(
|
113
|
+
request: EmbeddingDensityModel = EmbeddingDensityModel()
|
114
|
+
):
|
115
|
+
"""Calculate the density of cells in an embedding"""
|
116
|
+
|
117
|
+
try:
|
118
|
+
result = await forward_request("tl_embedding_density", request)
|
119
|
+
if result is not None:
|
120
|
+
return result
|
121
|
+
func_kwargs = filter_args(request, sc.tl.embedding_density)
|
122
|
+
ads = get_ads()
|
123
|
+
adata = ads.get_adata(request=request)
|
124
|
+
sc.tl.embedding_density(adata, **func_kwargs)
|
125
|
+
add_op_log(adata, sc.tl.embedding_density, func_kwargs)
|
126
|
+
return [generate_msg(request, adata, ads)]
|
127
|
+
except ToolError as e:
|
128
|
+
raise ToolError(e)
|
129
|
+
except Exception as e:
|
130
|
+
if hasattr(e, '__context__') and e.__context__:
|
131
|
+
raise ToolError(e.__context__)
|
132
|
+
else:
|
133
|
+
raise ToolError(e)
|
134
|
+
|
135
|
+
|
136
|
+
@tl_mcp.tool()
|
137
|
+
async def leiden(
|
138
|
+
request: LeidenModel = LeidenModel()
|
139
|
+
):
|
140
|
+
"""Leiden clustering algorithm for community detection"""
|
141
|
+
|
142
|
+
try:
|
143
|
+
result = await forward_request("tl_leiden", request)
|
144
|
+
if result is not None:
|
145
|
+
return result
|
146
|
+
func_kwargs = filter_args(request, sc.tl.leiden)
|
147
|
+
ads = get_ads()
|
148
|
+
adata = ads.get_adata(request=request)
|
149
|
+
sc.tl.leiden(adata, **func_kwargs)
|
150
|
+
add_op_log(adata, sc.tl.leiden, func_kwargs)
|
151
|
+
return [generate_msg(request, adata, ads)]
|
152
|
+
except ToolError as e:
|
153
|
+
raise ToolError(e)
|
154
|
+
except Exception as e:
|
155
|
+
if hasattr(e, '__context__') and e.__context__:
|
156
|
+
raise ToolError(e.__context__)
|
157
|
+
else:
|
158
|
+
raise ToolError(e)
|
159
|
+
|
160
|
+
|
161
|
+
@tl_mcp.tool()
|
162
|
+
async def louvain(
|
163
|
+
request: LouvainModel = LouvainModel()
|
164
|
+
):
|
165
|
+
"""Louvain clustering algorithm for community detection"""
|
166
|
+
|
167
|
+
try:
|
168
|
+
result = await forward_request("tl_louvain", request)
|
169
|
+
if result is not None:
|
170
|
+
return result
|
171
|
+
func_kwargs = filter_args(request, sc.tl.louvain)
|
172
|
+
ads = get_ads()
|
173
|
+
adata = ads.get_adata(request=request)
|
174
|
+
sc.tl.louvain(adata, **func_kwargs)
|
175
|
+
add_op_log(adata, sc.tl.louvain, func_kwargs)
|
176
|
+
return [generate_msg(request, adata, ads)]
|
177
|
+
except ToolError as e:
|
178
|
+
raise ToolError(e)
|
179
|
+
except Exception as e:
|
180
|
+
if hasattr(e, '__context__') and e.__context__:
|
181
|
+
raise ToolError(e.__context__)
|
182
|
+
else:
|
183
|
+
raise ToolError(e)
|
184
|
+
|
185
|
+
@tl_mcp.tool()
|
186
|
+
async def dendrogram(
|
187
|
+
request: DendrogramModel,
|
188
|
+
):
|
189
|
+
"""Hierarchical clustering dendrogram"""
|
190
|
+
|
191
|
+
try:
|
192
|
+
result = await forward_request("tl_dendrogram", request)
|
193
|
+
if result is not None:
|
194
|
+
return result
|
195
|
+
func_kwargs = filter_args(request, sc.tl.dendrogram)
|
196
|
+
ads = get_ads()
|
197
|
+
adata = ads.get_adata(request=request)
|
198
|
+
sc.tl.dendrogram(adata, **func_kwargs)
|
199
|
+
add_op_log(adata, sc.tl.dendrogram, func_kwargs)
|
200
|
+
return [generate_msg(request, adata, ads)]
|
201
|
+
except ToolError as e:
|
202
|
+
raise ToolError(e)
|
203
|
+
except Exception as e:
|
204
|
+
if hasattr(e, '__context__') and e.__context__:
|
205
|
+
raise ToolError(e.__context__)
|
206
|
+
else:
|
207
|
+
raise ToolError(e)
|
208
|
+
|
209
|
+
@tl_mcp.tool()
|
210
|
+
async def dpt(
|
211
|
+
request: DPTModel = DPTModel()
|
212
|
+
):
|
213
|
+
"""Diffusion Pseudotime (DPT) analysis"""
|
214
|
+
|
215
|
+
try:
|
216
|
+
result = await forward_request("tl_dpt", request)
|
217
|
+
if result is not None:
|
218
|
+
return result
|
219
|
+
func_kwargs = filter_args(request, sc.tl.dpt)
|
220
|
+
ads = get_ads()
|
221
|
+
adata = ads.get_adata(request=request)
|
222
|
+
sc.tl.dpt(adata, **func_kwargs)
|
223
|
+
add_op_log(adata, sc.tl.dpt, func_kwargs)
|
224
|
+
return [generate_msg(request, adata, ads)]
|
225
|
+
except ToolError as e:
|
226
|
+
raise ToolError(e)
|
227
|
+
except Exception as e:
|
228
|
+
if hasattr(e, '__context__') and e.__context__:
|
229
|
+
raise ToolError(e.__context__)
|
230
|
+
else:
|
231
|
+
raise ToolError(e)
|
232
|
+
|
233
|
+
|
234
|
+
@tl_mcp.tool()
|
235
|
+
async def paga(
|
236
|
+
request: PAGAModel = PAGAModel()
|
237
|
+
):
|
238
|
+
"""Partition-based graph abstraction"""
|
239
|
+
|
240
|
+
try:
|
241
|
+
result = await forward_request("tl_paga", request)
|
242
|
+
if result is not None:
|
243
|
+
return result
|
244
|
+
func_kwargs = filter_args(request, sc.tl.paga)
|
245
|
+
ads = get_ads()
|
246
|
+
adata = ads.get_adata(request=request)
|
247
|
+
sc.tl.paga(adata, **func_kwargs)
|
248
|
+
add_op_log(adata, sc.tl.paga, func_kwargs)
|
249
|
+
return [generate_msg(request, adata, ads)]
|
250
|
+
except ToolError as e:
|
251
|
+
raise ToolError(e)
|
252
|
+
except Exception as e:
|
253
|
+
if hasattr(e, '__context__') and e.__context__:
|
254
|
+
raise ToolError(e.__context__)
|
255
|
+
else:
|
256
|
+
raise ToolError(e)
|
257
|
+
|
258
|
+
|
259
|
+
@tl_mcp.tool()
|
260
|
+
async def ingest(
|
261
|
+
request: IngestModel = IngestModel()
|
262
|
+
):
|
263
|
+
"""Map labels and embeddings from reference data to new data"""
|
264
|
+
|
265
|
+
try:
|
266
|
+
result = await forward_request("tl_ingest", request)
|
267
|
+
if result is not None:
|
268
|
+
return result
|
269
|
+
func_kwargs = filter_args(request, sc.tl.ingest)
|
270
|
+
ads = get_ads()
|
271
|
+
adata = ads.get_adata(request=request)
|
272
|
+
sc.tl.ingest(adata, **func_kwargs)
|
273
|
+
add_op_log(adata, sc.tl.ingest, func_kwargs)
|
274
|
+
return [generate_msg(request, adata, ads)]
|
275
|
+
except ToolError as e:
|
276
|
+
raise ToolError(e)
|
277
|
+
except Exception as e:
|
278
|
+
if hasattr(e, '__context__') and e.__context__:
|
279
|
+
raise ToolError(e.__context__)
|
280
|
+
else:
|
281
|
+
raise ToolError(e)
|
282
|
+
|
283
|
+
@tl_mcp.tool()
|
284
|
+
async def rank_genes_groups(
|
285
|
+
request: RankGenesGroupsModel,
|
286
|
+
|
287
|
+
):
|
288
|
+
"""Rank genes for characterizing groups, for differentially expressison analysis"""
|
289
|
+
|
290
|
+
try:
|
291
|
+
result = await forward_request("tl_rank_genes_groups", request)
|
292
|
+
if result is not None:
|
293
|
+
return result
|
294
|
+
func_kwargs = filter_args(request, sc.tl.rank_genes_groups)
|
295
|
+
ads = get_ads()
|
296
|
+
adata = ads.get_adata(request=request)
|
297
|
+
sc.tl.rank_genes_groups(adata, **func_kwargs)
|
298
|
+
add_op_log(adata, sc.tl.rank_genes_groups, func_kwargs)
|
299
|
+
return [generate_msg(request, adata, ads)]
|
300
|
+
except ToolError as e:
|
301
|
+
raise ToolError(e)
|
302
|
+
except Exception as e:
|
303
|
+
if hasattr(e, '__context__') and e.__context__:
|
304
|
+
raise ToolError(e.__context__)
|
305
|
+
else:
|
306
|
+
raise ToolError(e)
|
307
|
+
|
308
|
+
|
309
|
+
@tl_mcp.tool()
|
310
|
+
async def filter_rank_genes_groups(
|
311
|
+
request: FilterRankGenesGroupsModel = FilterRankGenesGroupsModel()
|
312
|
+
):
|
313
|
+
"""Filter out genes based on fold change and fraction of genes"""
|
314
|
+
|
315
|
+
try:
|
316
|
+
result = await forward_request("tl_filter_rank_genes_groups", request)
|
317
|
+
if result is not None:
|
318
|
+
return result
|
319
|
+
func_kwargs = filter_args(request, sc.tl.filter_rank_genes_groups)
|
320
|
+
ads = get_ads()
|
321
|
+
adata = ads.get_adata(request=request)
|
322
|
+
sc.tl.filter_rank_genes_groups(adata, **func_kwargs)
|
323
|
+
add_op_log(adata, sc.tl.filter_rank_genes_groups, func_kwargs)
|
324
|
+
return [generate_msg(request, adata, ads)]
|
325
|
+
except ToolError as e:
|
326
|
+
raise ToolError(e)
|
327
|
+
except Exception as e:
|
328
|
+
if hasattr(e, '__context__') and e.__context__:
|
329
|
+
raise ToolError(e.__context__)
|
330
|
+
else:
|
331
|
+
raise ToolError(e)
|
332
|
+
|
333
|
+
|
334
|
+
@tl_mcp.tool()
|
335
|
+
async def marker_gene_overlap(
|
336
|
+
request: MarkerGeneOverlapModel = MarkerGeneOverlapModel()
|
337
|
+
):
|
338
|
+
"""Calculate overlap between data-derived marker genes and reference markers"""
|
339
|
+
|
340
|
+
try:
|
341
|
+
result = await forward_request("tl_marker_gene_overlap", request)
|
342
|
+
if result is not None:
|
343
|
+
return result
|
344
|
+
func_kwargs = filter_args(request, sc.tl.marker_gene_overlap)
|
345
|
+
ads = get_ads()
|
346
|
+
adata = ads.get_adata(request=request)
|
347
|
+
sc.tl.marker_gene_overlap(adata, **func_kwargs)
|
348
|
+
add_op_log(adata, sc.tl.marker_gene_overlap, func_kwargs)
|
349
|
+
return [generate_msg(request, adata, ads)]
|
350
|
+
except ToolError as e:
|
351
|
+
raise ToolError(e)
|
352
|
+
except Exception as e:
|
353
|
+
if hasattr(e, '__context__') and e.__context__:
|
354
|
+
raise ToolError(e.__context__)
|
355
|
+
else:
|
356
|
+
raise ToolError(e)
|
357
|
+
|
358
|
+
@tl_mcp.tool()
|
359
|
+
async def score_genes(
|
360
|
+
request: ScoreGenesModel,
|
361
|
+
|
362
|
+
):
|
363
|
+
"""Score a set of genes based on their average expression"""
|
364
|
+
try:
|
365
|
+
result = await forward_request("tl_score_genes", request)
|
366
|
+
if result is not None:
|
367
|
+
return result
|
368
|
+
func_kwargs = filter_args(request, sc.tl.score_genes)
|
369
|
+
ads = get_ads()
|
370
|
+
adata = ads.get_adata(request=request)
|
371
|
+
sc.tl.score_genes(adata, **func_kwargs)
|
372
|
+
add_op_log(adata, sc.tl.score_genes, func_kwargs)
|
373
|
+
return [generate_msg(request, adata, ads)]
|
374
|
+
except ToolError as e:
|
375
|
+
raise ToolError(e)
|
376
|
+
except Exception as e:
|
377
|
+
if hasattr(e, '__context__') and e.__context__:
|
378
|
+
raise ToolError(e.__context__)
|
379
|
+
else:
|
380
|
+
raise ToolError(e)
|
381
|
+
|
382
|
+
@tl_mcp.tool()
|
383
|
+
async def score_genes_cell_cycle(
|
384
|
+
request: ScoreGenesCellCycleModel,
|
385
|
+
|
386
|
+
):
|
387
|
+
"""Score cell cycle genes and assign cell cycle phases"""
|
388
|
+
|
389
|
+
try:
|
390
|
+
result = await forward_request("tl_score_genes_cell_cycle", request)
|
391
|
+
if result is not None:
|
392
|
+
return result
|
393
|
+
func_kwargs = filter_args(request, sc.tl.score_genes_cell_cycle)
|
394
|
+
ads = get_ads()
|
395
|
+
adata = ads.get_adata(request=request)
|
396
|
+
sc.tl.score_genes_cell_cycle(adata, **func_kwargs)
|
397
|
+
add_op_log(adata, sc.tl.score_genes_cell_cycle, func_kwargs)
|
398
|
+
return [generate_msg(request, adata, ads)]
|
399
|
+
except ToolError as e:
|
400
|
+
raise ToolError(e)
|
401
|
+
except Exception as e:
|
402
|
+
if hasattr(e, '__context__') and e.__context__:
|
403
|
+
raise ToolError(e.__context__)
|
404
|
+
else:
|
405
|
+
raise ToolError(e)
|
406
|
+
|
407
|
+
|
408
|
+
@tl_mcp.tool()
|
409
|
+
async def pca(
|
410
|
+
request: PCAModel = PCAModel()
|
411
|
+
):
|
412
|
+
"""Principal component analysis"""
|
413
|
+
|
414
|
+
try:
|
415
|
+
result = await forward_request("tl_pca", request)
|
416
|
+
if result is not None:
|
417
|
+
return result
|
418
|
+
func_kwargs = filter_args(request, sc.pp.pca)
|
419
|
+
ads = get_ads()
|
420
|
+
adata = ads.get_adata(request=request)
|
421
|
+
sc.pp.pca(adata, **func_kwargs)
|
422
|
+
add_op_log(adata, sc.pp.pca, func_kwargs)
|
423
|
+
return [
|
424
|
+
generate_msg(request, adata, ads)
|
425
|
+
]
|
426
|
+
except ToolError as e:
|
427
|
+
raise ToolError(e)
|
428
|
+
except Exception as e:
|
429
|
+
if hasattr(e, '__context__') and e.__context__:
|
430
|
+
raise ToolError(e.__context__)
|
431
|
+
else:
|
432
|
+
raise ToolError(e)
|
@@ -0,0 +1,251 @@
|
|
1
|
+
import os
|
2
|
+
import inspect
|
3
|
+
from pathlib import Path
|
4
|
+
import scanpy as sc
|
5
|
+
from fastmcp import FastMCP , Context
|
6
|
+
from fastmcp.exceptions import ToolError
|
7
|
+
from ..schema.util import *
|
8
|
+
from ..util import filter_args, forward_request, get_ads, generate_msg,add_op_log
|
9
|
+
|
10
|
+
|
11
|
+
ul_mcp = FastMCP("SCMCP-Util-Server")
|
12
|
+
|
13
|
+
|
14
|
+
@ul_mcp.tool()
|
15
|
+
async def query_op_log(request: QueryOpLogModel = QueryOpLogModel()):
|
16
|
+
"""Query the adata operation log"""
|
17
|
+
adata = get_ads().get_adata(request=request)
|
18
|
+
op_dic = adata.uns["operation"]["op"]
|
19
|
+
opids = adata.uns["operation"]["opid"][-n:]
|
20
|
+
op_list = []
|
21
|
+
for opid in opids:
|
22
|
+
op_list.append(op_dic[opid])
|
23
|
+
return op_list
|
24
|
+
|
25
|
+
|
26
|
+
@ul_mcp.tool()
|
27
|
+
async def mark_var(
|
28
|
+
request: MarkVarModel = MarkVarModel()
|
29
|
+
):
|
30
|
+
"""
|
31
|
+
Determine if each gene meets specific conditions and store results in adata.var as boolean values.
|
32
|
+
For example: mitochondrion genes startswith MT-.
|
33
|
+
The tool should be called first when calculate quality control metrics for mitochondrion, ribosomal, harhemoglobin genes, or other qc_vars.
|
34
|
+
"""
|
35
|
+
try:
|
36
|
+
result = await forward_request("ul_mark_var", request)
|
37
|
+
if result is not None:
|
38
|
+
return result
|
39
|
+
adata = get_ads().get_adata(request=request)
|
40
|
+
var_name = request.var_name
|
41
|
+
gene_class = request.gene_class
|
42
|
+
pattern_type = request.pattern_type
|
43
|
+
patterns = request.patterns
|
44
|
+
if gene_class is not None:
|
45
|
+
if gene_class == "mitochondrion":
|
46
|
+
adata.var["mt"] = adata.var_names.str.startswith(('MT-', 'Mt','mt-'))
|
47
|
+
var_name = "mt"
|
48
|
+
elif gene_class == "ribosomal":
|
49
|
+
adata.var["ribo"] = adata.var_names.str.startswith(("RPS", "RPL", "Rps", "Rpl"))
|
50
|
+
var_name = "ribo"
|
51
|
+
elif gene_class == "hemoglobin":
|
52
|
+
adata.var["hb"] = adata.var_names.str.contains("^HB[^(P)]", case=False)
|
53
|
+
var_name = "hb"
|
54
|
+
elif pattern_type is not None and patterns is not None:
|
55
|
+
if pattern_type == "startswith":
|
56
|
+
adata.var[var_name] = adata.var_names.str.startswith(patterns)
|
57
|
+
elif pattern_type == "endswith":
|
58
|
+
adata.var[var_name] = adata.var_names.str.endswith(patterns)
|
59
|
+
elif pattern_type == "contains":
|
60
|
+
adata.var[var_name] = adata.var_names.str.contains(patterns)
|
61
|
+
else:
|
62
|
+
raise ValueError(f"Did not support pattern_type: {pattern_type}")
|
63
|
+
else:
|
64
|
+
raise ValueError(f"Please provide validated parameter")
|
65
|
+
|
66
|
+
res = {var_name: adata.var[var_name].value_counts().to_dict(), "msg": f"add '{var_name}' column in adata.var"}
|
67
|
+
func_kwargs = {"var_name": var_name, "gene_class": gene_class, "pattern_type": pattern_type, "patterns": patterns}
|
68
|
+
add_op_log(adata, "mark_var", func_kwargs)
|
69
|
+
return res
|
70
|
+
except ToolError as e:
|
71
|
+
raise ToolError(e)
|
72
|
+
except Exception as e:
|
73
|
+
if hasattr(e, '__context__') and e.__context__:
|
74
|
+
raise ToolError(e.__context__)
|
75
|
+
else:
|
76
|
+
raise ToolError(e)
|
77
|
+
|
78
|
+
|
79
|
+
@ul_mcp.tool()
|
80
|
+
async def list_var(
|
81
|
+
request: ListVarModel = ListVarModel()
|
82
|
+
):
|
83
|
+
"""List key columns in adata.var. It should be called for checking when other tools need var key column names as input."""
|
84
|
+
try:
|
85
|
+
result = await forward_request("ul_list_var", request)
|
86
|
+
if result is not None:
|
87
|
+
return result
|
88
|
+
adata = get_ads().get_adata(request=request)
|
89
|
+
columns = list(adata.var.columns)
|
90
|
+
add_op_log(adata, list_var, {})
|
91
|
+
return columns
|
92
|
+
except ToolError as e:
|
93
|
+
raise ToolError(e)
|
94
|
+
except Exception as e:
|
95
|
+
if hasattr(e, '__context__') and e.__context__:
|
96
|
+
raise ToolError(e.__context__)
|
97
|
+
else:
|
98
|
+
raise ToolError(e)
|
99
|
+
|
100
|
+
@ul_mcp.tool()
|
101
|
+
async def list_obs(
|
102
|
+
request: ListObsModel = ListObsModel()
|
103
|
+
):
|
104
|
+
"""List key columns in adata.obs. It should be called before other tools need obs key column names input."""
|
105
|
+
try:
|
106
|
+
result = await forward_request("ul_list_obs", request)
|
107
|
+
if result is not None:
|
108
|
+
return result
|
109
|
+
adata = get_ads().get_adata(request=request)
|
110
|
+
columns = list(adata.obs.columns)
|
111
|
+
add_op_log(adata, list_obs, {})
|
112
|
+
return columns
|
113
|
+
except ToolError as e:
|
114
|
+
raise ToolError(e)
|
115
|
+
except Exception as e:
|
116
|
+
if hasattr(e, '__context__') and e.__context__:
|
117
|
+
raise ToolError(e.__context__)
|
118
|
+
else:
|
119
|
+
raise ToolError(e)
|
120
|
+
|
121
|
+
@ul_mcp.tool()
|
122
|
+
async def check_var(
|
123
|
+
request: VarNamesModel = VarNamesModel()
|
124
|
+
):
|
125
|
+
"""Check if genes/variables exist in adata.var_names. This tool should be called before gene expression visualizations or color by genes."""
|
126
|
+
try:
|
127
|
+
result = await forward_request("ul_check_var", request)
|
128
|
+
if result is not None:
|
129
|
+
return result
|
130
|
+
adata = get_ads().get_adata(request=request)
|
131
|
+
var_names = request.var_names
|
132
|
+
result = {v: v in adata.var_names for v in var_names}
|
133
|
+
add_op_log(adata, check_var, {"var_names": var_names})
|
134
|
+
return result
|
135
|
+
except ToolError as e:
|
136
|
+
raise ToolError(e)
|
137
|
+
except Exception as e:
|
138
|
+
if hasattr(e, '__context__') and e.__context__:
|
139
|
+
raise ToolError(e.__context__)
|
140
|
+
else:
|
141
|
+
raise ToolError(e)
|
142
|
+
|
143
|
+
@ul_mcp.tool()
|
144
|
+
async def merge_adata(
|
145
|
+
request: ConcatAdataModel = ConcatAdataModel()
|
146
|
+
):
|
147
|
+
"""Merge multiple adata objects."""
|
148
|
+
|
149
|
+
try:
|
150
|
+
result = await forward_request("ul_merge_adata", request)
|
151
|
+
if result is not None:
|
152
|
+
return result
|
153
|
+
ads = get_ads()
|
154
|
+
adata = ads.get_adata(request=request)
|
155
|
+
kwargs = {k: v for k, v in request.model_dump().items() if v is not None}
|
156
|
+
merged_adata = adata.concat(list(ads.adata_dic[dtype].values()), **kwargs)
|
157
|
+
ads.adata_dic[dtype] = {}
|
158
|
+
ads.active_id = "merged_adata"
|
159
|
+
add_op_log(merged_adata, ad.concat, kwargs)
|
160
|
+
ads.adata_dic[ads.active_id] = merged_adata
|
161
|
+
return {"status": "success", "message": "Successfully merged all AnnData objects"}
|
162
|
+
except ToolError as e:
|
163
|
+
raise ToolError(e)
|
164
|
+
except Exception as e:
|
165
|
+
if hasattr(e, '__context__') and e.__context__:
|
166
|
+
raise ToolError(e.__context__)
|
167
|
+
else:
|
168
|
+
raise ToolError(e)
|
169
|
+
|
170
|
+
|
171
|
+
@ul_mcp.tool()
|
172
|
+
async def set_dpt_iroot(
|
173
|
+
request: DPTIROOTModel,
|
174
|
+
|
175
|
+
):
|
176
|
+
"""Set the iroot cell"""
|
177
|
+
try:
|
178
|
+
result = await forward_request("ul_set_dpt_iroot", request)
|
179
|
+
if result is not None:
|
180
|
+
return result
|
181
|
+
adata = get_ads().get_adata(request=request)
|
182
|
+
diffmap_key = request.diffmap_key
|
183
|
+
dimension = request.dimension
|
184
|
+
direction = request.direction
|
185
|
+
if diffmap_key not in adata.obsm:
|
186
|
+
raise ValueError(f"Diffusion map key '{diffmap_key}' not found in adata.obsm")
|
187
|
+
if direction == "min":
|
188
|
+
adata.uns["iroot"] = adata.obsm[diffmap_key][:, dimension].argmin()
|
189
|
+
else:
|
190
|
+
adata.uns["iroot"] = adata.obsm[diffmap_key][:, dimension].argmax()
|
191
|
+
|
192
|
+
func_kwargs = {"diffmap_key": diffmap_key, "dimension": dimension, "direction": direction}
|
193
|
+
add_op_log(adata, "set_dpt_iroot", func_kwargs)
|
194
|
+
|
195
|
+
return {"status": "success", "message": f"Successfully set root cell for DPT using {direction} of dimension {dimension}"}
|
196
|
+
except ToolError as e:
|
197
|
+
raise ToolError(e)
|
198
|
+
except Exception as e:
|
199
|
+
if hasattr(e, '__context__') and e.__context__:
|
200
|
+
raise ToolError(e.__context__)
|
201
|
+
else:
|
202
|
+
raise ToolError(e)
|
203
|
+
|
204
|
+
@ul_mcp.tool()
|
205
|
+
async def add_layer(
|
206
|
+
request: AddLayerModel,
|
207
|
+
):
|
208
|
+
"""Add a layer to the AnnData object.
|
209
|
+
"""
|
210
|
+
try:
|
211
|
+
result = await forward_request("ul_add_layer", request)
|
212
|
+
if result is not None:
|
213
|
+
return result
|
214
|
+
adata = get_ads().get_adata(request=request)
|
215
|
+
layer_name = request.layer_name
|
216
|
+
|
217
|
+
# Check if layer already exists
|
218
|
+
if layer_name in adata.layers:
|
219
|
+
raise ValueError(f"Layer '{layer_name}' already exists in adata.layers")
|
220
|
+
# Add the data as a new layer
|
221
|
+
adata.layers[layer_name] = adata.X.copy()
|
222
|
+
|
223
|
+
func_kwargs = {"layer_name": layer_name}
|
224
|
+
add_op_log(adata, "add_layer", func_kwargs)
|
225
|
+
|
226
|
+
return {
|
227
|
+
"status": "success",
|
228
|
+
"message": f"Successfully added layer '{layer_name}' to adata.layers"
|
229
|
+
}
|
230
|
+
except ToolError as e:
|
231
|
+
raise ToolError(e)
|
232
|
+
except Exception as e:
|
233
|
+
if hasattr(e, '__context__') and e.__context__:
|
234
|
+
raise ToolError(e.__context__)
|
235
|
+
else:
|
236
|
+
raise ToolError(e)
|
237
|
+
|
238
|
+
@ul_mcp.tool()
|
239
|
+
async def check_samples():
|
240
|
+
"""check the stored samples
|
241
|
+
"""
|
242
|
+
try:
|
243
|
+
ads = get_ads()
|
244
|
+
return {"sampleid": [list(ads.adata_dic[dk].keys()) for dk in ads.adata_dic.keys()]}
|
245
|
+
except ToolError as e:
|
246
|
+
raise ToolError(e)
|
247
|
+
except Exception as e:
|
248
|
+
if hasattr(e, '__context__') and e.__context__:
|
249
|
+
raise ToolError(e.__context__)
|
250
|
+
else:
|
251
|
+
raise ToolError(e)
|