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.
Files changed (148) hide show
  1. senoquant/__init__.py +6 -0
  2. senoquant/_reader.py +7 -0
  3. senoquant/_widget.py +33 -0
  4. senoquant/napari.yaml +83 -0
  5. senoquant/reader/__init__.py +5 -0
  6. senoquant/reader/core.py +369 -0
  7. senoquant/tabs/__init__.py +15 -0
  8. senoquant/tabs/batch/__init__.py +10 -0
  9. senoquant/tabs/batch/backend.py +641 -0
  10. senoquant/tabs/batch/config.py +270 -0
  11. senoquant/tabs/batch/frontend.py +1283 -0
  12. senoquant/tabs/batch/io.py +326 -0
  13. senoquant/tabs/batch/layers.py +86 -0
  14. senoquant/tabs/quantification/__init__.py +1 -0
  15. senoquant/tabs/quantification/backend.py +228 -0
  16. senoquant/tabs/quantification/features/__init__.py +80 -0
  17. senoquant/tabs/quantification/features/base.py +142 -0
  18. senoquant/tabs/quantification/features/marker/__init__.py +5 -0
  19. senoquant/tabs/quantification/features/marker/config.py +69 -0
  20. senoquant/tabs/quantification/features/marker/dialog.py +437 -0
  21. senoquant/tabs/quantification/features/marker/export.py +879 -0
  22. senoquant/tabs/quantification/features/marker/feature.py +119 -0
  23. senoquant/tabs/quantification/features/marker/morphology.py +285 -0
  24. senoquant/tabs/quantification/features/marker/rows.py +654 -0
  25. senoquant/tabs/quantification/features/marker/thresholding.py +46 -0
  26. senoquant/tabs/quantification/features/roi.py +346 -0
  27. senoquant/tabs/quantification/features/spots/__init__.py +5 -0
  28. senoquant/tabs/quantification/features/spots/config.py +62 -0
  29. senoquant/tabs/quantification/features/spots/dialog.py +477 -0
  30. senoquant/tabs/quantification/features/spots/export.py +1292 -0
  31. senoquant/tabs/quantification/features/spots/feature.py +112 -0
  32. senoquant/tabs/quantification/features/spots/morphology.py +279 -0
  33. senoquant/tabs/quantification/features/spots/rows.py +241 -0
  34. senoquant/tabs/quantification/frontend.py +815 -0
  35. senoquant/tabs/segmentation/__init__.py +1 -0
  36. senoquant/tabs/segmentation/backend.py +131 -0
  37. senoquant/tabs/segmentation/frontend.py +1009 -0
  38. senoquant/tabs/segmentation/models/__init__.py +5 -0
  39. senoquant/tabs/segmentation/models/base.py +146 -0
  40. senoquant/tabs/segmentation/models/cpsam/details.json +65 -0
  41. senoquant/tabs/segmentation/models/cpsam/model.py +150 -0
  42. senoquant/tabs/segmentation/models/default_2d/details.json +69 -0
  43. senoquant/tabs/segmentation/models/default_2d/model.py +664 -0
  44. senoquant/tabs/segmentation/models/default_3d/details.json +69 -0
  45. senoquant/tabs/segmentation/models/default_3d/model.py +682 -0
  46. senoquant/tabs/segmentation/models/hf.py +71 -0
  47. senoquant/tabs/segmentation/models/nuclear_dilation/__init__.py +1 -0
  48. senoquant/tabs/segmentation/models/nuclear_dilation/details.json +26 -0
  49. senoquant/tabs/segmentation/models/nuclear_dilation/model.py +96 -0
  50. senoquant/tabs/segmentation/models/perinuclear_rings/__init__.py +1 -0
  51. senoquant/tabs/segmentation/models/perinuclear_rings/details.json +34 -0
  52. senoquant/tabs/segmentation/models/perinuclear_rings/model.py +132 -0
  53. senoquant/tabs/segmentation/stardist_onnx_utils/__init__.py +2 -0
  54. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/__init__.py +3 -0
  55. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/__init__.py +6 -0
  56. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/generate.py +470 -0
  57. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/prepare.py +273 -0
  58. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/rawdata.py +112 -0
  59. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/transform.py +384 -0
  60. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/__init__.py +0 -0
  61. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/blocks.py +184 -0
  62. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/losses.py +79 -0
  63. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/nets.py +165 -0
  64. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/predict.py +467 -0
  65. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/probability.py +67 -0
  66. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/train.py +148 -0
  67. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/io/__init__.py +163 -0
  68. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/__init__.py +52 -0
  69. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/base_model.py +329 -0
  70. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_isotropic.py +160 -0
  71. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_projection.py +178 -0
  72. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_standard.py +446 -0
  73. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_upsampling.py +54 -0
  74. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/config.py +254 -0
  75. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/pretrained.py +119 -0
  76. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/scripts/__init__.py +0 -0
  77. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/scripts/care_predict.py +180 -0
  78. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/__init__.py +5 -0
  79. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/plot_utils.py +159 -0
  80. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/six.py +18 -0
  81. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/tf.py +644 -0
  82. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/utils.py +272 -0
  83. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/version.py +1 -0
  84. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/docs/source/conf.py +368 -0
  85. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/setup.py +68 -0
  86. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_datagen.py +169 -0
  87. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_models.py +462 -0
  88. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_utils.py +166 -0
  89. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tools/create_zip_contents.py +34 -0
  90. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/__init__.py +30 -0
  91. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/big.py +624 -0
  92. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/bioimageio_utils.py +494 -0
  93. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/data/__init__.py +39 -0
  94. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/__init__.py +10 -0
  95. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/geom2d.py +215 -0
  96. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/geom3d.py +349 -0
  97. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/matching.py +483 -0
  98. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/__init__.py +28 -0
  99. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/base.py +1217 -0
  100. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/model2d.py +594 -0
  101. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/model3d.py +696 -0
  102. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/nms.py +384 -0
  103. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/__init__.py +2 -0
  104. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/plot.py +74 -0
  105. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/render.py +298 -0
  106. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/rays3d.py +373 -0
  107. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/sample_patches.py +65 -0
  108. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/__init__.py +0 -0
  109. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/predict2d.py +90 -0
  110. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/predict3d.py +93 -0
  111. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/utils.py +408 -0
  112. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/version.py +1 -0
  113. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/__init__.py +45 -0
  114. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/__init__.py +17 -0
  115. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/cli.py +55 -0
  116. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/core.py +285 -0
  117. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/__init__.py +15 -0
  118. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/cli.py +36 -0
  119. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/divisibility.py +193 -0
  120. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/probe.py +100 -0
  121. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/receptive_field.py +182 -0
  122. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/rf_cli.py +48 -0
  123. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/valid_sizes.py +278 -0
  124. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/post/__init__.py +8 -0
  125. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/post/core.py +157 -0
  126. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/pre/__init__.py +17 -0
  127. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/pre/core.py +226 -0
  128. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/predict/__init__.py +5 -0
  129. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/predict/core.py +401 -0
  130. senoquant/tabs/settings/__init__.py +1 -0
  131. senoquant/tabs/settings/backend.py +29 -0
  132. senoquant/tabs/settings/frontend.py +19 -0
  133. senoquant/tabs/spots/__init__.py +1 -0
  134. senoquant/tabs/spots/backend.py +139 -0
  135. senoquant/tabs/spots/frontend.py +800 -0
  136. senoquant/tabs/spots/models/__init__.py +5 -0
  137. senoquant/tabs/spots/models/base.py +94 -0
  138. senoquant/tabs/spots/models/rmp/details.json +61 -0
  139. senoquant/tabs/spots/models/rmp/model.py +499 -0
  140. senoquant/tabs/spots/models/udwt/details.json +103 -0
  141. senoquant/tabs/spots/models/udwt/model.py +482 -0
  142. senoquant/utils.py +25 -0
  143. senoquant-1.0.0b1.dist-info/METADATA +193 -0
  144. senoquant-1.0.0b1.dist-info/RECORD +148 -0
  145. senoquant-1.0.0b1.dist-info/WHEEL +5 -0
  146. senoquant-1.0.0b1.dist-info/entry_points.txt +2 -0
  147. senoquant-1.0.0b1.dist-info/licenses/LICENSE +28 -0
  148. senoquant-1.0.0b1.dist-info/top_level.txt +1 -0
senoquant/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """SenoQuant napari plugin package."""
2
+
3
+ from ._widget import SenoQuantWidget
4
+
5
+ __version__ = "1.0.0b1"
6
+ __all__ = ["SenoQuantWidget"]
senoquant/_reader.py ADDED
@@ -0,0 +1,7 @@
1
+ """Napari reader entrypoint for SenoQuant."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from senoquant.reader.core import get_reader
6
+
7
+ __all__ = ["get_reader"]
senoquant/_widget.py ADDED
@@ -0,0 +1,33 @@
1
+ """Example QtPy widget for napari."""
2
+
3
+ from qtpy.QtWidgets import QTabWidget, QVBoxLayout, QWidget
4
+
5
+ from .tabs import BatchTab, QuantificationTab, SegmentationTab, SettingsTab, SpotsTab
6
+ from .tabs.settings.backend import SettingsBackend
7
+
8
+
9
+ class SenoQuantWidget(QWidget):
10
+ """Main SenoQuant widget with tabbed UI."""
11
+
12
+ def __init__(self, napari_viewer):
13
+ super().__init__()
14
+ self._viewer = napari_viewer
15
+ self._settings_backend = SettingsBackend()
16
+
17
+ layout = QVBoxLayout()
18
+
19
+ tabs = QTabWidget()
20
+ tabs.addTab(
21
+ SegmentationTab(
22
+ napari_viewer=napari_viewer,
23
+ settings_backend=self._settings_backend,
24
+ ),
25
+ "Segmentation",
26
+ )
27
+ tabs.addTab(SpotsTab(napari_viewer=napari_viewer), "Spots")
28
+ tabs.addTab(QuantificationTab(napari_viewer=napari_viewer), "Quantification")
29
+ tabs.addTab(BatchTab(napari_viewer=napari_viewer), "Batch")
30
+ tabs.addTab(SettingsTab(backend=self._settings_backend), "Settings")
31
+
32
+ layout.addWidget(tabs)
33
+ self.setLayout(layout)
senoquant/napari.yaml ADDED
@@ -0,0 +1,83 @@
1
+ name: senoquant
2
+ contributions:
3
+ commands:
4
+ - id: senoquant.make_widget
5
+ python_name: senoquant._widget:SenoQuantWidget
6
+ title: SenoQuant
7
+ - id: senoquant.reader
8
+ python_name: senoquant._reader:get_reader
9
+ title: SenoQuant reader
10
+ widgets:
11
+ - command: senoquant.make_widget
12
+ display_name: SenoQuant
13
+ readers:
14
+ - command: senoquant.reader
15
+ filename_patterns:
16
+ - "*.czi"
17
+ - "*.dv"
18
+ - "*.r3d"
19
+ - "*.264"
20
+ - "*.265"
21
+ - "*.3fr"
22
+ - "*.3g2"
23
+ - "*.a64"
24
+ - "*.imt"
25
+ - "*.mcidas"
26
+ - "*.pcx"
27
+ - "*.spider"
28
+ - "*.xvthumb"
29
+ - "*.adp"
30
+ - "*.amr"
31
+ - "*.amv"
32
+ - "*.apng"
33
+ - "*.arw"
34
+ - "*.asf"
35
+ - "*.avc"
36
+ - "*.avi"
37
+ - "*.avs"
38
+ - "*.avs2"
39
+ - "*.bay"
40
+ - "*.bif"
41
+ - "*.bmp"
42
+ - "*.cdg"
43
+ - "*.cgi"
44
+ - "*.cif"
45
+ - "*.ct"
46
+ - "*.dcr"
47
+ - "*.dib"
48
+ - "*.dip"
49
+ - "*.dng"
50
+ - "*.dnxhd"
51
+ - "*.dvd"
52
+ - "*.erf"
53
+ - "*.exr"
54
+ - "*.fff"
55
+ - "*.gif"
56
+ - "*.icb"
57
+ - "*.if"
58
+ - "*.iiq"
59
+ - "*.ism"
60
+ - "*.jif"
61
+ - "*.jfif"
62
+ - "*.jng"
63
+ - "*.jp2"
64
+ - "*.jpg"
65
+ - "*.mov"
66
+ - "*.mp4"
67
+ - "*.mpo"
68
+ - "*.msp"
69
+ - "*.pdf"
70
+ - "*.png"
71
+ - "*.ppm"
72
+ - "*.ps"
73
+ - "*.zif"
74
+ - "*.lif"
75
+ - "*.nd2"
76
+ - "*.ome.tiff"
77
+ - "*.tiff"
78
+ - "*.ome.tif"
79
+ - "*.tif"
80
+ - "*.zarr"
81
+ - "*.sldy"
82
+ - "*.dir"
83
+ accepts_directories: false
@@ -0,0 +1,5 @@
1
+ """Reader package for SenoQuant."""
2
+
3
+ from .core import get_reader
4
+
5
+ __all__ = ["get_reader"]
@@ -0,0 +1,369 @@
1
+ """Core BioIO reader implementation for SenoQuant."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import itertools
6
+ from pathlib import Path
7
+ from typing import Callable, Iterable
8
+
9
+ try:
10
+ from bioio_base.exceptions import UnsupportedFileFormatError
11
+ except Exception: # pragma: no cover - optional dependency
12
+ UnsupportedFileFormatError = Exception
13
+
14
+
15
+ def get_reader(path: str | list[str]) -> Callable | None:
16
+ """Return a reader callable for the given path.
17
+
18
+ Parameters
19
+ ----------
20
+ path : str or list of str
21
+ Path(s) selected in the napari reader dialog.
22
+
23
+ Returns
24
+ -------
25
+ callable or None
26
+ Reader callable that returns napari layer data, or ``None`` if the
27
+ path is not supported.
28
+
29
+ Notes
30
+ -----
31
+ This uses ``bioio.BioImage.determine_plugin`` to ensure the file can be
32
+ handled by BioIO. If the file is unsupported or BioIO is unavailable,
33
+ ``None`` is returned so napari can try other readers.
34
+ """
35
+ if isinstance(path, (list, tuple)):
36
+ if not path:
37
+ return None
38
+ path = path[0]
39
+ if not isinstance(path, str) or not path:
40
+ return None
41
+ if not Path(path).is_file():
42
+ return None
43
+ try:
44
+ import bioio
45
+ except ImportError:
46
+ return None
47
+ if not hasattr(bioio.BioImage, "determine_plugin"):
48
+ return None
49
+ try:
50
+ plugin = bioio.BioImage.determine_plugin(path)
51
+ except (
52
+ AttributeError,
53
+ ImportError,
54
+ ValueError,
55
+ RuntimeError,
56
+ FileNotFoundError,
57
+ OSError,
58
+ UnsupportedFileFormatError,
59
+ Exception,
60
+ ):
61
+ return None
62
+ if plugin is None:
63
+ return None
64
+ return _read_senoquant
65
+
66
+
67
+ def _read_senoquant(path: str) -> Iterable[tuple]:
68
+ """Read image data using BioIO and return napari layer tuples.
69
+
70
+ Parameters
71
+ ----------
72
+ path : str
73
+ File path to read.
74
+
75
+ Returns
76
+ -------
77
+ iterable of tuple
78
+ Napari layer tuples of the form ``(data, metadata, layer_type)``.
79
+
80
+ Notes
81
+ -----
82
+ When multiple scenes are present, each scene becomes a separate layer
83
+ with metadata describing the scene index and name.
84
+ """
85
+ try:
86
+ from bioio import BioImage
87
+ except Exception as exc: # pragma: no cover - dependency dependent
88
+ raise ImportError(
89
+ "BioIO is required for the SenoQuant reader."
90
+ ) from exc
91
+
92
+ base_name = Path(path).name
93
+ image = _open_bioimage(path)
94
+ layers: list[tuple] = []
95
+ colormap_cycle = _colormap_cycle()
96
+ scenes = image.scenes
97
+
98
+ for scene_idx, scene_id in enumerate(scenes):
99
+ image.set_scene(scene_id)
100
+ layers.extend(
101
+ _iter_channel_layers(
102
+ image,
103
+ base_name=base_name,
104
+ scene_id=scene_id,
105
+ scene_idx=scene_idx,
106
+ total_scenes=len(scenes),
107
+ path=path,
108
+ colormap_cycle=colormap_cycle,
109
+ )
110
+ )
111
+
112
+ return layers
113
+
114
+
115
+ def _open_bioimage(path: str):
116
+ """Open a BioImage using bioio.
117
+
118
+ Parameters
119
+ ----------
120
+ path : str
121
+ File path to read.
122
+
123
+ Returns
124
+ -------
125
+ bioio.BioImage
126
+ BioIO image instance for the requested file.
127
+ """
128
+ import bioio
129
+
130
+ plugin = None
131
+ try:
132
+ plugin = bioio.BioImage.determine_plugin(path)
133
+ except Exception:
134
+ plugin = None
135
+
136
+ if _should_force_tifffile(plugin, path):
137
+ image = _try_bioimage_readers(
138
+ bioio,
139
+ path,
140
+ reader_names=("bioio_tifffile", "bioio_ome_tiff"),
141
+ )
142
+ if image is not None:
143
+ return image
144
+
145
+ return bioio.BioImage(path)
146
+
147
+
148
+ def _should_force_tifffile(plugin, path: str) -> bool:
149
+ """Return True when tiff_glob should be bypassed for single-file TIFFs."""
150
+ if "*" in path or "?" in path:
151
+ return False
152
+ if not path.lower().endswith((".tif", ".tiff")):
153
+ return False
154
+ names = set()
155
+ if isinstance(plugin, str):
156
+ names.add(plugin)
157
+ else:
158
+ for attr in ("name", "value", "__name__", "__module__"):
159
+ value = getattr(plugin, attr, None)
160
+ if value:
161
+ names.add(str(value))
162
+ entrypoint = getattr(plugin, "entrypoint", None)
163
+ if entrypoint is not None:
164
+ for attr in ("name", "value", "__name__", "__module__"):
165
+ value = getattr(entrypoint, attr, None)
166
+ if value:
167
+ names.add(str(value))
168
+ return any("tiff_glob" in name or "tiff-glob" in name for name in names)
169
+
170
+
171
+ def _try_bioimage_readers(bioio, path: str, reader_names: tuple[str, ...]):
172
+ """Try opening a BioImage with explicit reader plugins."""
173
+ import importlib
174
+
175
+ for reader_name in reader_names:
176
+ module = None
177
+ try:
178
+ module = importlib.import_module(reader_name)
179
+ except Exception:
180
+ module = None
181
+ if module is not None:
182
+ reader_cls = getattr(module, "Reader", None)
183
+ if reader_cls is not None:
184
+ try:
185
+ return bioio.BioImage(path, reader=reader_cls)
186
+ except Exception:
187
+ continue
188
+ return None
189
+
190
+
191
+ def _colormap_cycle() -> Iterable[str]:
192
+ """Return an iterator cycling through approved colormap names.
193
+
194
+ Returns
195
+ -------
196
+ iterable of str
197
+ Cycle of colormap names to assign to reader layers.
198
+ """
199
+ names = [
200
+ "blue",
201
+ "bop blue",
202
+ "bop orange",
203
+ "bop purple",
204
+ "cyan",
205
+ # "fire",
206
+ # "gist_earth",
207
+ # "gray",
208
+ # "gray_r",
209
+ "green",
210
+ # "HiLo",
211
+ # "hsv",
212
+ # "I Blue",
213
+ # "I Bordeaux",
214
+ # "I Forest",
215
+ # "I Orange",
216
+ # "I Purple",
217
+ # "ice",
218
+ # "inferno",
219
+ # "magenta",
220
+ # "magma",
221
+ # "nan",
222
+ # "PiYG",
223
+ # "plasma",
224
+ "red",
225
+ # "turbo",
226
+ # "twilight",
227
+ # "twilight_shifted",
228
+ # "viridis",
229
+ "yellow",
230
+ ]
231
+ return itertools.cycle(names)
232
+
233
+
234
+ def _physical_pixel_sizes(image) -> dict[str, float | None]:
235
+ """Return physical pixel sizes (um) for the active scene."""
236
+ try:
237
+ sizes = image.physical_pixel_sizes
238
+ except Exception:
239
+ return {"Z": None, "Y": None, "X": None}
240
+ return {
241
+ "Z": sizes.Z,
242
+ "Y": sizes.Y,
243
+ "X": sizes.X,
244
+ }
245
+
246
+
247
+ def _axes_present(image) -> set[str]:
248
+ """Return the set of axis labels present in the BioIO image."""
249
+ dims = getattr(image, "dims", None)
250
+ if dims is None:
251
+ return set()
252
+ if isinstance(dims, str):
253
+ return set(dims)
254
+ order = getattr(dims, "order", None)
255
+ if isinstance(order, str):
256
+ return set(order)
257
+ axes = getattr(dims, "axes", None)
258
+ if not axes:
259
+ return set()
260
+ result: set[str] = set()
261
+ for axis in axes:
262
+ if isinstance(axis, str):
263
+ result.add(axis)
264
+ continue
265
+ name = (
266
+ getattr(axis, "name", None)
267
+ or getattr(axis, "value", None)
268
+ or getattr(axis, "axis", None)
269
+ )
270
+ if name:
271
+ result.add(str(name))
272
+ return result
273
+
274
+
275
+ def _iter_channel_layers(
276
+ image,
277
+ *,
278
+ base_name: str,
279
+ scene_id: str,
280
+ scene_idx: int,
281
+ total_scenes: int,
282
+ path: str,
283
+ colormap_cycle: Iterable[str] | None = None,
284
+ ) -> list[tuple]:
285
+ """Split BioIO data into single-channel (Z)YX napari layers.
286
+
287
+ Parameters
288
+ ----------
289
+ image : bioio.BioImage
290
+ BioIO image with the current scene selected.
291
+ base_name : str
292
+ Base filename for layer naming.
293
+ scene_id : str
294
+ Scene identifier string.
295
+ scene_idx : int
296
+ Scene index within the file.
297
+ total_scenes : int
298
+ Total number of scenes in the file.
299
+ path : str
300
+ Original image path to store in the metadata.
301
+ colormap_cycle : iterable of str or None, optional
302
+ Iterator that provides colormap names to assign to each layer.
303
+
304
+ Returns
305
+ -------
306
+ list of tuple
307
+ Napari layer tuples for each channel.
308
+ """
309
+ dims = getattr(image, "dims", None)
310
+ axes_present = _axes_present(image)
311
+ t_size = getattr(dims, "T", 1) if "T" in axes_present else 1
312
+ c_size = getattr(dims, "C", 1) if "C" in axes_present else 1
313
+ z_size = getattr(dims, "Z", 1) if "Z" in axes_present else 1
314
+
315
+ scene_name = scene_id or f"Scene {scene_idx}"
316
+ scene_meta = {
317
+ "scene_id": scene_id,
318
+ "scene_index": scene_idx,
319
+ "scene_name": scene_name,
320
+ "total_scenes": total_scenes,
321
+ }
322
+ layers: list[tuple] = []
323
+ t_index = 0
324
+
325
+ if c_size > 1:
326
+ order = "CZYX" if z_size > 1 else "CYX"
327
+ kwargs = {}
328
+ if "T" in axes_present and "T" not in order:
329
+ kwargs["T"] = t_index
330
+ if "Z" in axes_present and "Z" not in order:
331
+ kwargs["Z"] = 0
332
+ data = image.get_image_data(order, **kwargs)
333
+ channel_iter = range(c_size)
334
+ else:
335
+ order = "ZYX" if z_size > 1 else "YX"
336
+ kwargs = {}
337
+ if "T" in axes_present and "T" not in order:
338
+ kwargs["T"] = t_index
339
+ if "C" in axes_present and "C" not in order:
340
+ kwargs["C"] = 0
341
+ if "Z" in axes_present and "Z" not in order:
342
+ kwargs["Z"] = 0
343
+ data = image.get_image_data(order, **kwargs)
344
+ channel_iter = [0]
345
+
346
+ for channel_index in channel_iter:
347
+ layer_data = data[channel_index] if c_size > 1 else data
348
+
349
+ layer_name = f"{base_name} - {scene_name}" if total_scenes > 1 else base_name
350
+ if c_size > 1:
351
+ layer_name = f"{layer_name} - Channel {channel_index}"
352
+
353
+ physical_sizes = _physical_pixel_sizes(image)
354
+ meta = {
355
+ "name": layer_name,
356
+ "blending": "additive",
357
+ "metadata": {
358
+ "bioio_metadata": image.metadata,
359
+ "scene_info": scene_meta,
360
+ "path": path,
361
+ "channel_index": channel_index,
362
+ "physical_pixel_sizes": physical_sizes,
363
+ },
364
+ }
365
+ if colormap_cycle is not None:
366
+ meta["colormap"] = next(colormap_cycle)
367
+ layers.append((layer_data, meta, "image"))
368
+
369
+ return layers
@@ -0,0 +1,15 @@
1
+ """Tab widgets for SenoQuant."""
2
+
3
+ from .segmentation.frontend import SegmentationTab
4
+ from .spots.frontend import SpotsTab
5
+ from .quantification.frontend import QuantificationTab
6
+ from .settings.frontend import SettingsTab
7
+ from .batch.frontend import BatchTab
8
+
9
+ __all__ = [
10
+ "SegmentationTab",
11
+ "SpotsTab",
12
+ "QuantificationTab",
13
+ "SettingsTab",
14
+ "BatchTab",
15
+ ]
@@ -0,0 +1,10 @@
1
+ """Batch tab modules.
2
+
3
+ Exports the Batch UI widget and configuration/backend helpers.
4
+ """
5
+
6
+ from .backend import BatchBackend
7
+ from .config import BatchChannelConfig, BatchJobConfig
8
+ from .frontend import BatchTab
9
+
10
+ __all__ = ["BatchBackend", "BatchChannelConfig", "BatchJobConfig", "BatchTab"]