scmcp-shared 0.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.
- scmcp_shared/__init__.py +3 -0
- scmcp_shared/logging_config.py +31 -0
- scmcp_shared/schema/__init__.py +1 -0
- scmcp_shared/schema/io.py +120 -0
- scmcp_shared/schema/pl.py +948 -0
- scmcp_shared/schema/pp.py +707 -0
- scmcp_shared/schema/tl.py +902 -0
- scmcp_shared/schema/util.py +131 -0
- scmcp_shared/server/__init__.py +1 -0
- scmcp_shared/server/io.py +80 -0
- scmcp_shared/util.py +186 -0
- scmcp_shared-0.1.0.dist-info/METADATA +44 -0
- scmcp_shared-0.1.0.dist-info/RECORD +15 -0
- scmcp_shared-0.1.0.dist-info/WHEEL +4 -0
- scmcp_shared-0.1.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,707 @@
|
|
1
|
+
from pydantic import (
|
2
|
+
Field,
|
3
|
+
ValidationInfo,
|
4
|
+
computed_field,
|
5
|
+
field_validator,
|
6
|
+
model_validator,
|
7
|
+
BaseModel
|
8
|
+
)
|
9
|
+
from typing import Optional, Union, List, Dict, Any
|
10
|
+
from typing import Literal
|
11
|
+
import numpy as np
|
12
|
+
|
13
|
+
|
14
|
+
class FilterCells(BaseModel):
|
15
|
+
"""Input schema for the filter_cells preprocessing tool."""
|
16
|
+
|
17
|
+
min_counts: Optional[int] = Field(
|
18
|
+
default=None,
|
19
|
+
description="Minimum number of counts required for a cell to pass filtering."
|
20
|
+
)
|
21
|
+
|
22
|
+
min_genes: Optional[int] = Field(
|
23
|
+
default=None,
|
24
|
+
description="Minimum number of genes expressed required for a cell to pass filtering."
|
25
|
+
)
|
26
|
+
|
27
|
+
max_counts: Optional[int] = Field(
|
28
|
+
default=None,
|
29
|
+
description="Maximum number of counts required for a cell to pass filtering."
|
30
|
+
)
|
31
|
+
|
32
|
+
max_genes: Optional[int] = Field(
|
33
|
+
default=None,
|
34
|
+
description="Maximum number of genes expressed required for a cell to pass filtering."
|
35
|
+
)
|
36
|
+
|
37
|
+
@field_validator('min_counts', 'min_genes', 'max_counts', 'max_genes')
|
38
|
+
def validate_positive_integers(cls, v: Optional[int]) -> Optional[int]:
|
39
|
+
"""验证整数参数为正数"""
|
40
|
+
if v is not None and v <= 0:
|
41
|
+
raise ValueError("过滤参数必须是正整数")
|
42
|
+
return v
|
43
|
+
|
44
|
+
|
45
|
+
class FilterGenes(BaseModel):
|
46
|
+
"""Input schema for the filter_genes preprocessing tool."""
|
47
|
+
|
48
|
+
min_counts: Optional[int] = Field(
|
49
|
+
default=None,
|
50
|
+
description="Minimum number of counts required for a gene to pass filtering."
|
51
|
+
)
|
52
|
+
|
53
|
+
min_cells: Optional[int] = Field(
|
54
|
+
default=None,
|
55
|
+
description="Minimum number of cells expressed required for a gene to pass filtering."
|
56
|
+
)
|
57
|
+
|
58
|
+
max_counts: Optional[int] = Field(
|
59
|
+
default=None,
|
60
|
+
description="Maximum number of counts required for a gene to pass filtering."
|
61
|
+
)
|
62
|
+
|
63
|
+
max_cells: Optional[int] = Field(
|
64
|
+
default=None,
|
65
|
+
description="Maximum number of cells expressed required for a gene to pass filtering."
|
66
|
+
)
|
67
|
+
|
68
|
+
@field_validator('min_counts', 'min_cells', 'max_counts', 'max_cells')
|
69
|
+
def validate_positive_integers(cls, v: Optional[int]) -> Optional[int]:
|
70
|
+
"""验证整数参数为正数"""
|
71
|
+
if v is not None and v <= 0:
|
72
|
+
raise ValueError("must be positive_integers")
|
73
|
+
return v
|
74
|
+
|
75
|
+
|
76
|
+
class SubsetCellModel(BaseModel):
|
77
|
+
"""Input schema for subsetting AnnData objects based on various criteria."""
|
78
|
+
obs_key: Optional[str] = Field(
|
79
|
+
default=None,
|
80
|
+
description="Key in adata.obs to use for subsetting observations/cells."
|
81
|
+
)
|
82
|
+
obs_min: Optional[float] = Field(
|
83
|
+
default=None,
|
84
|
+
description="Minimum value for the obs_key to include in the subset."
|
85
|
+
)
|
86
|
+
obs_max: Optional[float] = Field(
|
87
|
+
default=None,
|
88
|
+
description="Maximum value for the obs_key to include in the subset."
|
89
|
+
)
|
90
|
+
obs_value: Optional[Any] = Field(
|
91
|
+
default=None,
|
92
|
+
description="Exact value for the obs_key to include in the subset (adata.obs[obs_key] == obs_value)."
|
93
|
+
)
|
94
|
+
min_counts: Optional[int] = Field(
|
95
|
+
default=None,
|
96
|
+
description="Minimum number of counts required for a cell to pass filtering."
|
97
|
+
)
|
98
|
+
min_genes: Optional[int] = Field(
|
99
|
+
default=None,
|
100
|
+
description="Minimum number of genes expressed required for a cell to pass filtering."
|
101
|
+
)
|
102
|
+
max_counts: Optional[int] = Field(
|
103
|
+
default=None,
|
104
|
+
description="Maximum number of counts required for a cell to pass filtering."
|
105
|
+
)
|
106
|
+
max_genes: Optional[int] = Field(
|
107
|
+
default=None,
|
108
|
+
description="Maximum number of genes expressed required for a cell to pass filtering."
|
109
|
+
)
|
110
|
+
|
111
|
+
|
112
|
+
class SubsetGeneModel(BaseModel):
|
113
|
+
"""Input schema for subsetting AnnData objects based on various criteria."""
|
114
|
+
min_counts: Optional[int] = Field(
|
115
|
+
default=None,
|
116
|
+
description="Minimum number of counts required for a gene to pass filtering."
|
117
|
+
)
|
118
|
+
min_cells: Optional[int] = Field(
|
119
|
+
default=None,
|
120
|
+
description="Minimum number of cells expressed required for a gene to pass filtering."
|
121
|
+
)
|
122
|
+
max_counts: Optional[int] = Field(
|
123
|
+
default=None,
|
124
|
+
description="Maximum number of counts required for a gene to pass filtering."
|
125
|
+
)
|
126
|
+
max_cells: Optional[int] = Field(
|
127
|
+
default=None,
|
128
|
+
description="Maximum number of cells expressed required for a gene to pass filtering."
|
129
|
+
)
|
130
|
+
var_key: Optional[str] = Field(
|
131
|
+
default=None,
|
132
|
+
description="Key in adata.var to use for subsetting variables/genes."
|
133
|
+
)
|
134
|
+
var_min: Optional[float] = Field(
|
135
|
+
default=None,
|
136
|
+
description="Minimum value for the var_key to include in the subset."
|
137
|
+
)
|
138
|
+
var_max: Optional[float] = Field(
|
139
|
+
default=None,
|
140
|
+
description="Maximum value for the var_key to include in the subset."
|
141
|
+
)
|
142
|
+
highly_variable: Optional[bool] = Field(
|
143
|
+
default=False,
|
144
|
+
description="If True, subset to highly variable genes. Requires 'highly_variable' column in adata.var."
|
145
|
+
)
|
146
|
+
|
147
|
+
|
148
|
+
class CalculateQCMetrics(BaseModel):
|
149
|
+
"""Input schema for the calculate_qc_metrics preprocessing tool."""
|
150
|
+
|
151
|
+
expr_type: str = Field(
|
152
|
+
default="counts",
|
153
|
+
description="Name of kind of values in X."
|
154
|
+
)
|
155
|
+
|
156
|
+
var_type: str = Field(
|
157
|
+
default="genes",
|
158
|
+
description="The kind of thing the variables are."
|
159
|
+
)
|
160
|
+
|
161
|
+
qc_vars: Optional[Union[List[str], str]] = Field(
|
162
|
+
default=[],
|
163
|
+
description=(
|
164
|
+
"Keys for boolean columns of .var which identify variables you could want to control for "
|
165
|
+
"mark_var tool should be called frist when you want to calculate mt, ribo, hb, and check tool output for var columns"
|
166
|
+
)
|
167
|
+
)
|
168
|
+
|
169
|
+
percent_top: Optional[List[int]] = Field(
|
170
|
+
default=[50, 100, 200, 500],
|
171
|
+
description="List of ranks (where genes are ranked by expression) at which the cumulative proportion of expression will be reported as a percentage."
|
172
|
+
)
|
173
|
+
|
174
|
+
layer: Optional[str] = Field(
|
175
|
+
default=None,
|
176
|
+
description="If provided, use adata.layers[layer] for expression values instead of adata.X"
|
177
|
+
)
|
178
|
+
|
179
|
+
use_raw: bool = Field(
|
180
|
+
default=False,
|
181
|
+
description="If True, use adata.raw.X for expression values instead of adata.X"
|
182
|
+
)
|
183
|
+
log1p: bool = Field(
|
184
|
+
default=True,
|
185
|
+
description="Set to False to skip computing log1p transformed annotations."
|
186
|
+
)
|
187
|
+
|
188
|
+
@field_validator('percent_top')
|
189
|
+
def validate_percent_top(cls, v: Optional[List[int]]) -> Optional[List[int]]:
|
190
|
+
"""验证 percent_top 中的值为正整数"""
|
191
|
+
if v is not None:
|
192
|
+
for rank in v:
|
193
|
+
if not isinstance(rank, int) or rank <= 0:
|
194
|
+
raise ValueError("percent_top 中的所有值必须是正整数")
|
195
|
+
return v
|
196
|
+
|
197
|
+
|
198
|
+
|
199
|
+
class Log1PModel(BaseModel):
|
200
|
+
"""Input schema for the log1p preprocessing tool."""
|
201
|
+
|
202
|
+
base: Optional[Union[int, float]] = Field(
|
203
|
+
default=None,
|
204
|
+
description="Base of the logarithm. Natural logarithm is used by default."
|
205
|
+
)
|
206
|
+
|
207
|
+
chunked: Optional[bool] = Field(
|
208
|
+
default=None,
|
209
|
+
description="Process the data matrix in chunks, which will save memory."
|
210
|
+
)
|
211
|
+
|
212
|
+
chunk_size: Optional[int] = Field(
|
213
|
+
default=None,
|
214
|
+
description="Number of observations in the chunks to process the data in."
|
215
|
+
)
|
216
|
+
|
217
|
+
layer: Optional[str] = Field(
|
218
|
+
default=None,
|
219
|
+
description="Entry of layers to transform."
|
220
|
+
)
|
221
|
+
|
222
|
+
obsm: Optional[str] = Field(
|
223
|
+
default=None,
|
224
|
+
description="Entry of obsm to transform."
|
225
|
+
)
|
226
|
+
|
227
|
+
@field_validator('chunk_size')
|
228
|
+
def validate_chunk_size(cls, v: Optional[int]) -> Optional[int]:
|
229
|
+
"""Validate chunk_size is positive integer"""
|
230
|
+
if v is not None and v <= 0:
|
231
|
+
raise ValueError("chunk_size must be a positive integer")
|
232
|
+
return v
|
233
|
+
|
234
|
+
|
235
|
+
class PCAModel(BaseModel):
|
236
|
+
"""Input schema for the PCA preprocessing tool."""
|
237
|
+
|
238
|
+
n_comps: Optional[int] = Field(
|
239
|
+
default=None,
|
240
|
+
description="Number of principal components to compute. Defaults to 50 or 1 - minimum dimension size.",
|
241
|
+
gt=0
|
242
|
+
)
|
243
|
+
|
244
|
+
layer: Optional[str] = Field(
|
245
|
+
default=None,
|
246
|
+
description="If provided, which element of layers to use for PCA."
|
247
|
+
)
|
248
|
+
|
249
|
+
zero_center: Optional[bool] = Field(
|
250
|
+
default=True,
|
251
|
+
description="If True, compute standard PCA from covariance matrix."
|
252
|
+
)
|
253
|
+
|
254
|
+
svd_solver: Optional[Literal["arpack", "randomized", "auto", "lobpcg", "tsqr"]] = Field(
|
255
|
+
default=None,
|
256
|
+
description="SVD solver to use."
|
257
|
+
)
|
258
|
+
mask_var: Optional[Union[str, bool]] = Field(
|
259
|
+
default=None,
|
260
|
+
description="Boolean mask or string referring to var column for subsetting genes."
|
261
|
+
)
|
262
|
+
dtype: str = Field(
|
263
|
+
default="float32",
|
264
|
+
description="Numpy data type string for the result."
|
265
|
+
)
|
266
|
+
chunked: bool = Field(
|
267
|
+
default=False,
|
268
|
+
description="If True, perform an incremental PCA on segments."
|
269
|
+
)
|
270
|
+
|
271
|
+
chunk_size: Optional[int] = Field(
|
272
|
+
default=None,
|
273
|
+
description="Number of observations to include in each chunk.",
|
274
|
+
gt=0
|
275
|
+
)
|
276
|
+
|
277
|
+
@field_validator('n_comps', 'chunk_size')
|
278
|
+
def validate_positive_integers(cls, v: Optional[int]) -> Optional[int]:
|
279
|
+
"""Validate positive integers"""
|
280
|
+
if v is not None and v <= 0:
|
281
|
+
raise ValueError("must be a positive integer")
|
282
|
+
return v
|
283
|
+
|
284
|
+
@field_validator('dtype')
|
285
|
+
def validate_dtype(cls, v: str) -> str:
|
286
|
+
"""Validate numpy dtype"""
|
287
|
+
if v not in ["float32", "float64"]:
|
288
|
+
raise ValueError("dtype must be either 'float32' or 'float64'")
|
289
|
+
return v
|
290
|
+
|
291
|
+
|
292
|
+
class HighlyVariableGenesModel(BaseModel):
|
293
|
+
"""Input schema for the highly_variable_genes preprocessing tool."""
|
294
|
+
|
295
|
+
layer: Optional[str] = Field(
|
296
|
+
default=None,
|
297
|
+
description="If provided, use adata.layers[layer] for expression values."
|
298
|
+
)
|
299
|
+
|
300
|
+
n_top_genes: Optional[int] = Field(
|
301
|
+
default=None,
|
302
|
+
description="Number of highly-variable genes to keep. Mandatory if `flavor='seurat_v3'",
|
303
|
+
)
|
304
|
+
|
305
|
+
min_disp: Optional[float] = Field(
|
306
|
+
default=0.5,
|
307
|
+
description="Minimum dispersion cutoff for gene selection."
|
308
|
+
)
|
309
|
+
|
310
|
+
max_disp: Optional[float] = Field(
|
311
|
+
default=np.inf,
|
312
|
+
description="Maximum dispersion cutoff for gene selection."
|
313
|
+
)
|
314
|
+
min_mean: Optional[float] = Field(
|
315
|
+
default=0.0125,
|
316
|
+
description="Minimum mean expression cutoff for gene selection."
|
317
|
+
)
|
318
|
+
max_mean: Optional[float] = Field(
|
319
|
+
default=3,
|
320
|
+
description="Maximum mean expression cutoff for gene selection."
|
321
|
+
)
|
322
|
+
span: Optional[float] = Field(
|
323
|
+
default=0.3,
|
324
|
+
description="Fraction of data used for loess model fit in seurat_v3.",
|
325
|
+
gt=0,
|
326
|
+
lt=1
|
327
|
+
)
|
328
|
+
n_bins: Optional[int] = Field(
|
329
|
+
default=20,
|
330
|
+
description="Number of bins for mean expression binning.",
|
331
|
+
gt=0
|
332
|
+
)
|
333
|
+
flavor: Optional[Literal['seurat', 'cell_ranger', 'seurat_v3', 'seurat_v3_paper']] = Field(
|
334
|
+
default='seurat',
|
335
|
+
description="Method for identifying highly variable genes."
|
336
|
+
)
|
337
|
+
subset: Optional[bool] = Field(
|
338
|
+
default=False,
|
339
|
+
description="Inplace subset to highly-variable genes if True."
|
340
|
+
)
|
341
|
+
batch_key: Optional[str] = Field(
|
342
|
+
default=None,
|
343
|
+
description="Key in adata.obs for batch information."
|
344
|
+
)
|
345
|
+
|
346
|
+
check_values: Optional[bool] = Field(
|
347
|
+
default=True,
|
348
|
+
description="Check if counts are integers for seurat_v3 flavor."
|
349
|
+
)
|
350
|
+
|
351
|
+
@field_validator('n_top_genes', 'n_bins')
|
352
|
+
def validate_positive_integers(cls, v: Optional[int]) -> Optional[int]:
|
353
|
+
"""Validate positive integers"""
|
354
|
+
if v is not None and v <= 0:
|
355
|
+
raise ValueError("must be a positive integer")
|
356
|
+
return v
|
357
|
+
|
358
|
+
@field_validator('span')
|
359
|
+
def validate_span(cls, v: float) -> float:
|
360
|
+
"""Validate span is between 0 and 1"""
|
361
|
+
if v <= 0 or v >= 1:
|
362
|
+
raise ValueError("span must be between 0 and 1")
|
363
|
+
return v
|
364
|
+
|
365
|
+
|
366
|
+
class RegressOutModel(BaseModel):
|
367
|
+
"""Input schema for the regress_out preprocessing tool."""
|
368
|
+
|
369
|
+
keys: Union[str, List[str]] = Field(
|
370
|
+
description="Keys for observation annotation on which to regress on."
|
371
|
+
)
|
372
|
+
layer: Optional[str] = Field(
|
373
|
+
default=None,
|
374
|
+
description="If provided, which element of layers to regress on."
|
375
|
+
)
|
376
|
+
n_jobs: Optional[int] = Field(
|
377
|
+
default=None,
|
378
|
+
description="Number of jobs for parallel computation.",
|
379
|
+
gt=0
|
380
|
+
)
|
381
|
+
|
382
|
+
@field_validator('n_jobs')
|
383
|
+
def validate_n_jobs(cls, v: Optional[int]) -> Optional[int]:
|
384
|
+
"""Validate n_jobs is positive integer"""
|
385
|
+
if v is not None and v <= 0:
|
386
|
+
raise ValueError("n_jobs must be a positive integer")
|
387
|
+
return v
|
388
|
+
|
389
|
+
@field_validator('keys')
|
390
|
+
def validate_keys(cls, v: Union[str, List[str]]) -> Union[str, List[str]]:
|
391
|
+
"""Ensure keys is either a string or list of strings"""
|
392
|
+
if isinstance(v, str):
|
393
|
+
return v
|
394
|
+
elif isinstance(v, list) and all(isinstance(item, str) for item in v):
|
395
|
+
return v
|
396
|
+
raise ValueError("keys must be a string or list of strings")
|
397
|
+
|
398
|
+
|
399
|
+
class ScaleModel(BaseModel):
|
400
|
+
"""Input schema for the scale preprocessing tool."""
|
401
|
+
|
402
|
+
zero_center: bool = Field(
|
403
|
+
default=True,
|
404
|
+
description="If False, omit zero-centering variables to handle sparse input efficiently."
|
405
|
+
)
|
406
|
+
|
407
|
+
max_value: Optional[float] = Field(
|
408
|
+
default=None,
|
409
|
+
description="Clip (truncate) to this value after scaling. If None, do not clip."
|
410
|
+
)
|
411
|
+
|
412
|
+
layer: Optional[str] = Field(
|
413
|
+
default=None,
|
414
|
+
description="If provided, which element of layers to scale."
|
415
|
+
)
|
416
|
+
|
417
|
+
obsm: Optional[str] = Field(
|
418
|
+
default=None,
|
419
|
+
description="If provided, which element of obsm to scale."
|
420
|
+
)
|
421
|
+
|
422
|
+
mask_obs: Optional[Union[str, bool]] = Field(
|
423
|
+
default=None,
|
424
|
+
description="Boolean mask or string referring to obs column for subsetting observations."
|
425
|
+
)
|
426
|
+
|
427
|
+
@field_validator('max_value')
|
428
|
+
def validate_max_value(cls, v: Optional[float]) -> Optional[float]:
|
429
|
+
"""Validate max_value is positive if provided"""
|
430
|
+
if v is not None and v <= 0:
|
431
|
+
raise ValueError("max_value must be positive if provided")
|
432
|
+
return v
|
433
|
+
|
434
|
+
|
435
|
+
class CombatModel(BaseModel):
|
436
|
+
"""Input schema for the combat batch effect correction tool."""
|
437
|
+
|
438
|
+
key: str = Field(
|
439
|
+
default='batch',
|
440
|
+
description="Key to a categorical annotation from adata.obs that will be used for batch effect removal."
|
441
|
+
)
|
442
|
+
|
443
|
+
covariates: Optional[List[str]] = Field(
|
444
|
+
default=None,
|
445
|
+
description="Additional covariates besides the batch variable such as adjustment variables or biological condition."
|
446
|
+
)
|
447
|
+
|
448
|
+
@field_validator('key')
|
449
|
+
def validate_key(cls, v: str) -> str:
|
450
|
+
"""Validate key is not empty"""
|
451
|
+
if not v.strip():
|
452
|
+
raise ValueError("key cannot be empty")
|
453
|
+
return v
|
454
|
+
|
455
|
+
@field_validator('covariates')
|
456
|
+
def validate_covariates(cls, v: Optional[List[str]]) -> Optional[List[str]]:
|
457
|
+
"""Validate covariates are non-empty strings if provided"""
|
458
|
+
if v is not None:
|
459
|
+
if not all(isinstance(item, str) and item.strip() for item in v):
|
460
|
+
raise ValueError("covariates must be non-empty strings")
|
461
|
+
return v
|
462
|
+
|
463
|
+
|
464
|
+
class ScrubletModel(BaseModel):
|
465
|
+
"""Input schema for the scrublet doublet prediction tool."""
|
466
|
+
|
467
|
+
adata_sim: Optional[str] = Field(
|
468
|
+
default=None,
|
469
|
+
description="Optional path to AnnData object with simulated doublets."
|
470
|
+
)
|
471
|
+
|
472
|
+
batch_key: Optional[str] = Field(
|
473
|
+
default=None,
|
474
|
+
description="Key in adata.obs for batch information."
|
475
|
+
)
|
476
|
+
|
477
|
+
sim_doublet_ratio: float = Field(
|
478
|
+
default=2.0,
|
479
|
+
description="Number of doublets to simulate relative to observed transcriptomes.",
|
480
|
+
gt=0
|
481
|
+
)
|
482
|
+
|
483
|
+
expected_doublet_rate: float = Field(
|
484
|
+
default=0.05,
|
485
|
+
description="Estimated doublet rate for the experiment.",
|
486
|
+
ge=0,
|
487
|
+
le=1
|
488
|
+
)
|
489
|
+
|
490
|
+
stdev_doublet_rate: float = Field(
|
491
|
+
default=0.02,
|
492
|
+
description="Uncertainty in the expected doublet rate.",
|
493
|
+
ge=0,
|
494
|
+
le=1
|
495
|
+
)
|
496
|
+
|
497
|
+
synthetic_doublet_umi_subsampling: float = Field(
|
498
|
+
default=1.0,
|
499
|
+
description="Rate for sampling UMIs when creating synthetic doublets.",
|
500
|
+
gt=0,
|
501
|
+
le=1
|
502
|
+
)
|
503
|
+
|
504
|
+
knn_dist_metric: str = Field(
|
505
|
+
default="euclidean",
|
506
|
+
description="Distance metric used when finding nearest neighbors."
|
507
|
+
)
|
508
|
+
|
509
|
+
normalize_variance: bool = Field(
|
510
|
+
default=True,
|
511
|
+
description="Normalize data such that each gene has variance of 1."
|
512
|
+
)
|
513
|
+
|
514
|
+
log_transform: bool = Field(
|
515
|
+
default=False,
|
516
|
+
description="Whether to log-transform the data prior to PCA."
|
517
|
+
)
|
518
|
+
|
519
|
+
mean_center: bool = Field(
|
520
|
+
default=True,
|
521
|
+
description="Center data such that each gene has mean of 0."
|
522
|
+
)
|
523
|
+
|
524
|
+
n_prin_comps: int = Field(
|
525
|
+
default=30,
|
526
|
+
description="Number of principal components used for embedding.",
|
527
|
+
gt=0
|
528
|
+
)
|
529
|
+
|
530
|
+
use_approx_neighbors: Optional[bool] = Field(
|
531
|
+
default=None,
|
532
|
+
description="Use approximate nearest neighbor method (annoy)."
|
533
|
+
)
|
534
|
+
|
535
|
+
get_doublet_neighbor_parents: bool = Field(
|
536
|
+
default=False,
|
537
|
+
description="Return parent transcriptomes that generated doublet neighbors."
|
538
|
+
)
|
539
|
+
|
540
|
+
n_neighbors: Optional[int] = Field(
|
541
|
+
default=None,
|
542
|
+
description="Number of neighbors used to construct KNN graph.",
|
543
|
+
gt=0
|
544
|
+
)
|
545
|
+
|
546
|
+
threshold: Optional[float] = Field(
|
547
|
+
default=None,
|
548
|
+
description="Doublet score threshold for calling a transcriptome a doublet.",
|
549
|
+
ge=0,
|
550
|
+
le=1
|
551
|
+
)
|
552
|
+
|
553
|
+
@field_validator('sim_doublet_ratio', 'expected_doublet_rate', 'stdev_doublet_rate',
|
554
|
+
'synthetic_doublet_umi_subsampling', 'n_prin_comps', 'n_neighbors')
|
555
|
+
def validate_positive_numbers(cls, v: Optional[Union[int, float]]) -> Optional[Union[int, float]]:
|
556
|
+
"""Validate positive numbers where applicable"""
|
557
|
+
if v is not None and v <= 0:
|
558
|
+
raise ValueError("must be a positive number")
|
559
|
+
return v
|
560
|
+
|
561
|
+
@field_validator('knn_dist_metric')
|
562
|
+
def validate_knn_dist_metric(cls, v: str) -> str:
|
563
|
+
"""Validate distance metric is supported"""
|
564
|
+
valid_metrics = ['euclidean', 'manhattan', 'cosine', 'correlation']
|
565
|
+
if v.lower() not in valid_metrics:
|
566
|
+
raise ValueError(f"knn_dist_metric must be one of {valid_metrics}")
|
567
|
+
return v.lower()
|
568
|
+
|
569
|
+
|
570
|
+
class NeighborsModel(BaseModel):
|
571
|
+
"""Input schema for the neighbors graph construction tool."""
|
572
|
+
|
573
|
+
n_neighbors: int = Field(
|
574
|
+
default=15,
|
575
|
+
description="Size of local neighborhood used for manifold approximation.",
|
576
|
+
gt=1,
|
577
|
+
le=100
|
578
|
+
)
|
579
|
+
|
580
|
+
n_pcs: Optional[int] = Field(
|
581
|
+
default=None,
|
582
|
+
description="Number of PCs to use. If None, automatically determined.",
|
583
|
+
ge=0
|
584
|
+
)
|
585
|
+
|
586
|
+
use_rep: Optional[str] = Field(
|
587
|
+
default=None,
|
588
|
+
description="Key for .obsm to use as representation."
|
589
|
+
)
|
590
|
+
|
591
|
+
knn: bool = Field(
|
592
|
+
default=True,
|
593
|
+
description="Whether to use hard threshold for neighbor restriction."
|
594
|
+
)
|
595
|
+
|
596
|
+
method: Literal['umap', 'gauss'] = Field(
|
597
|
+
default='umap',
|
598
|
+
description="Method for computing connectivities ('umap' or 'gauss')."
|
599
|
+
)
|
600
|
+
|
601
|
+
transformer: Optional[str] = Field(
|
602
|
+
default=None,
|
603
|
+
description="Approximate kNN search implementation ('pynndescent' or 'rapids')."
|
604
|
+
)
|
605
|
+
|
606
|
+
metric: str = Field(
|
607
|
+
default='euclidean',
|
608
|
+
description="Distance metric to use."
|
609
|
+
)
|
610
|
+
|
611
|
+
metric_kwds: Dict[str, Any] = Field(
|
612
|
+
default_factory=dict,
|
613
|
+
description="Options for the distance metric."
|
614
|
+
)
|
615
|
+
|
616
|
+
random_state: int = Field(
|
617
|
+
default=0,
|
618
|
+
description="Random seed for reproducibility."
|
619
|
+
)
|
620
|
+
|
621
|
+
key_added: Optional[str] = Field(
|
622
|
+
default=None,
|
623
|
+
description="Key prefix for storing neighbor results."
|
624
|
+
)
|
625
|
+
|
626
|
+
@field_validator('n_neighbors', 'n_pcs')
|
627
|
+
def validate_positive_integers(cls, v: Optional[int]) -> Optional[int]:
|
628
|
+
"""Validate positive integers where applicable"""
|
629
|
+
if v is not None and v <= 0:
|
630
|
+
raise ValueError("must be a positive integer")
|
631
|
+
return v
|
632
|
+
|
633
|
+
@field_validator('method')
|
634
|
+
def validate_method(cls, v: str) -> str:
|
635
|
+
"""Validate method is supported"""
|
636
|
+
if v not in ['umap', 'gauss']:
|
637
|
+
raise ValueError("method must be either 'umap' or 'gauss'")
|
638
|
+
return v
|
639
|
+
|
640
|
+
@field_validator('transformer')
|
641
|
+
def validate_transformer(cls, v: Optional[str]) -> Optional[str]:
|
642
|
+
"""Validate transformer option is supported"""
|
643
|
+
if v is not None and v not in ['pynndescent', 'rapids']:
|
644
|
+
raise ValueError("transformer must be either 'pynndescent' or 'rapids'")
|
645
|
+
return v
|
646
|
+
|
647
|
+
|
648
|
+
class NormalizeTotalModel(BaseModel):
|
649
|
+
"""Input schema for the normalize_total preprocessing tool."""
|
650
|
+
|
651
|
+
target_sum: Optional[float] = Field(
|
652
|
+
default=None,
|
653
|
+
description="If None, after normalization, each cell has a total count equal to the median of total counts before normalization. If a number is provided, each cell will have this total count after normalization."
|
654
|
+
)
|
655
|
+
|
656
|
+
exclude_highly_expressed: bool = Field(
|
657
|
+
default=False,
|
658
|
+
description="Exclude highly expressed genes for the computation of the normalization factor for each cell."
|
659
|
+
)
|
660
|
+
|
661
|
+
max_fraction: float = Field(
|
662
|
+
default=0.05,
|
663
|
+
description="If exclude_highly_expressed=True, consider cells as highly expressed that have more counts than max_fraction of the original total counts in at least one cell.",
|
664
|
+
gt=0,
|
665
|
+
le=1
|
666
|
+
)
|
667
|
+
|
668
|
+
key_added: Optional[str] = Field(
|
669
|
+
default=None,
|
670
|
+
description="Name of the field in adata.obs where the normalization factor is stored."
|
671
|
+
)
|
672
|
+
|
673
|
+
layer: Optional[str] = Field(
|
674
|
+
default=None,
|
675
|
+
description="Layer to normalize instead of X. If None, X is normalized."
|
676
|
+
)
|
677
|
+
|
678
|
+
layers: Optional[Union[Literal['all'], List[str]]] = Field(
|
679
|
+
default=None,
|
680
|
+
description="List of layers to normalize. If 'all', normalize all layers."
|
681
|
+
)
|
682
|
+
|
683
|
+
layer_norm: Optional[str] = Field(
|
684
|
+
default=None,
|
685
|
+
description="Specifies how to normalize layers."
|
686
|
+
)
|
687
|
+
|
688
|
+
inplace: bool = Field(
|
689
|
+
default=True,
|
690
|
+
description="Whether to update adata or return dictionary with normalized copies."
|
691
|
+
)
|
692
|
+
|
693
|
+
@field_validator('target_sum')
|
694
|
+
def validate_target_sum(cls, v: Optional[float]) -> Optional[float]:
|
695
|
+
"""Validate target_sum is positive if provided"""
|
696
|
+
if v is not None and v <= 0:
|
697
|
+
raise ValueError("target_sum must be positive")
|
698
|
+
return v
|
699
|
+
|
700
|
+
@field_validator('max_fraction')
|
701
|
+
def validate_max_fraction(cls, v: float) -> float:
|
702
|
+
"""Validate max_fraction is between 0 and 1"""
|
703
|
+
if v <= 0 or v > 1:
|
704
|
+
raise ValueError("max_fraction must be between 0 and 1")
|
705
|
+
return v
|
706
|
+
|
707
|
+
|