senoquant 1.0.0b1__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.
- senoquant/__init__.py +6 -0
- senoquant/_reader.py +7 -0
- senoquant/_widget.py +33 -0
- senoquant/napari.yaml +83 -0
- senoquant/reader/__init__.py +5 -0
- senoquant/reader/core.py +369 -0
- senoquant/tabs/__init__.py +15 -0
- senoquant/tabs/batch/__init__.py +10 -0
- senoquant/tabs/batch/backend.py +641 -0
- senoquant/tabs/batch/config.py +270 -0
- senoquant/tabs/batch/frontend.py +1283 -0
- senoquant/tabs/batch/io.py +326 -0
- senoquant/tabs/batch/layers.py +86 -0
- senoquant/tabs/quantification/__init__.py +1 -0
- senoquant/tabs/quantification/backend.py +228 -0
- senoquant/tabs/quantification/features/__init__.py +80 -0
- senoquant/tabs/quantification/features/base.py +142 -0
- senoquant/tabs/quantification/features/marker/__init__.py +5 -0
- senoquant/tabs/quantification/features/marker/config.py +69 -0
- senoquant/tabs/quantification/features/marker/dialog.py +437 -0
- senoquant/tabs/quantification/features/marker/export.py +879 -0
- senoquant/tabs/quantification/features/marker/feature.py +119 -0
- senoquant/tabs/quantification/features/marker/morphology.py +285 -0
- senoquant/tabs/quantification/features/marker/rows.py +654 -0
- senoquant/tabs/quantification/features/marker/thresholding.py +46 -0
- senoquant/tabs/quantification/features/roi.py +346 -0
- senoquant/tabs/quantification/features/spots/__init__.py +5 -0
- senoquant/tabs/quantification/features/spots/config.py +62 -0
- senoquant/tabs/quantification/features/spots/dialog.py +477 -0
- senoquant/tabs/quantification/features/spots/export.py +1292 -0
- senoquant/tabs/quantification/features/spots/feature.py +112 -0
- senoquant/tabs/quantification/features/spots/morphology.py +279 -0
- senoquant/tabs/quantification/features/spots/rows.py +241 -0
- senoquant/tabs/quantification/frontend.py +815 -0
- senoquant/tabs/segmentation/__init__.py +1 -0
- senoquant/tabs/segmentation/backend.py +131 -0
- senoquant/tabs/segmentation/frontend.py +1009 -0
- senoquant/tabs/segmentation/models/__init__.py +5 -0
- senoquant/tabs/segmentation/models/base.py +146 -0
- senoquant/tabs/segmentation/models/cpsam/details.json +65 -0
- senoquant/tabs/segmentation/models/cpsam/model.py +150 -0
- senoquant/tabs/segmentation/models/default_2d/details.json +69 -0
- senoquant/tabs/segmentation/models/default_2d/model.py +664 -0
- senoquant/tabs/segmentation/models/default_3d/details.json +69 -0
- senoquant/tabs/segmentation/models/default_3d/model.py +682 -0
- senoquant/tabs/segmentation/models/hf.py +71 -0
- senoquant/tabs/segmentation/models/nuclear_dilation/__init__.py +1 -0
- senoquant/tabs/segmentation/models/nuclear_dilation/details.json +26 -0
- senoquant/tabs/segmentation/models/nuclear_dilation/model.py +96 -0
- senoquant/tabs/segmentation/models/perinuclear_rings/__init__.py +1 -0
- senoquant/tabs/segmentation/models/perinuclear_rings/details.json +34 -0
- senoquant/tabs/segmentation/models/perinuclear_rings/model.py +132 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/__init__.py +2 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/__init__.py +3 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/__init__.py +6 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/generate.py +470 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/prepare.py +273 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/rawdata.py +112 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/transform.py +384 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/__init__.py +0 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/blocks.py +184 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/losses.py +79 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/nets.py +165 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/predict.py +467 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/probability.py +67 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/train.py +148 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/io/__init__.py +163 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/__init__.py +52 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/base_model.py +329 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_isotropic.py +160 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_projection.py +178 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_standard.py +446 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_upsampling.py +54 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/config.py +254 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/pretrained.py +119 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/scripts/__init__.py +0 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/scripts/care_predict.py +180 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/__init__.py +5 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/plot_utils.py +159 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/six.py +18 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/tf.py +644 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/utils.py +272 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/version.py +1 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/docs/source/conf.py +368 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/setup.py +68 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_datagen.py +169 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_models.py +462 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_utils.py +166 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tools/create_zip_contents.py +34 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/__init__.py +30 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/big.py +624 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/bioimageio_utils.py +494 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/data/__init__.py +39 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/__init__.py +10 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/geom2d.py +215 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/geom3d.py +349 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/matching.py +483 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/__init__.py +28 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/base.py +1217 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/model2d.py +594 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/model3d.py +696 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/nms.py +384 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/__init__.py +2 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/plot.py +74 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/render.py +298 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/rays3d.py +373 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/sample_patches.py +65 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/__init__.py +0 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/predict2d.py +90 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/predict3d.py +93 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/utils.py +408 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/version.py +1 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/__init__.py +45 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/__init__.py +17 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/cli.py +55 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/core.py +285 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/__init__.py +15 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/cli.py +36 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/divisibility.py +193 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/probe.py +100 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/receptive_field.py +182 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/rf_cli.py +48 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/valid_sizes.py +278 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/post/__init__.py +8 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/post/core.py +157 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/pre/__init__.py +17 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/pre/core.py +226 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/predict/__init__.py +5 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/predict/core.py +401 -0
- senoquant/tabs/settings/__init__.py +1 -0
- senoquant/tabs/settings/backend.py +29 -0
- senoquant/tabs/settings/frontend.py +19 -0
- senoquant/tabs/spots/__init__.py +1 -0
- senoquant/tabs/spots/backend.py +139 -0
- senoquant/tabs/spots/frontend.py +800 -0
- senoquant/tabs/spots/models/__init__.py +5 -0
- senoquant/tabs/spots/models/base.py +94 -0
- senoquant/tabs/spots/models/rmp/details.json +61 -0
- senoquant/tabs/spots/models/rmp/model.py +499 -0
- senoquant/tabs/spots/models/udwt/details.json +103 -0
- senoquant/tabs/spots/models/udwt/model.py +482 -0
- senoquant/utils.py +25 -0
- senoquant-1.0.0b1.dist-info/METADATA +193 -0
- senoquant-1.0.0b1.dist-info/RECORD +148 -0
- senoquant-1.0.0b1.dist-info/WHEEL +5 -0
- senoquant-1.0.0b1.dist-info/entry_points.txt +2 -0
- senoquant-1.0.0b1.dist-info/licenses/LICENSE +28 -0
- senoquant-1.0.0b1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Model wrapper for segmentation resources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SenoQuantSegmentationModel:
|
|
10
|
+
"""Handle per-model storage and metadata paths.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
name : str
|
|
15
|
+
Model identifier used for folder creation.
|
|
16
|
+
models_root : pathlib.Path or None
|
|
17
|
+
Optional root folder for model storage.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, name: str, models_root: Path | None = None) -> None:
|
|
21
|
+
"""Initialize the model wrapper and ensure its folder exists."""
|
|
22
|
+
if not name:
|
|
23
|
+
raise ValueError("Model name must be non-empty.")
|
|
24
|
+
|
|
25
|
+
self.name = name
|
|
26
|
+
self.models_root = models_root or Path(__file__).parent
|
|
27
|
+
self.model_dir = self.models_root / name
|
|
28
|
+
self.model_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def details_path(self) -> Path:
|
|
32
|
+
"""Return the path to the details JSON file."""
|
|
33
|
+
return self.model_dir / "details.json"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def class_path(self) -> Path:
|
|
37
|
+
"""Return the path to the model class file."""
|
|
38
|
+
return self.model_dir / "model.py"
|
|
39
|
+
|
|
40
|
+
def load_details(self) -> dict:
|
|
41
|
+
"""Load model metadata from the details file.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
dict
|
|
46
|
+
Parsed model metadata dictionary.
|
|
47
|
+
"""
|
|
48
|
+
if not self.details_path.exists():
|
|
49
|
+
return {}
|
|
50
|
+
with self.details_path.open("r", encoding="utf-8") as handle:
|
|
51
|
+
return json.load(handle)
|
|
52
|
+
|
|
53
|
+
def run(self, **kwargs) -> dict | None:
|
|
54
|
+
"""Run the model with the provided inputs and settings.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
**kwargs
|
|
59
|
+
Model inputs and settings passed from the UI.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
dict or None
|
|
64
|
+
Result dictionary from the model, or None if not implemented.
|
|
65
|
+
"""
|
|
66
|
+
raise NotImplementedError("Model run not implemented.")
|
|
67
|
+
|
|
68
|
+
def list_settings(self) -> list[dict]:
|
|
69
|
+
"""Return the settings definitions for this model.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
list[dict]
|
|
74
|
+
Settings definitions for building the UI.
|
|
75
|
+
"""
|
|
76
|
+
details = self.load_details()
|
|
77
|
+
settings = details.get("settings", [])
|
|
78
|
+
if isinstance(settings, list):
|
|
79
|
+
return settings
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
def display_order(self) -> float | None:
|
|
83
|
+
"""Return the optional display ordering for this model.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
float or None
|
|
88
|
+
Numeric ordering value if specified in details.json.
|
|
89
|
+
"""
|
|
90
|
+
details = self.load_details()
|
|
91
|
+
order = details.get("order")
|
|
92
|
+
if isinstance(order, (int, float)):
|
|
93
|
+
return float(order)
|
|
94
|
+
if isinstance(order, str):
|
|
95
|
+
try:
|
|
96
|
+
return float(order)
|
|
97
|
+
except ValueError:
|
|
98
|
+
return None
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
def supports_task(self, task: str) -> bool:
|
|
102
|
+
"""Return whether the model supports a given task.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
task : str
|
|
107
|
+
Task name, such as "nuclear" or "cytoplasmic".
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
bool
|
|
112
|
+
True if the task is supported.
|
|
113
|
+
"""
|
|
114
|
+
details = self.load_details()
|
|
115
|
+
tasks = details.get("tasks", {})
|
|
116
|
+
task_info = tasks.get(task, {})
|
|
117
|
+
return bool(task_info.get("supported", False))
|
|
118
|
+
|
|
119
|
+
def cytoplasmic_input_modes(self) -> list[str]:
|
|
120
|
+
"""Return supported input modes for cytoplasmic segmentation.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
list[str]
|
|
125
|
+
Input modes, e.g., "cytoplasmic" or "nuclear+cytoplasmic".
|
|
126
|
+
"""
|
|
127
|
+
details = self.load_details()
|
|
128
|
+
tasks = details.get("tasks", {})
|
|
129
|
+
task_info = tasks.get("cytoplasmic", {})
|
|
130
|
+
modes = task_info.get("input_modes", [])
|
|
131
|
+
if isinstance(modes, list):
|
|
132
|
+
return modes
|
|
133
|
+
return []
|
|
134
|
+
|
|
135
|
+
def cytoplasmic_nuclear_optional(self) -> bool:
|
|
136
|
+
"""Return whether the nuclear channel is optional for cytoplasmic mode.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
bool
|
|
141
|
+
True when the nuclear channel is optional.
|
|
142
|
+
"""
|
|
143
|
+
details = self.load_details()
|
|
144
|
+
tasks = details.get("tasks", {})
|
|
145
|
+
task_info = tasks.get("cytoplasmic", {})
|
|
146
|
+
return bool(task_info.get("nuclear_channel_optional", False))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cpsam",
|
|
3
|
+
"description": "Cellpose SAM model",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"order": 3,
|
|
6
|
+
"tasks": {
|
|
7
|
+
"nuclear": {
|
|
8
|
+
"supported": true
|
|
9
|
+
},
|
|
10
|
+
"cytoplasmic": {
|
|
11
|
+
"supported": true,
|
|
12
|
+
"input_modes": ["cytoplasmic", "nuclear+cytoplasmic"],
|
|
13
|
+
"nuclear_channel_optional": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"settings": [
|
|
17
|
+
{
|
|
18
|
+
"key": "diameter",
|
|
19
|
+
"label": "Diameter",
|
|
20
|
+
"type": "float",
|
|
21
|
+
"decimals": 1,
|
|
22
|
+
"min": 0.1,
|
|
23
|
+
"max": 1000.0,
|
|
24
|
+
"default": 30.0
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"key": "flow_threshold",
|
|
28
|
+
"label": "Flow threshold",
|
|
29
|
+
"type": "float",
|
|
30
|
+
"decimals": 1,
|
|
31
|
+
"min": 0.0,
|
|
32
|
+
"max": 2.0,
|
|
33
|
+
"default": 0.4
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"key": "cellprob_threshold",
|
|
37
|
+
"label": "Cellprob threshold",
|
|
38
|
+
"type": "float",
|
|
39
|
+
"decimals": 1,
|
|
40
|
+
"min": -6.0,
|
|
41
|
+
"max": 6.0,
|
|
42
|
+
"default": 0.0
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"key": "n_iterations",
|
|
46
|
+
"label": "Number of iterations",
|
|
47
|
+
"type": "int",
|
|
48
|
+
"min": 0,
|
|
49
|
+
"max": 9999,
|
|
50
|
+
"default": 0
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"key": "use_3d",
|
|
54
|
+
"label": "3D",
|
|
55
|
+
"type": "bool",
|
|
56
|
+
"default": false
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"key": "normalize",
|
|
60
|
+
"label": "Normalize",
|
|
61
|
+
"type": "bool",
|
|
62
|
+
"default": true
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""CPSAM segmentation model implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from senoquant.utils import layer_data_asarray
|
|
9
|
+
from ..hf import DEFAULT_REPO_ID, ensure_hf_model
|
|
10
|
+
from ..base import SenoQuantSegmentationModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CPSAMModel(SenoQuantSegmentationModel):
|
|
14
|
+
"""CPSAM segmentation model implementation."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, models_root=None) -> None:
|
|
17
|
+
"""Initialize the CPSAM model wrapper."""
|
|
18
|
+
super().__init__("cpsam", models_root=models_root)
|
|
19
|
+
from cellpose.models import CellposeModel
|
|
20
|
+
|
|
21
|
+
model_path = Path(self.model_dir) / "cpsam"
|
|
22
|
+
if not model_path.exists():
|
|
23
|
+
try:
|
|
24
|
+
model_path = ensure_hf_model(
|
|
25
|
+
"cpsam",
|
|
26
|
+
self.model_dir,
|
|
27
|
+
repo_id=DEFAULT_REPO_ID,
|
|
28
|
+
)
|
|
29
|
+
except RuntimeError:
|
|
30
|
+
pass
|
|
31
|
+
# Always request GPU; Cellpose will fall back if unavailable.
|
|
32
|
+
self._model = CellposeModel(gpu=True, pretrained_model=str(model_path))
|
|
33
|
+
|
|
34
|
+
def run(self, **kwargs) -> dict:
|
|
35
|
+
"""Run CPSAM using the Cellpose API.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
**kwargs
|
|
40
|
+
task : str
|
|
41
|
+
Segmentation task ("nuclear" or "cytoplasmic").
|
|
42
|
+
layer : napari.layers.Image or None
|
|
43
|
+
Nuclear image layer for nuclear task.
|
|
44
|
+
cytoplasmic_layer : napari.layers.Image or None
|
|
45
|
+
Cytoplasmic image layer for cytoplasmic task.
|
|
46
|
+
nuclear_layer : napari.layers.Image or None
|
|
47
|
+
Nuclear image layer for cytoplasmic task.
|
|
48
|
+
settings : dict
|
|
49
|
+
Model settings keyed by the details.json schema.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
dict
|
|
54
|
+
Dictionary containing masks, flows, and styles from Cellpose.
|
|
55
|
+
"""
|
|
56
|
+
task = kwargs.get("task")
|
|
57
|
+
settings = kwargs.get("settings", {})
|
|
58
|
+
|
|
59
|
+
do_3d = bool(settings.get("use_3d", False))
|
|
60
|
+
normalize = bool(settings.get("normalize", True))
|
|
61
|
+
diameter = settings.get("diameter")
|
|
62
|
+
flow_threshold = settings.get("flow_threshold", 0.4)
|
|
63
|
+
cellprob_threshold = settings.get("cellprob_threshold", 0.0)
|
|
64
|
+
n_iterations = settings.get("n_iterations", 0)
|
|
65
|
+
|
|
66
|
+
if task == "nuclear":
|
|
67
|
+
layer = kwargs.get("layer")
|
|
68
|
+
image = self._extract_layer_data(layer, required=True)
|
|
69
|
+
input_data = self._prepare_input(image)
|
|
70
|
+
elif task == "cytoplasmic":
|
|
71
|
+
cyto_layer = kwargs.get("cytoplasmic_layer")
|
|
72
|
+
nuclear_layer = kwargs.get("nuclear_layer")
|
|
73
|
+
cyto_image = self._extract_layer_data(cyto_layer, required=True)
|
|
74
|
+
nuclear_image = self._extract_layer_data(
|
|
75
|
+
nuclear_layer, required=False
|
|
76
|
+
)
|
|
77
|
+
if nuclear_image is None:
|
|
78
|
+
input_data = self._prepare_input(cyto_image)
|
|
79
|
+
else:
|
|
80
|
+
input_data = self._prepare_input(
|
|
81
|
+
nuclear_image,
|
|
82
|
+
cyto_image,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
raise ValueError("Unknown task for CPSAM.")
|
|
86
|
+
|
|
87
|
+
masks, flows, styles = self._model.eval(
|
|
88
|
+
input_data,
|
|
89
|
+
normalize=normalize,
|
|
90
|
+
diameter=diameter,
|
|
91
|
+
flow_threshold=flow_threshold,
|
|
92
|
+
cellprob_threshold=cellprob_threshold,
|
|
93
|
+
do_3D=do_3d,
|
|
94
|
+
z_axis=0 if do_3d else None,
|
|
95
|
+
niter=n_iterations,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return {"masks": masks, "flows": flows, "styles": styles}
|
|
99
|
+
|
|
100
|
+
def _extract_layer_data(self, layer, required: bool) -> np.ndarray | None:
|
|
101
|
+
"""Return numpy data for the given napari layer.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
layer : object or None
|
|
106
|
+
Napari layer to convert.
|
|
107
|
+
required : bool
|
|
108
|
+
Whether a missing layer should raise an error.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
numpy.ndarray or None
|
|
113
|
+
Layer data or None if not required and missing.
|
|
114
|
+
"""
|
|
115
|
+
if layer is None:
|
|
116
|
+
if required:
|
|
117
|
+
raise ValueError("Layer is required for CPSAM.")
|
|
118
|
+
return None
|
|
119
|
+
return layer_data_asarray(layer)
|
|
120
|
+
|
|
121
|
+
def _prepare_input(
|
|
122
|
+
self,
|
|
123
|
+
nuclear: np.ndarray,
|
|
124
|
+
cytoplasmic: np.ndarray | None = None,
|
|
125
|
+
) -> np.ndarray:
|
|
126
|
+
"""Prepare CPSAM input as YX, ZYX, CYX, or ZCYX.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
nuclear : numpy.ndarray
|
|
131
|
+
Nuclear image data.
|
|
132
|
+
cytoplasmic : numpy.ndarray or None
|
|
133
|
+
Cytoplasmic image data, when provided.
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
numpy.ndarray
|
|
138
|
+
Input array suitable for Cellpose eval.
|
|
139
|
+
"""
|
|
140
|
+
if nuclear.ndim not in (2, 3):
|
|
141
|
+
raise ValueError("Input image must be 2D or 3D.")
|
|
142
|
+
if cytoplasmic is not None and cytoplasmic.shape != nuclear.shape:
|
|
143
|
+
raise ValueError("Nuclear and cytoplasmic images must match in shape.")
|
|
144
|
+
|
|
145
|
+
if cytoplasmic is None:
|
|
146
|
+
return nuclear.astype(np.float32)
|
|
147
|
+
|
|
148
|
+
axis = 0 if nuclear.ndim == 2 else 1
|
|
149
|
+
stacked = np.stack([nuclear, cytoplasmic], axis=axis)
|
|
150
|
+
return stacked.astype(np.float32)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "default_2d",
|
|
3
|
+
"description": "Fine-tuned StarDist 2D model for nuclear segmentation.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"order": 1,
|
|
6
|
+
"tasks": {
|
|
7
|
+
"nuclear": {
|
|
8
|
+
"supported": true
|
|
9
|
+
},
|
|
10
|
+
"cytoplasmic": {
|
|
11
|
+
"supported": false
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"settings": [
|
|
15
|
+
{
|
|
16
|
+
"key": "object_diameter_px",
|
|
17
|
+
"label": "Object diameter (px)",
|
|
18
|
+
"type": "float",
|
|
19
|
+
"decimals": 1,
|
|
20
|
+
"min": 1.0,
|
|
21
|
+
"max": 500.0,
|
|
22
|
+
"default": 30.0
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"key": "prob_thresh",
|
|
26
|
+
"label": "Prob threshold",
|
|
27
|
+
"type": "float",
|
|
28
|
+
"decimals": 3,
|
|
29
|
+
"min": 0.0,
|
|
30
|
+
"max": 1.0,
|
|
31
|
+
"default": 0.496187
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"key": "nms_thresh",
|
|
35
|
+
"label": "NMS threshold",
|
|
36
|
+
"type": "float",
|
|
37
|
+
"decimals": 3,
|
|
38
|
+
"min": 0.0,
|
|
39
|
+
"max": 1.0,
|
|
40
|
+
"default": 0.3
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"key": "normalize",
|
|
44
|
+
"label": "Normalize",
|
|
45
|
+
"type": "bool",
|
|
46
|
+
"default": true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"key": "pmin",
|
|
50
|
+
"label": "Percentile min",
|
|
51
|
+
"type": "float",
|
|
52
|
+
"decimals": 1,
|
|
53
|
+
"min": 0.0,
|
|
54
|
+
"max": 100.0,
|
|
55
|
+
"default": 1.0,
|
|
56
|
+
"enabled_by": "normalize"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"key": "pmax",
|
|
60
|
+
"label": "Percentile max",
|
|
61
|
+
"type": "float",
|
|
62
|
+
"decimals": 1,
|
|
63
|
+
"min": 0.0,
|
|
64
|
+
"max": 100.0,
|
|
65
|
+
"default": 99.8,
|
|
66
|
+
"enabled_by": "normalize"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|