deprojpy 0.1.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.
Files changed (37) hide show
  1. deprojpy-0.1.0/LICENSE +28 -0
  2. deprojpy-0.1.0/PKG-INFO +225 -0
  3. deprojpy-0.1.0/README-pypi.md +199 -0
  4. deprojpy-0.1.0/README.md +199 -0
  5. deprojpy-0.1.0/deprojpy/__init__.py +44 -0
  6. deprojpy-0.1.0/deprojpy/cli.py +133 -0
  7. deprojpy-0.1.0/deprojpy/core.py +232 -0
  8. deprojpy-0.1.0/deprojpy/geometry.py +136 -0
  9. deprojpy-0.1.0/deprojpy/heightmap.py +93 -0
  10. deprojpy-0.1.0/deprojpy/io.py +72 -0
  11. deprojpy-0.1.0/deprojpy/labels.py +104 -0
  12. deprojpy-0.1.0/deprojpy/mask.py +125 -0
  13. deprojpy-0.1.0/deprojpy/models.py +142 -0
  14. deprojpy-0.1.0/deprojpy/objects.py +20 -0
  15. deprojpy-0.1.0/deprojpy/plotting.py +730 -0
  16. deprojpy-0.1.0/deprojpy/surface_distance/__init__.py +21 -0
  17. deprojpy-0.1.0/deprojpy/surface_distance/_api.py +36 -0
  18. deprojpy-0.1.0/deprojpy/surface_distance/_boundary_surface.py +118 -0
  19. deprojpy-0.1.0/deprojpy/surface_distance/_calculator.py +540 -0
  20. deprojpy-0.1.0/deprojpy/surface_distance/_graph.py +467 -0
  21. deprojpy-0.1.0/deprojpy/surface_distance/_helpers.py +75 -0
  22. deprojpy-0.1.0/deprojpy/surface_distance/_straight.py +241 -0
  23. deprojpy-0.1.0/deprojpy.egg-info/PKG-INFO +225 -0
  24. deprojpy-0.1.0/deprojpy.egg-info/SOURCES.txt +35 -0
  25. deprojpy-0.1.0/deprojpy.egg-info/dependency_links.txt +1 -0
  26. deprojpy-0.1.0/deprojpy.egg-info/entry_points.txt +2 -0
  27. deprojpy-0.1.0/deprojpy.egg-info/requires.txt +18 -0
  28. deprojpy-0.1.0/deprojpy.egg-info/top_level.txt +1 -0
  29. deprojpy-0.1.0/pyproject.toml +49 -0
  30. deprojpy-0.1.0/setup.cfg +4 -0
  31. deprojpy-0.1.0/tests/test_core.py +292 -0
  32. deprojpy-0.1.0/tests/test_examples.py +68 -0
  33. deprojpy-0.1.0/tests/test_geometry.py +75 -0
  34. deprojpy-0.1.0/tests/test_labels.py +98 -0
  35. deprojpy-0.1.0/tests/test_mask_to_objects.py +47 -0
  36. deprojpy-0.1.0/tests/test_sample_smoke.py +54 -0
  37. deprojpy-0.1.0/tests/test_surface_distance.py +461 -0
deprojpy-0.1.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, zen-laboratory and mwappner
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.4
2
+ Name: deprojpy
3
+ Version: 0.1.0
4
+ Summary: Python behavioral core port of the DeProj MATLAB toolbox
5
+ Author: zen-laboratory, mwappner
6
+ License-Expression: BSD-3-Clause
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: numpy>=1.24
11
+ Requires-Dist: scipy>=1.10
12
+ Requires-Dist: scikit-image>=0.21
13
+ Requires-Dist: pandas>=2.0
14
+ Requires-Dist: networkx>=3.0
15
+ Requires-Dist: tifffile>=2023.0
16
+ Requires-Dist: matplotlib>=3.7
17
+ Requires-Dist: numba>=0.57
18
+ Requires-Dist: labelimage-tools[all]>=0.1.3
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=7; extra == "test"
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+ Requires-Dist: ruff>=0.5; extra == "dev"
24
+ Provides-Extra: labels
25
+ Dynamic: license-file
26
+
27
+ # DeProjPy
28
+
29
+ `deprojpy` is a Python implementation of the core [matlab project DeProj](https://gitlab.pasteur.fr/iah-public/DeProj) workflow: deproject segmented epithelial cell contours onto a height-map surface, compute 3D cell geometry, and estimate distances on the deprojected surface.
30
+
31
+
32
+ The goal is not GUI parity with MATLAB. The focus is a scriptable Python API for deprojection, feature measurement, plotting, and surface-distance calculations.
33
+
34
+ ## Installation
35
+
36
+ Install from PyPI:
37
+
38
+ ```bash
39
+ python -m pip install deprojpy
40
+ ```
41
+
42
+ For development, clone the repository and install it in editable mode:
43
+
44
+ ```bash
45
+ git clone https://github.com/zen-laboratory/DeProjPy.git
46
+ cd DeProjPy
47
+ python -m pip install -e ".[test]"
48
+ python -m pytest
49
+ ```
50
+
51
+ The label-image workflow uses the published companion `labelimage-tools`
52
+ package, which is installed automatically with DeProjPy. To install or upgrade
53
+ it explicitly:
54
+
55
+ ```bash
56
+ python -m pip install "labelimage-tools[all]"
57
+ ```
58
+
59
+ ## Quick start: DeProj-style mask input
60
+
61
+ The MATLAB-style workflow starts from a binary-like segmentation mask and a height map. In the mask image, cell interiors are non-zero and cell borders are zero-valued ridges.
62
+
63
+ ```python
64
+ import deprojpy as dp
65
+
66
+ mask, heightmap = dp.load_tiff_pair("samples/Segmentation-2.tif", "samples/HeightMap-2.tif")
67
+ result = dp.from_heightmap(mask, heightmap,
68
+ pixel_size=0.183, voxel_depth=1.0, units="µm",
69
+ invert_z=True, inpaint_zeros=True, prune_zeros=True)
70
+
71
+ df = result.to_dataframe()
72
+ result.to_csv("measurements.csv")
73
+ ```
74
+
75
+ Returned boundaries, centers, and junction centroids use geometric `(x, y, z)` order in physical units. Input images still use normal image indexing `(row, column)`.
76
+
77
+ Plot a scalar feature on the deprojected cell polygons:
78
+
79
+ ```python
80
+ fig, ax = dp.plotting.plot_feature_map(result, "area")
81
+ fig, ax = dp.plotting.plot_heightmap_with_centers(heightmap, result)
82
+ ```
83
+
84
+ <p align="center">
85
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/examples/output/plots/area_map.png" width="70%">
86
+ </p>
87
+ <p align="center">
88
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/examples/output/plots/heightmap_centers.png" width="70%">
89
+ </p>
90
+
91
+ ## Why deproject?
92
+
93
+ Measurements made directly on the 2D segmentation image can underestimate cell
94
+ geometry when the tissue surface is curved or tilted. DeProjPy measures cell
95
+ contours after lifting them onto the height-map surface, so quantities such as
96
+ area and perimeter are reported in surface-corrected physical units.
97
+
98
+ A useful diagnostic is the relative area difference between the deprojected
99
+ surface area and the original projected 2D area:
100
+
101
+ ```python
102
+ fig, ax = dp.plotting.plot_relative_error_map(result, 'area')
103
+ ```
104
+
105
+ <p align="center">
106
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/docs/images/relative_area_error.jpg" width="70%">
107
+ </p>
108
+
109
+ Positive values indicate cells whose surface-corrected area is larger than their
110
+ projected 2D area. This is the geometric correction DeProj is designed to make
111
+ visible and measurable.
112
+
113
+ ## Alternative input: labeled segmentations
114
+
115
+ DeProjPy is modernized compared to the Matlab version, as it can also start from an integer label image, where each cell has a unique label and background is zero. This is useful for outputs from segmentation pipelines that already return labels rather than DeProj-style ridge masks, such as [FishFeats](https://gletort.github.io/FishFeats/).
116
+
117
+ | DeProj-style mask | Label image |
118
+ |---|---|
119
+ | `from_heightmap(mask, heightmap, ...)` | `from_labels(labels, heightmap, ...)` |
120
+ | Cell interiors are non-zero; borders are zero. | All pixels in a cell share a unique integer label; background is zero. |
121
+
122
+ <p align="center">
123
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/docs/images/mask_input.png" width="42%">
124
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/docs/images/labels_input.png" width="42%">
125
+ </p>
126
+
127
+ ```python
128
+ import deprojpy as dp
129
+
130
+ labels, heightmap = dp.load_label_heightmap_pair("samples/Labels-2.tif", "samples/HeightMap-2.tif")
131
+ result = dp.from_labels(labels, heightmap,
132
+ pixel_size=0.183, voxel_depth=1.0, units="µm",
133
+ invert_z=True, inpaint_zeros=True, prune_zeros=True)
134
+
135
+ df = result.to_dataframe()
136
+ ```
137
+
138
+ The label workflow returns the same kind of `DeprojResult` as the mask workflow. Original label IDs are preserved as `source_label` when available.
139
+
140
+ ## Surface distances
141
+
142
+ DeProjPy introduces new reusable tools for distances constrained to a surface.
143
+
144
+ There are two main distance ideas:
145
+
146
+ - **straight surface distance**: sample the surface along the straight segment in
147
+ `xy` and measure the lifted 3D polyline;
148
+ - **graph geodesic distance**: build a sparse surface graph and compute shortest
149
+ paths on the graph. This is an approximation to the continuous geodesic.
150
+
151
+ For plotting paths on the fitted DeProj cell-contour surface, build the calculator from cell boundaries:
152
+
153
+ ```python
154
+ import numpy as np
155
+ from deprojpy.surface_distance import SurfaceDistanceCalculator, SurfaceGraph
156
+
157
+ centers = df[["center_x", "center_y"]].to_numpy()
158
+
159
+ calc = SurfaceDistanceCalculator.from_cell_boundaries(result)
160
+ d_straight = calc.straight_distance(centers[10], centers[200], input_units="physical")
161
+
162
+ graph = SurfaceGraph.from_calculator(calc, step="auto", connectivity="16")
163
+ d_graph, path_px = graph.distance(centers[10], centers[200],
164
+ input_units="physical", return_path=True)
165
+ ```
166
+
167
+ For all-pairs straight surface distances between cell centers:
168
+
169
+ ```python
170
+ D = calc.straight_pairwise_distances(centers, input_units="physical")
171
+ ```
172
+
173
+ Graph paths are returned in pixel coordinates, matching graph nodes. To plot them in 3D, sample the calculator surface and convert xy back to physical units:
174
+
175
+ ```python
176
+ path_z = calc.sample_height(path_px, input_units="pixel")
177
+ path_xyz = np.column_stack([path_px * result.pixel_size, path_z])
178
+ ```
179
+
180
+ <p align="center">
181
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/docs/images/3D_distance.png" width="100%">
182
+ </p>
183
+
184
+ Use `SurfaceDistanceCalculator.from_result(...)` when you want distances on the prepared height-map surface. Use `SurfaceDistanceCalculator.from_cell_boundaries(...)` when you want paths and distances on the fitted surface represented by the deprojected cell contours.
185
+
186
+ ## What is in a result?
187
+
188
+ A `DeprojResult` contains:
189
+
190
+ - `result.epicells`: one `EpiCell` per retained cell;
191
+ - `result.to_dataframe()`: a tabular summary of cell features;
192
+ - `result.prepared_heightmap`: the height map used for deprojection;
193
+ - deprojected boundaries, centers, junctions, and geometry in physical units.
194
+
195
+ The most common workflow is:
196
+
197
+ ```python
198
+ result = dp.from_heightmap(mask, heightmap, ...)
199
+ df = result.to_dataframe()
200
+ ```
201
+
202
+ or:
203
+
204
+ ```python
205
+ result = dp.from_labels(labels, heightmap, ...)
206
+ df = result.to_dataframe()
207
+ ```
208
+
209
+ ## More examples
210
+
211
+ See [`docs/cookbook.md`](https://github.com/zen-laboratory/DeProjPy/blob/main/docs/cookbook.md) and the scripts in [`examples/`](https://github.com/zen-laboratory/DeProjPy/tree/main/examples) for copy-pastable workflows covering plotting, diagnostics, label inputs, exports, curvature maps, and surface distances.
212
+
213
+ ## Validation status
214
+
215
+ The Python implementation is tested with synthetic invariants and the original DeProj sample images. It is not yet certified against MATLAB golden outputs.
216
+
217
+ With the original sample files, expected smoke-check values include:
218
+
219
+ - image shape `(282, 508)`;
220
+ - 426 retained cells;
221
+ - finite positive cell areas and perimeters.
222
+
223
+ ## License
224
+
225
+ DeProjPy is distributed under the BSD 3-Clause license. It is partly a Python implementation/port of the core workflow from the original BSD-licensed [MATLAB DeProj project](https://gitlab.pasteur.fr/iah-public/DeProj). This package does not imply endorsement by the original authors or institutions.
@@ -0,0 +1,199 @@
1
+ # DeProjPy
2
+
3
+ `deprojpy` is a Python implementation of the core [matlab project DeProj](https://gitlab.pasteur.fr/iah-public/DeProj) workflow: deproject segmented epithelial cell contours onto a height-map surface, compute 3D cell geometry, and estimate distances on the deprojected surface.
4
+
5
+
6
+ The goal is not GUI parity with MATLAB. The focus is a scriptable Python API for deprojection, feature measurement, plotting, and surface-distance calculations.
7
+
8
+ ## Installation
9
+
10
+ Install from PyPI:
11
+
12
+ ```bash
13
+ python -m pip install deprojpy
14
+ ```
15
+
16
+ For development, clone the repository and install it in editable mode:
17
+
18
+ ```bash
19
+ git clone https://github.com/zen-laboratory/DeProjPy.git
20
+ cd DeProjPy
21
+ python -m pip install -e ".[test]"
22
+ python -m pytest
23
+ ```
24
+
25
+ The label-image workflow uses the published companion `labelimage-tools`
26
+ package, which is installed automatically with DeProjPy. To install or upgrade
27
+ it explicitly:
28
+
29
+ ```bash
30
+ python -m pip install "labelimage-tools[all]"
31
+ ```
32
+
33
+ ## Quick start: DeProj-style mask input
34
+
35
+ The MATLAB-style workflow starts from a binary-like segmentation mask and a height map. In the mask image, cell interiors are non-zero and cell borders are zero-valued ridges.
36
+
37
+ ```python
38
+ import deprojpy as dp
39
+
40
+ mask, heightmap = dp.load_tiff_pair("samples/Segmentation-2.tif", "samples/HeightMap-2.tif")
41
+ result = dp.from_heightmap(mask, heightmap,
42
+ pixel_size=0.183, voxel_depth=1.0, units="µm",
43
+ invert_z=True, inpaint_zeros=True, prune_zeros=True)
44
+
45
+ df = result.to_dataframe()
46
+ result.to_csv("measurements.csv")
47
+ ```
48
+
49
+ Returned boundaries, centers, and junction centroids use geometric `(x, y, z)` order in physical units. Input images still use normal image indexing `(row, column)`.
50
+
51
+ Plot a scalar feature on the deprojected cell polygons:
52
+
53
+ ```python
54
+ fig, ax = dp.plotting.plot_feature_map(result, "area")
55
+ fig, ax = dp.plotting.plot_heightmap_with_centers(heightmap, result)
56
+ ```
57
+
58
+ <p align="center">
59
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/examples/output/plots/area_map.png" width="70%">
60
+ </p>
61
+ <p align="center">
62
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/examples/output/plots/heightmap_centers.png" width="70%">
63
+ </p>
64
+
65
+ ## Why deproject?
66
+
67
+ Measurements made directly on the 2D segmentation image can underestimate cell
68
+ geometry when the tissue surface is curved or tilted. DeProjPy measures cell
69
+ contours after lifting them onto the height-map surface, so quantities such as
70
+ area and perimeter are reported in surface-corrected physical units.
71
+
72
+ A useful diagnostic is the relative area difference between the deprojected
73
+ surface area and the original projected 2D area:
74
+
75
+ ```python
76
+ fig, ax = dp.plotting.plot_relative_error_map(result, 'area')
77
+ ```
78
+
79
+ <p align="center">
80
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/docs/images/relative_area_error.jpg" width="70%">
81
+ </p>
82
+
83
+ Positive values indicate cells whose surface-corrected area is larger than their
84
+ projected 2D area. This is the geometric correction DeProj is designed to make
85
+ visible and measurable.
86
+
87
+ ## Alternative input: labeled segmentations
88
+
89
+ DeProjPy is modernized compared to the Matlab version, as it can also start from an integer label image, where each cell has a unique label and background is zero. This is useful for outputs from segmentation pipelines that already return labels rather than DeProj-style ridge masks, such as [FishFeats](https://gletort.github.io/FishFeats/).
90
+
91
+ | DeProj-style mask | Label image |
92
+ |---|---|
93
+ | `from_heightmap(mask, heightmap, ...)` | `from_labels(labels, heightmap, ...)` |
94
+ | Cell interiors are non-zero; borders are zero. | All pixels in a cell share a unique integer label; background is zero. |
95
+
96
+ <p align="center">
97
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/docs/images/mask_input.png" width="42%">
98
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/docs/images/labels_input.png" width="42%">
99
+ </p>
100
+
101
+ ```python
102
+ import deprojpy as dp
103
+
104
+ labels, heightmap = dp.load_label_heightmap_pair("samples/Labels-2.tif", "samples/HeightMap-2.tif")
105
+ result = dp.from_labels(labels, heightmap,
106
+ pixel_size=0.183, voxel_depth=1.0, units="µm",
107
+ invert_z=True, inpaint_zeros=True, prune_zeros=True)
108
+
109
+ df = result.to_dataframe()
110
+ ```
111
+
112
+ The label workflow returns the same kind of `DeprojResult` as the mask workflow. Original label IDs are preserved as `source_label` when available.
113
+
114
+ ## Surface distances
115
+
116
+ DeProjPy introduces new reusable tools for distances constrained to a surface.
117
+
118
+ There are two main distance ideas:
119
+
120
+ - **straight surface distance**: sample the surface along the straight segment in
121
+ `xy` and measure the lifted 3D polyline;
122
+ - **graph geodesic distance**: build a sparse surface graph and compute shortest
123
+ paths on the graph. This is an approximation to the continuous geodesic.
124
+
125
+ For plotting paths on the fitted DeProj cell-contour surface, build the calculator from cell boundaries:
126
+
127
+ ```python
128
+ import numpy as np
129
+ from deprojpy.surface_distance import SurfaceDistanceCalculator, SurfaceGraph
130
+
131
+ centers = df[["center_x", "center_y"]].to_numpy()
132
+
133
+ calc = SurfaceDistanceCalculator.from_cell_boundaries(result)
134
+ d_straight = calc.straight_distance(centers[10], centers[200], input_units="physical")
135
+
136
+ graph = SurfaceGraph.from_calculator(calc, step="auto", connectivity="16")
137
+ d_graph, path_px = graph.distance(centers[10], centers[200],
138
+ input_units="physical", return_path=True)
139
+ ```
140
+
141
+ For all-pairs straight surface distances between cell centers:
142
+
143
+ ```python
144
+ D = calc.straight_pairwise_distances(centers, input_units="physical")
145
+ ```
146
+
147
+ Graph paths are returned in pixel coordinates, matching graph nodes. To plot them in 3D, sample the calculator surface and convert xy back to physical units:
148
+
149
+ ```python
150
+ path_z = calc.sample_height(path_px, input_units="pixel")
151
+ path_xyz = np.column_stack([path_px * result.pixel_size, path_z])
152
+ ```
153
+
154
+ <p align="center">
155
+ <img src="https://raw.githubusercontent.com/zen-laboratory/DeProjPy/main/docs/images/3D_distance.png" width="100%">
156
+ </p>
157
+
158
+ Use `SurfaceDistanceCalculator.from_result(...)` when you want distances on the prepared height-map surface. Use `SurfaceDistanceCalculator.from_cell_boundaries(...)` when you want paths and distances on the fitted surface represented by the deprojected cell contours.
159
+
160
+ ## What is in a result?
161
+
162
+ A `DeprojResult` contains:
163
+
164
+ - `result.epicells`: one `EpiCell` per retained cell;
165
+ - `result.to_dataframe()`: a tabular summary of cell features;
166
+ - `result.prepared_heightmap`: the height map used for deprojection;
167
+ - deprojected boundaries, centers, junctions, and geometry in physical units.
168
+
169
+ The most common workflow is:
170
+
171
+ ```python
172
+ result = dp.from_heightmap(mask, heightmap, ...)
173
+ df = result.to_dataframe()
174
+ ```
175
+
176
+ or:
177
+
178
+ ```python
179
+ result = dp.from_labels(labels, heightmap, ...)
180
+ df = result.to_dataframe()
181
+ ```
182
+
183
+ ## More examples
184
+
185
+ See [`docs/cookbook.md`](https://github.com/zen-laboratory/DeProjPy/blob/main/docs/cookbook.md) and the scripts in [`examples/`](https://github.com/zen-laboratory/DeProjPy/tree/main/examples) for copy-pastable workflows covering plotting, diagnostics, label inputs, exports, curvature maps, and surface distances.
186
+
187
+ ## Validation status
188
+
189
+ The Python implementation is tested with synthetic invariants and the original DeProj sample images. It is not yet certified against MATLAB golden outputs.
190
+
191
+ With the original sample files, expected smoke-check values include:
192
+
193
+ - image shape `(282, 508)`;
194
+ - 426 retained cells;
195
+ - finite positive cell areas and perimeters.
196
+
197
+ ## License
198
+
199
+ DeProjPy is distributed under the BSD 3-Clause license. It is partly a Python implementation/port of the core workflow from the original BSD-licensed [MATLAB DeProj project](https://gitlab.pasteur.fr/iah-public/DeProj). This package does not imply endorsement by the original authors or institutions.
@@ -0,0 +1,199 @@
1
+ # DeProjPy
2
+
3
+ `deprojpy` is a Python implementation of the core [matlab project DeProj](https://gitlab.pasteur.fr/iah-public/DeProj) workflow: deproject segmented epithelial cell contours onto a height-map surface, compute 3D cell geometry, and estimate distances on the deprojected surface.
4
+
5
+
6
+ The goal is not GUI parity with MATLAB. The focus is a scriptable Python API for deprojection, feature measurement, plotting, and surface-distance calculations.
7
+
8
+ ## Installation
9
+
10
+ Install from PyPI:
11
+
12
+ ```bash
13
+ python -m pip install deprojpy
14
+ ```
15
+
16
+ For development, clone the repository and install it in editable mode:
17
+
18
+ ```bash
19
+ git clone https://github.com/zen-laboratory/DeProjPy.git
20
+ cd DeProjPy
21
+ python -m pip install -e ".[test]"
22
+ python -m pytest
23
+ ```
24
+
25
+ The label-image workflow uses the published companion `labelimage-tools`
26
+ package, which is installed automatically with DeProjPy. To install or upgrade
27
+ it explicitly:
28
+
29
+ ```bash
30
+ python -m pip install "labelimage-tools[all]"
31
+ ```
32
+
33
+ ## Quick start: DeProj-style mask input
34
+
35
+ The MATLAB-style workflow starts from a binary-like segmentation mask and a height map. In the mask image, cell interiors are non-zero and cell borders are zero-valued ridges.
36
+
37
+ ```python
38
+ import deprojpy as dp
39
+
40
+ mask, heightmap = dp.load_tiff_pair("samples/Segmentation-2.tif", "samples/HeightMap-2.tif")
41
+ result = dp.from_heightmap(mask, heightmap,
42
+ pixel_size=0.183, voxel_depth=1.0, units="µm",
43
+ invert_z=True, inpaint_zeros=True, prune_zeros=True)
44
+
45
+ df = result.to_dataframe()
46
+ result.to_csv("measurements.csv")
47
+ ```
48
+
49
+ Returned boundaries, centers, and junction centroids use geometric `(x, y, z)` order in physical units. Input images still use normal image indexing `(row, column)`.
50
+
51
+ Plot a scalar feature on the deprojected cell polygons:
52
+
53
+ ```python
54
+ fig, ax = dp.plotting.plot_feature_map(result, "area")
55
+ fig, ax = dp.plotting.plot_heightmap_with_centers(heightmap, result)
56
+ ```
57
+
58
+ <p align="center">
59
+ <img src="examples/output/plots/area_map.png" width="70%">
60
+ </p>
61
+ <p align="center">
62
+ <img src="examples/output/plots/heightmap_centers.png" width="70%">
63
+ </p>
64
+
65
+ ## Why deproject?
66
+
67
+ Measurements made directly on the 2D segmentation image can underestimate cell
68
+ geometry when the tissue surface is curved or tilted. DeProjPy measures cell
69
+ contours after lifting them onto the height-map surface, so quantities such as
70
+ area and perimeter are reported in surface-corrected physical units.
71
+
72
+ A useful diagnostic is the relative area difference between the deprojected
73
+ surface area and the original projected 2D area:
74
+
75
+ ```python
76
+ fig, ax = dp.plotting.plot_relative_error_map(result, 'area')
77
+ ```
78
+
79
+ <p align="center">
80
+ <img src="docs/images/relative_area_error.jpg" width="70%">
81
+ </p>
82
+
83
+ Positive values indicate cells whose surface-corrected area is larger than their
84
+ projected 2D area. This is the geometric correction DeProj is designed to make
85
+ visible and measurable.
86
+
87
+ ## Alternative input: labeled segmentations
88
+
89
+ DeProjPy is modernized compared to the Matlab version, as it can also start from an integer label image, where each cell has a unique label and background is zero. This is useful for outputs from segmentation pipelines that already return labels rather than DeProj-style ridge masks, such as [FishFeats](https://gletort.github.io/FishFeats/).
90
+
91
+ | DeProj-style mask | Label image |
92
+ |---|---|
93
+ | `from_heightmap(mask, heightmap, ...)` | `from_labels(labels, heightmap, ...)` |
94
+ | Cell interiors are non-zero; borders are zero. | All pixels in a cell share a unique integer label; background is zero. |
95
+
96
+ <p align="center">
97
+ <img src="docs/images/mask_input.png" width="42%">
98
+ <img src="docs/images/labels_input.png" width="42%">
99
+ </p>
100
+
101
+ ```python
102
+ import deprojpy as dp
103
+
104
+ labels, heightmap = dp.load_label_heightmap_pair("samples/Labels-2.tif", "samples/HeightMap-2.tif")
105
+ result = dp.from_labels(labels, heightmap,
106
+ pixel_size=0.183, voxel_depth=1.0, units="µm",
107
+ invert_z=True, inpaint_zeros=True, prune_zeros=True)
108
+
109
+ df = result.to_dataframe()
110
+ ```
111
+
112
+ The label workflow returns the same kind of `DeprojResult` as the mask workflow. Original label IDs are preserved as `source_label` when available.
113
+
114
+ ## Surface distances
115
+
116
+ DeProjPy introduces new reusable tools for distances constrained to a surface.
117
+
118
+ There are two main distance ideas:
119
+
120
+ - **straight surface distance**: sample the surface along the straight segment in
121
+ `xy` and measure the lifted 3D polyline;
122
+ - **graph geodesic distance**: build a sparse surface graph and compute shortest
123
+ paths on the graph. This is an approximation to the continuous geodesic.
124
+
125
+ For plotting paths on the fitted DeProj cell-contour surface, build the calculator from cell boundaries:
126
+
127
+ ```python
128
+ import numpy as np
129
+ from deprojpy.surface_distance import SurfaceDistanceCalculator, SurfaceGraph
130
+
131
+ centers = df[["center_x", "center_y"]].to_numpy()
132
+
133
+ calc = SurfaceDistanceCalculator.from_cell_boundaries(result)
134
+ d_straight = calc.straight_distance(centers[10], centers[200], input_units="physical")
135
+
136
+ graph = SurfaceGraph.from_calculator(calc, step="auto", connectivity="16")
137
+ d_graph, path_px = graph.distance(centers[10], centers[200],
138
+ input_units="physical", return_path=True)
139
+ ```
140
+
141
+ For all-pairs straight surface distances between cell centers:
142
+
143
+ ```python
144
+ D = calc.straight_pairwise_distances(centers, input_units="physical")
145
+ ```
146
+
147
+ Graph paths are returned in pixel coordinates, matching graph nodes. To plot them in 3D, sample the calculator surface and convert xy back to physical units:
148
+
149
+ ```python
150
+ path_z = calc.sample_height(path_px, input_units="pixel")
151
+ path_xyz = np.column_stack([path_px * result.pixel_size, path_z])
152
+ ```
153
+
154
+ <p align="center">
155
+ <img src="docs/images/3D_distance.png" width="100%">
156
+ </p>
157
+
158
+ Use `SurfaceDistanceCalculator.from_result(...)` when you want distances on the prepared height-map surface. Use `SurfaceDistanceCalculator.from_cell_boundaries(...)` when you want paths and distances on the fitted surface represented by the deprojected cell contours.
159
+
160
+ ## What is in a result?
161
+
162
+ A `DeprojResult` contains:
163
+
164
+ - `result.epicells`: one `EpiCell` per retained cell;
165
+ - `result.to_dataframe()`: a tabular summary of cell features;
166
+ - `result.prepared_heightmap`: the height map used for deprojection;
167
+ - deprojected boundaries, centers, junctions, and geometry in physical units.
168
+
169
+ The most common workflow is:
170
+
171
+ ```python
172
+ result = dp.from_heightmap(mask, heightmap, ...)
173
+ df = result.to_dataframe()
174
+ ```
175
+
176
+ or:
177
+
178
+ ```python
179
+ result = dp.from_labels(labels, heightmap, ...)
180
+ df = result.to_dataframe()
181
+ ```
182
+
183
+ ## More examples
184
+
185
+ See [`docs/cookbook.md`](docs/cookbook.md) and the scripts in [`examples/`](examples/) for copy-pastable workflows covering plotting, diagnostics, label inputs, exports, curvature maps, and surface distances.
186
+
187
+ ## Validation status
188
+
189
+ The Python implementation is tested with synthetic invariants and the original DeProj sample images. It is not yet certified against MATLAB golden outputs.
190
+
191
+ With the original sample files, expected smoke-check values include:
192
+
193
+ - image shape `(282, 508)`;
194
+ - 426 retained cells;
195
+ - finite positive cell areas and perimeters.
196
+
197
+ ## License
198
+
199
+ DeProjPy is distributed under the BSD 3-Clause license. It is a partly Python implementation/port of the core workflow from the original BSD-licensed [MATLAB DeProj project](https://gitlab.pasteur.fr/iah-public/DeProj). This package does not imply endorsement by the original authors or institutions.