pentachrome-plugin 0.4.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dimitrios Tsilis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,251 @@
1
+ Metadata-Version: 2.4
2
+ Name: pentachrome-plugin
3
+ Version: 0.4.0
4
+ Summary: Napari plugin for the Pentachrome histology pipeline: VSI extraction, nnUNet inference, statistics.
5
+ Author: Dimitrios Tsilis
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/dtsilis7/PentachromePipeline
8
+ Project-URL: Bug Tracker, https://github.com/dtsilis7/PentachromePipeline/issues
9
+ Keywords: napari,histology,nnunet,bioformats,segmentation
10
+ Classifier: Framework :: napari
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: napari[all]>=0.4.18
20
+ Requires-Dist: qtpy
21
+ Requires-Dist: tifffile
22
+ Requires-Dist: numpy<2
23
+ Requires-Dist: opencv-python
24
+ Requires-Dist: scipy
25
+ Requires-Dist: scikit-image
26
+ Requires-Dist: skan
27
+ Requires-Dist: imagecodecs
28
+ Dynamic: license-file
29
+
30
+ # pentachrome-plugin
31
+
32
+ Napari plugin for the Pentachrome histology pipeline. Current widgets:
33
+
34
+ - **VSI to TIFF Extractor** (Phase 1) — extract tissue-region TIFFs from VSI files.
35
+ - **nnUNet Inference** (Phase 2) — run the trained Epithelium / MultiStructure models on selected TIFF layers and load colorized masks back into the viewer.
36
+ - **Mask Statistics** (Phase 3) — per-region statistics (thickness, composition, cell densities) computed on the inference output, with CSV export.
37
+
38
+ Source and issues: https://github.com/dtsilis7/PentachromePipeline
39
+
40
+ **Requirements:** Windows, Python 3.10, napari >= 0.4.18, and a Java JDK (JDK 17 confirmed). The Java/bioformats stack and the nnUNet model weights are installed separately (see below) — they can't come from a plain `pip install`.
41
+
42
+ ## Install (Windows, PowerShell)
43
+
44
+ Requires a working Java JDK on PATH (JDK 17 confirmed working).
45
+
46
+ ### Via napari plugin manager (after publishing)
47
+
48
+ Once this package is published to PyPI it shows up in napari's **Plugins -> Install/Uninstall Plugins** (search "pentachrome"). That installs the Python package only — you still need the conda-forge Java/bioformats step and the nnUNet weights below for the extractor and inference to work.
49
+
50
+ ### Recommended: conda env
51
+
52
+ `cd` into the plugin directory first — `pip install -e .` resolves `.` relative to your current shell directory:
53
+
54
+ ```powershell
55
+ conda activate napari
56
+ cd "...\pentachrome_plugin"
57
+ conda install -c conda-forge python-javabridge python-bioformats
58
+ pip install opencv-python tifffile
59
+ pip install -e .
60
+ ```
61
+
62
+ If you would rather not `cd`, pass the absolute path explicitly:
63
+
64
+ ```powershell
65
+ pip install -e "...\pentachrome_plugin"
66
+ ```
67
+
68
+ Conda-forge ships pre-built wheels for `python-javabridge` and `python-bioformats` and avoids the MSVC + NumPy-2 compile failure that `pip install python-javabridge` hits today (the C extension references `_PyArray_Descr` fields removed in NumPy 2.0).
69
+
70
+ ### Fallback: pure pip
71
+
72
+ Only use this if conda-forge is unavailable. NumPy must be pinned below 2 *before* javabridge builds, and build isolation must be off so the build sees the pinned NumPy:
73
+
74
+ ```powershell
75
+ cd "...\pentachrome_plugin"
76
+ pip install "numpy<2"
77
+ pip install --no-build-isolation python-javabridge python-bioformats
78
+ pip install opencv-python tifffile
79
+ pip install -e .
80
+ ```
81
+
82
+ ## Launch
83
+
84
+ ```powershell
85
+ conda activate napari # or whichever env you installed into
86
+ python -m napari
87
+ ```
88
+
89
+ In napari: **Plugins -> VSI to TIFF Extractor** or **Plugins -> nnUNet Inference**.
90
+
91
+ ## nnUNet Inference (Phase 2)
92
+
93
+ Requires `nnunetv2` installed in the same environment (the `nnUNetv2_predict` CLI must be on PATH).
94
+
95
+ ```powershell
96
+ conda activate napari
97
+ pip install nnunetv2
98
+ ```
99
+
100
+ Workflow:
101
+
102
+ 1. Load TIFFs into napari (e.g. via Phase 1's auto-load checkbox, or drag-and-drop).
103
+ 2. Open **Plugins -> nnUNet Inference**.
104
+ 3. Select one or more image layers in the list.
105
+ 4. Tick **Epithelium**, **MultiStructure**, or both.
106
+ 5. Set **Output folder** (where raw + colorized masks go) and **nnUNet results** (folder containing `Dataset001_Epithelium` and `Dataset002_MultiStructure`). The results path auto-fills if `nnUNet_Training/nnUNet_results/results` is found.
107
+ 6. Pick **Device** (`cpu` or `cuda`) and click **Analyze**.
108
+
109
+ ### Speed vs quality (important on CPU)
110
+
111
+ nnUNet inference on a laptop CPU is slow because every image goes through *folds × mirror augmentations × sliding-window patches* forward passes. With defaults that can be 20+ passes per image. The widget exposes three knobs in the **Speed / quality** group:
112
+
113
+ | Knob | Default | What it does |
114
+ | --- | --- | --- |
115
+ | Epithelium folds | `Fold 0 only` | Use 1 of the 5 trained folds for Dataset001. All 5 ensembled is best quality but ~5x slower. Dataset002 only has fold 0 trained, so it's always 1 fold. |
116
+ | Disable test-time mirroring | on | Passes `--disable_tta`. Skips the 4 mirror augmentations the model normally averages over. ~4x faster, small accuracy hit. |
117
+ | Sliding-window step | `0.5` | Passes `-step_size`. Larger = fewer overlapping patches = faster but rougher tile borders. Try `0.7` for a middle ground. |
118
+
119
+ With all three defaults on a CPU laptop, one ROI tile should take a few minutes instead of 30+. Switch to `All 5 folds` + TTA on once you've moved to a GPU box.
120
+
121
+ ### Continuing from the extractor
122
+
123
+ The two widgets are linked through two small bridges, so you can run **Extract -> Analyze** in a single napari session without re-picking files:
124
+
125
+ - When the extractor auto-loads a TIFF as a viewer layer, it stashes the on-disk path on `layer.metadata['source_tiff']`. The inference widget reads that during staging and **copies the original file** into `_staging_input/` rather than re-saving the in-memory array — important for 15k x 15k tiles.
126
+ - When an extraction completes, the inference widget's **"Use last extractor output"** button pre-fills the output folder to `<extractor_output_root>/_inference`, so masks land next to the per-VSI subfolders the extractor created.
127
+
128
+ Both bridges are in-process only (see `_session.py`); they reset when napari closes.
129
+
130
+ Outputs land in:
131
+
132
+ ```
133
+ <output_folder>/
134
+ _staging_input/ # nnUNet-named (_0000.tif) copies of the selected layers
135
+ epithelium_raw/ # binary masks from Dataset001
136
+ epithelium_colored/ # RGB colorized masks (red epithelium)
137
+ multistructure_raw/ # 6-class masks from Dataset002
138
+ multistructure_colored/ # RGB colorized masks (Elastin/Collagen/Nuclei/Mucins/Membrane/Goblets)
139
+ ```
140
+
141
+ Colorized masks are added to the viewer as RGB image layers when the run finishes.
142
+
143
+ ### nnUNet inference architecture
144
+
145
+ Same subprocess pattern as Phase 1. The widget never imports torch or nnUNetv2 directly; it spawns `_inference_worker.py` which:
146
+
147
+ - sets `nnUNet_results` to the configured results dir,
148
+ - calls `nnUNetv2_predict` once per enabled model (folds 0-4 for Epithelium, fold 0 for MultiStructure, matching `run_inference.py`),
149
+ - colorizes the resulting integer masks with the palettes from `colorize_masks.py` / `compare_grid.py`,
150
+ - streams JSON-line events on stdout for the widget's progress bar and log.
151
+
152
+ ## How it works
153
+
154
+ - The widget itself never touches the JVM. When you click **Extract ROIs**, it spawns `_vsi_worker.py` as a separate Python process.
155
+ - That worker process starts the bioformats JVM, loops over the VSI files using `TileMaskStitcher` (reused from `VSI_Handler/tile_mask_stitcher.py`), writes numbered TIFFs into `<output_root>/<vsi_basename>/`, and emits JSON-line progress events on stdout.
156
+ - The widget streams those events on a background thread and updates the progress bar / log without blocking the UI.
157
+ - When the worker exits, the JVM dies with it. The next extraction batch starts a fresh JVM in a fresh process - this avoids the "JVM cannot be restarted" pitfall during a long napari session.
158
+
159
+ ## Defaults
160
+
161
+ The parameter defaults mirror `Processing_VSI_Files.py`:
162
+
163
+ | Parameter | Default |
164
+ | --- | --- |
165
+ | Series | 6 |
166
+ | Tile width / height | 15000 |
167
+ | Threshold | 50 |
168
+ | Min ROI area | 150000 |
169
+ | Merge margin | 1000 |
170
+ | Extra crop margin | 100 |
171
+
172
+ ## Layout
173
+
174
+ ```
175
+ pentachrome_plugin/
176
+ pyproject.toml
177
+ README.md
178
+ src/pentachrome_plugin/
179
+ __init__.py
180
+ napari.yaml # napari manifest
181
+ _session.py # in-process cross-widget state (extractor -> inference -> analysis)
182
+ _widget.py # VsiExtractorWidget (Phase 1)
183
+ _vsi_worker.py # VSI subprocess entrypoint
184
+ _inference_widget.py # NnUnetInferenceWidget (Phase 2)
185
+ _inference_worker.py # nnUNet subprocess entrypoint
186
+ _analysis_widget.py # AnalysisWidget (Phase 3, in-process)
187
+ ```
188
+
189
+ Phase 3 (Mask Statistics) lives alongside these and registers through `napari.yaml`.
190
+
191
+ ## Mask Statistics (Phase 3)
192
+
193
+ Pure in-process; no subprocess needed (no JVM, no torch). Reuses
194
+ `EpithelialAnalysis/Analyzers/` (`Descriptors.py`, `Thickness.py`), so the
195
+ same metrics that fed the original `region_summary.csv` show up in the
196
+ widget.
197
+
198
+ Workflow:
199
+
200
+ 1. Run Phase 2 first so `epithelium_raw/` and `multistructure_raw/` exist.
201
+ 2. Open **Plugins -> Mask Statistics**.
202
+ 3. Select one or more image layers in the list (their names must match the
203
+ mask filenames in `epithelium_raw/` / `multistructure_raw/`; if the
204
+ inference widget staged them, that's already true).
205
+ 4. Click **Use last inference output** (or browse).
206
+ 5. Tweak **Pixel size**, **Region dilation**, **Min epithelium area** if
207
+ needed (defaults match `Main.py`).
208
+ 6. Click **Analyze**.
209
+
210
+ For each detected epithelial region the widget reports:
211
+
212
+ | Column | What it is |
213
+ | --- | --- |
214
+ | Area (mm^2) | Region area after the 50 um dilation |
215
+ | Thickness mean/std (um) | Medial-axis thickness of (membrane within eroded region) U goblets U nuclei |
216
+ | Elastin / Collagen / Other % | Fraction of stained structure pixels — same definition as `compute_structure_percentages` |
217
+ | Mucin % | Mucin pixels as a fraction of the epithelium area (not of total structure pixels) |
218
+ | Nuclei / mm^2 and Goblets / mm^2 | Density per mm^2 of epithelium — goblet hyperplasia is a classic COPD readout |
219
+ | Nuclei (n), Goblets (n) | Raw counts inside the region |
220
+
221
+ A bold **(all regions)** row appended per image gives area-weighted means
222
+ of the percentages / thickness and totals for the counts. **Export CSV...**
223
+ saves the whole table (per-region rows + aggregate rows).
224
+
225
+ The elastin organization score (`ElastinAnalyzer.determine_organized_region`)
226
+ from `Main.py` is intentionally not yet exposed — it's much heavier (skan +
227
+ shapely + ROI polygons) and will land as a separate toggle.
228
+
229
+ ### Class isolation
230
+
231
+ A "Class isolation" group at the top of the widget lets you view a single
232
+ class (or a combination) without rerunning anything:
233
+
234
+ 1. Pick a source layer (the **original** TIFF — not a colorized mask).
235
+ 2. Tick one or more of **Elastin**, **Collagen**, **Nuclei**, **Mucins**,
236
+ **Cell Membrane**, **Goblets**, **Epithelium**.
237
+ 3. Click one of:
238
+ - **Show as mask** — adds a new layer that's white everywhere except the
239
+ ticked classes, colored with the same palette as the inference widget.
240
+ - **Show on original** — adds a copy of the original image with all
241
+ pixels outside the ticked classes turned white. Useful for sanity-
242
+ checking the segmentation against the stain.
243
+ 4. **Clear isolated layers** removes everything this panel added in one go.
244
+
245
+ Masks are read on demand from the inference output folder; the original
246
+ layer's pixels are taken from the viewer.
247
+
248
+ ## License
249
+
250
+ MIT. (Matches `license = {text = "MIT"}` in `pyproject.toml`. Consider adding a
251
+ top-level `LICENSE` file with the MIT text so it ships in the sdist/wheel.)
@@ -0,0 +1,222 @@
1
+ # pentachrome-plugin
2
+
3
+ Napari plugin for the Pentachrome histology pipeline. Current widgets:
4
+
5
+ - **VSI to TIFF Extractor** (Phase 1) — extract tissue-region TIFFs from VSI files.
6
+ - **nnUNet Inference** (Phase 2) — run the trained Epithelium / MultiStructure models on selected TIFF layers and load colorized masks back into the viewer.
7
+ - **Mask Statistics** (Phase 3) — per-region statistics (thickness, composition, cell densities) computed on the inference output, with CSV export.
8
+
9
+ Source and issues: https://github.com/dtsilis7/PentachromePipeline
10
+
11
+ **Requirements:** Windows, Python 3.10, napari >= 0.4.18, and a Java JDK (JDK 17 confirmed). The Java/bioformats stack and the nnUNet model weights are installed separately (see below) — they can't come from a plain `pip install`.
12
+
13
+ ## Install (Windows, PowerShell)
14
+
15
+ Requires a working Java JDK on PATH (JDK 17 confirmed working).
16
+
17
+ ### Via napari plugin manager (after publishing)
18
+
19
+ Once this package is published to PyPI it shows up in napari's **Plugins -> Install/Uninstall Plugins** (search "pentachrome"). That installs the Python package only — you still need the conda-forge Java/bioformats step and the nnUNet weights below for the extractor and inference to work.
20
+
21
+ ### Recommended: conda env
22
+
23
+ `cd` into the plugin directory first — `pip install -e .` resolves `.` relative to your current shell directory:
24
+
25
+ ```powershell
26
+ conda activate napari
27
+ cd "...\pentachrome_plugin"
28
+ conda install -c conda-forge python-javabridge python-bioformats
29
+ pip install opencv-python tifffile
30
+ pip install -e .
31
+ ```
32
+
33
+ If you would rather not `cd`, pass the absolute path explicitly:
34
+
35
+ ```powershell
36
+ pip install -e "...\pentachrome_plugin"
37
+ ```
38
+
39
+ Conda-forge ships pre-built wheels for `python-javabridge` and `python-bioformats` and avoids the MSVC + NumPy-2 compile failure that `pip install python-javabridge` hits today (the C extension references `_PyArray_Descr` fields removed in NumPy 2.0).
40
+
41
+ ### Fallback: pure pip
42
+
43
+ Only use this if conda-forge is unavailable. NumPy must be pinned below 2 *before* javabridge builds, and build isolation must be off so the build sees the pinned NumPy:
44
+
45
+ ```powershell
46
+ cd "...\pentachrome_plugin"
47
+ pip install "numpy<2"
48
+ pip install --no-build-isolation python-javabridge python-bioformats
49
+ pip install opencv-python tifffile
50
+ pip install -e .
51
+ ```
52
+
53
+ ## Launch
54
+
55
+ ```powershell
56
+ conda activate napari # or whichever env you installed into
57
+ python -m napari
58
+ ```
59
+
60
+ In napari: **Plugins -> VSI to TIFF Extractor** or **Plugins -> nnUNet Inference**.
61
+
62
+ ## nnUNet Inference (Phase 2)
63
+
64
+ Requires `nnunetv2` installed in the same environment (the `nnUNetv2_predict` CLI must be on PATH).
65
+
66
+ ```powershell
67
+ conda activate napari
68
+ pip install nnunetv2
69
+ ```
70
+
71
+ Workflow:
72
+
73
+ 1. Load TIFFs into napari (e.g. via Phase 1's auto-load checkbox, or drag-and-drop).
74
+ 2. Open **Plugins -> nnUNet Inference**.
75
+ 3. Select one or more image layers in the list.
76
+ 4. Tick **Epithelium**, **MultiStructure**, or both.
77
+ 5. Set **Output folder** (where raw + colorized masks go) and **nnUNet results** (folder containing `Dataset001_Epithelium` and `Dataset002_MultiStructure`). The results path auto-fills if `nnUNet_Training/nnUNet_results/results` is found.
78
+ 6. Pick **Device** (`cpu` or `cuda`) and click **Analyze**.
79
+
80
+ ### Speed vs quality (important on CPU)
81
+
82
+ nnUNet inference on a laptop CPU is slow because every image goes through *folds × mirror augmentations × sliding-window patches* forward passes. With defaults that can be 20+ passes per image. The widget exposes three knobs in the **Speed / quality** group:
83
+
84
+ | Knob | Default | What it does |
85
+ | --- | --- | --- |
86
+ | Epithelium folds | `Fold 0 only` | Use 1 of the 5 trained folds for Dataset001. All 5 ensembled is best quality but ~5x slower. Dataset002 only has fold 0 trained, so it's always 1 fold. |
87
+ | Disable test-time mirroring | on | Passes `--disable_tta`. Skips the 4 mirror augmentations the model normally averages over. ~4x faster, small accuracy hit. |
88
+ | Sliding-window step | `0.5` | Passes `-step_size`. Larger = fewer overlapping patches = faster but rougher tile borders. Try `0.7` for a middle ground. |
89
+
90
+ With all three defaults on a CPU laptop, one ROI tile should take a few minutes instead of 30+. Switch to `All 5 folds` + TTA on once you've moved to a GPU box.
91
+
92
+ ### Continuing from the extractor
93
+
94
+ The two widgets are linked through two small bridges, so you can run **Extract -> Analyze** in a single napari session without re-picking files:
95
+
96
+ - When the extractor auto-loads a TIFF as a viewer layer, it stashes the on-disk path on `layer.metadata['source_tiff']`. The inference widget reads that during staging and **copies the original file** into `_staging_input/` rather than re-saving the in-memory array — important for 15k x 15k tiles.
97
+ - When an extraction completes, the inference widget's **"Use last extractor output"** button pre-fills the output folder to `<extractor_output_root>/_inference`, so masks land next to the per-VSI subfolders the extractor created.
98
+
99
+ Both bridges are in-process only (see `_session.py`); they reset when napari closes.
100
+
101
+ Outputs land in:
102
+
103
+ ```
104
+ <output_folder>/
105
+ _staging_input/ # nnUNet-named (_0000.tif) copies of the selected layers
106
+ epithelium_raw/ # binary masks from Dataset001
107
+ epithelium_colored/ # RGB colorized masks (red epithelium)
108
+ multistructure_raw/ # 6-class masks from Dataset002
109
+ multistructure_colored/ # RGB colorized masks (Elastin/Collagen/Nuclei/Mucins/Membrane/Goblets)
110
+ ```
111
+
112
+ Colorized masks are added to the viewer as RGB image layers when the run finishes.
113
+
114
+ ### nnUNet inference architecture
115
+
116
+ Same subprocess pattern as Phase 1. The widget never imports torch or nnUNetv2 directly; it spawns `_inference_worker.py` which:
117
+
118
+ - sets `nnUNet_results` to the configured results dir,
119
+ - calls `nnUNetv2_predict` once per enabled model (folds 0-4 for Epithelium, fold 0 for MultiStructure, matching `run_inference.py`),
120
+ - colorizes the resulting integer masks with the palettes from `colorize_masks.py` / `compare_grid.py`,
121
+ - streams JSON-line events on stdout for the widget's progress bar and log.
122
+
123
+ ## How it works
124
+
125
+ - The widget itself never touches the JVM. When you click **Extract ROIs**, it spawns `_vsi_worker.py` as a separate Python process.
126
+ - That worker process starts the bioformats JVM, loops over the VSI files using `TileMaskStitcher` (reused from `VSI_Handler/tile_mask_stitcher.py`), writes numbered TIFFs into `<output_root>/<vsi_basename>/`, and emits JSON-line progress events on stdout.
127
+ - The widget streams those events on a background thread and updates the progress bar / log without blocking the UI.
128
+ - When the worker exits, the JVM dies with it. The next extraction batch starts a fresh JVM in a fresh process - this avoids the "JVM cannot be restarted" pitfall during a long napari session.
129
+
130
+ ## Defaults
131
+
132
+ The parameter defaults mirror `Processing_VSI_Files.py`:
133
+
134
+ | Parameter | Default |
135
+ | --- | --- |
136
+ | Series | 6 |
137
+ | Tile width / height | 15000 |
138
+ | Threshold | 50 |
139
+ | Min ROI area | 150000 |
140
+ | Merge margin | 1000 |
141
+ | Extra crop margin | 100 |
142
+
143
+ ## Layout
144
+
145
+ ```
146
+ pentachrome_plugin/
147
+ pyproject.toml
148
+ README.md
149
+ src/pentachrome_plugin/
150
+ __init__.py
151
+ napari.yaml # napari manifest
152
+ _session.py # in-process cross-widget state (extractor -> inference -> analysis)
153
+ _widget.py # VsiExtractorWidget (Phase 1)
154
+ _vsi_worker.py # VSI subprocess entrypoint
155
+ _inference_widget.py # NnUnetInferenceWidget (Phase 2)
156
+ _inference_worker.py # nnUNet subprocess entrypoint
157
+ _analysis_widget.py # AnalysisWidget (Phase 3, in-process)
158
+ ```
159
+
160
+ Phase 3 (Mask Statistics) lives alongside these and registers through `napari.yaml`.
161
+
162
+ ## Mask Statistics (Phase 3)
163
+
164
+ Pure in-process; no subprocess needed (no JVM, no torch). Reuses
165
+ `EpithelialAnalysis/Analyzers/` (`Descriptors.py`, `Thickness.py`), so the
166
+ same metrics that fed the original `region_summary.csv` show up in the
167
+ widget.
168
+
169
+ Workflow:
170
+
171
+ 1. Run Phase 2 first so `epithelium_raw/` and `multistructure_raw/` exist.
172
+ 2. Open **Plugins -> Mask Statistics**.
173
+ 3. Select one or more image layers in the list (their names must match the
174
+ mask filenames in `epithelium_raw/` / `multistructure_raw/`; if the
175
+ inference widget staged them, that's already true).
176
+ 4. Click **Use last inference output** (or browse).
177
+ 5. Tweak **Pixel size**, **Region dilation**, **Min epithelium area** if
178
+ needed (defaults match `Main.py`).
179
+ 6. Click **Analyze**.
180
+
181
+ For each detected epithelial region the widget reports:
182
+
183
+ | Column | What it is |
184
+ | --- | --- |
185
+ | Area (mm^2) | Region area after the 50 um dilation |
186
+ | Thickness mean/std (um) | Medial-axis thickness of (membrane within eroded region) U goblets U nuclei |
187
+ | Elastin / Collagen / Other % | Fraction of stained structure pixels — same definition as `compute_structure_percentages` |
188
+ | Mucin % | Mucin pixels as a fraction of the epithelium area (not of total structure pixels) |
189
+ | Nuclei / mm^2 and Goblets / mm^2 | Density per mm^2 of epithelium — goblet hyperplasia is a classic COPD readout |
190
+ | Nuclei (n), Goblets (n) | Raw counts inside the region |
191
+
192
+ A bold **(all regions)** row appended per image gives area-weighted means
193
+ of the percentages / thickness and totals for the counts. **Export CSV...**
194
+ saves the whole table (per-region rows + aggregate rows).
195
+
196
+ The elastin organization score (`ElastinAnalyzer.determine_organized_region`)
197
+ from `Main.py` is intentionally not yet exposed — it's much heavier (skan +
198
+ shapely + ROI polygons) and will land as a separate toggle.
199
+
200
+ ### Class isolation
201
+
202
+ A "Class isolation" group at the top of the widget lets you view a single
203
+ class (or a combination) without rerunning anything:
204
+
205
+ 1. Pick a source layer (the **original** TIFF — not a colorized mask).
206
+ 2. Tick one or more of **Elastin**, **Collagen**, **Nuclei**, **Mucins**,
207
+ **Cell Membrane**, **Goblets**, **Epithelium**.
208
+ 3. Click one of:
209
+ - **Show as mask** — adds a new layer that's white everywhere except the
210
+ ticked classes, colored with the same palette as the inference widget.
211
+ - **Show on original** — adds a copy of the original image with all
212
+ pixels outside the ticked classes turned white. Useful for sanity-
213
+ checking the segmentation against the stain.
214
+ 4. **Clear isolated layers** removes everything this panel added in one go.
215
+
216
+ Masks are read on demand from the inference output folder; the original
217
+ layer's pixels are taken from the viewer.
218
+
219
+ ## License
220
+
221
+ MIT. (Matches `license = {text = "MIT"}` in `pyproject.toml`. Consider adding a
222
+ top-level `LICENSE` file with the MIT text so it ships in the sdist/wheel.)
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pentachrome-plugin"
7
+ version = "0.4.0"
8
+ description = "Napari plugin for the Pentachrome histology pipeline: VSI extraction, nnUNet inference, statistics."
9
+ authors = [{name = "Dimitrios Tsilis"}]
10
+ readme = "README.md"
11
+ license = {text = "MIT"}
12
+ requires-python = ">=3.10"
13
+ keywords = ["napari", "histology", "nnunet", "bioformats", "segmentation"]
14
+ classifiers = [
15
+ "Framework :: napari",
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Science/Research",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Operating System :: Microsoft :: Windows",
20
+ "License :: OSI Approved :: MIT License",
21
+ ]
22
+ dependencies = [
23
+ "napari[all]>=0.4.18",
24
+ "qtpy",
25
+ "tifffile",
26
+ "numpy<2",
27
+ "opencv-python",
28
+ "scipy",
29
+ "scikit-image",
30
+ "skan",
31
+ "imagecodecs",
32
+ ]
33
+
34
+ [project.urls]
35
+ "Homepage" = "https://github.com/dtsilis7/PentachromePipeline"
36
+ "Bug Tracker" = "https://github.com/dtsilis7/PentachromePipeline/issues"
37
+
38
+ [project.entry-points."napari.manifest"]
39
+ pentachrome-plugin = "pentachrome_plugin:napari.yaml"
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
43
+
44
+ [tool.setuptools.package-data]
45
+ pentachrome_plugin = ["napari.yaml"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ __version__ = "0.4.0"
2
+
3
+ from ._guided_widget import GuidedWidget
4
+ from ._widget import VsiExtractorWidget
5
+ from ._inference_widget import NnUnetInferenceWidget
6
+ from ._analysis_widget import AnalysisWidget
7
+
8
+ __all__ = [
9
+ "GuidedWidget",
10
+ "VsiExtractorWidget",
11
+ "NnUnetInferenceWidget",
12
+ "AnalysisWidget",
13
+ ]