pyrametric 0.0.1__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 Bugra Oezdemir
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,219 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyrametric
3
+ Version: 0.0.1
4
+ Summary: Lazy, memory-bounded connected-components labeling and per-object measurement for OME-Zarr pyramids: label pyramids (from a mask), regionprops-like feature extraction, and label filtering (pyrops plugin; wraps tilewise-ccl)
5
+ Author-email: Bugra Oezdemir <bugraa.ozdemir@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/bugraoezdemir/pyrametric
8
+ Keywords: ome-zarr,ngff,connected-components,labeling,segmentation,regionprops,image-pyramid,bioimage,microscopy,out-of-core
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
20
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: tilewise-ccl>=0.0.4
25
+ Requires-Dist: ome_zarr_pyramid>=0.0.7
26
+ Requires-Dist: scipy
27
+ Requires-Dist: numpy
28
+ Requires-Dist: dask
29
+ Requires-Dist: zarr
30
+ Provides-Extra: dyna
31
+ Requires-Dist: ome_zarr_pyramid[dyna]>=0.0.7; extra == "dyna"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0; extra == "dev"
34
+ Requires-Dist: pandas; extra == "dev"
35
+ Requires-Dist: dyna-zarr>=0.0.6; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # pyrametric
39
+
40
+ Lazy, memory-bounded **connected-components labeling and per-object
41
+ measurement** for OME-Zarr pyramids, with OME-NGFF label-image output.
42
+
43
+ `pyrametric` is the OME-Zarr-aware layer over [`tilewise-ccl`](../tilewise-ccl): it
44
+ labels the level-0 binary mask of a `Pyramid`, propagates the labels down the
45
+ resolution levels, measures per-object features, and writes the result as a
46
+ spec-compliant NGFF **label image** (`labels/<name>/` with `image-label`
47
+ metadata). It works on already-segmented masks — thresholding/segmentation
48
+ lives in [`pyrops`](../pyrops). It provides:
49
+
50
+ - **`label_pyramid`** — label a mask `Pyramid` → a lazy, writable label
51
+ `Pyramid` (optionally with a per-object properties table).
52
+ - **`extract_features`** / **`ObjectFeatures`** — regionprops-like per-object
53
+ measurement (area, bbox, moments, physical units, ...) over a label pyramid,
54
+ computed lazily and memory-bounded (validated against skimage in 2D and 3D).
55
+ - **`write_label_pyramid`** — write a label pyramid as an OME-Zarr label image
56
+ (colors, properties, `source` back-reference; default `labels/<name>/`
57
+ location).
58
+ - **`label_array`** — re-exported from `tilewise-ccl` for array-level labeling.
59
+
60
+ The heavy lifting (fast tile-wise connected components, memory-bounded, exact,
61
+ returning a lazy dask array) lives in `tilewise-ccl`; this package adds the
62
+ pyramid, axis-handling, measurement, and NGFF I/O concerns on top.
63
+
64
+ ---
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ pip install pyrametric
70
+ # or, from a checkout:
71
+ pip install -e .
72
+ ```
73
+
74
+ Depends on `tilewise-ccl`, `ome_zarr_pyramid`, plus `numpy`/`scipy`/`dask`/`zarr`.
75
+
76
+ ---
77
+
78
+ ## Quick start
79
+
80
+ The input's level 0 must already be a **binary mask** (background 0 + one
81
+ foreground value) — there is no thresholding here (binarize beforehand).
82
+
83
+ ```python
84
+ from ome_zarr_pyramid.core.io import IO
85
+ from pyrametric import label_pyramid, write_label_pyramid
86
+
87
+ io = IO()
88
+ mask_pyr = io.read_pyramid("mask.zarr") # level 0 = a binary mask
89
+
90
+ # label + propagate down the pyramid -> a lazy, writable label Pyramid
91
+ label_pyr = label_pyramid(mask_pyr, connectivity=2, n_workers=8)
92
+
93
+ # ...write it as a plain multiscale array,
94
+ io.write_pyramid(label_pyr, "labels.zarr", overwrite=True)
95
+ ```
96
+
97
+ ### With properties + an OME-Zarr label image
98
+
99
+ ```python
100
+ # also measure per-object properties (area + bounding box) - FREE from the labeling
101
+ # metadata pass; they are stamped onto the returned pyramid's image-label metadata
102
+ label_pyr = label_pyramid(mask_pyr, properties=True, n_workers=8)
103
+
104
+ # write a spec-compliant label image under the source's labels/ group:
105
+ # mask.zarr/labels/cells/ (multiscales + image-label: colors, properties, source)
106
+ write_label_pyramid(label_pyr, source="mask.zarr", name="cells")
107
+ ```
108
+
109
+ ### Attaching labels to the source image, written together
110
+
111
+ `Pyramid.add_image_label` attaches a label pyramid to its source **image** pyramid,
112
+ so the two travel together and a single `write_pyramid` emits the image plus its
113
+ `labels/<name>/` sub-group in one OME-Zarr store — the common "image + its
114
+ segmentation, side by side" layout that viewers open as one dataset:
115
+
116
+ ```python
117
+ image_pyr = io.read_pyramid("image.zarr") # the intensity image
118
+ mask_pyr = io.read_pyramid("mask.zarr") # its binary mask (segment/threshold upstream)
119
+
120
+ # label the mask, build its multiscale, name it, then attach it to the image
121
+ label_pyr = label_pyramid(mask_pyr, properties=True).downscale().rename("cells")
122
+ combined = image_pyr.add_image_label(label_pyr, name="cells")
123
+
124
+ io.write_pyramid(combined, "image_with_labels.zarr", overwrite=True)
125
+ ```
126
+
127
+ ### Labeling a sub-region
128
+
129
+ To label only part of the image, select it first with `Pyramid.isel` (any axis,
130
+ int/slice/list) — the label matches that sub-region's geometry:
131
+
132
+ ```python
133
+ label_pyr = label_pyramid(mask_pyr.isel(c=1, z=slice(100, 150)))
134
+ ```
135
+
136
+ ---
137
+
138
+ ## API
139
+
140
+ ### `label_pyramid(pyramid, tile_shape=None, connectivity=2, n_workers=1, properties=False, verbose=False)`
141
+
142
+ Label a binary-mask `Pyramid`'s level 0 and propagate the labels down the
143
+ resolution levels (nearest-neighbour / stride downsampling — the correct choice
144
+ for categorical labels). To label a sub-region, select it first with
145
+ `Pyramid.isel` (e.g. `mask.isel(c=1, z=slice(100, 150))`).
146
+
147
+ **Returns** a lazy label `Pyramid` (dtype int32 unless the object count exceeds
148
+ int32), mirroring the source's axes, units, per-level shapes and scales. With
149
+ `properties=True` the same pyramid additionally carries its OME `image-label`
150
+ metadata (per-object colors + an area/bbox properties table), so it is write-ready.
151
+
152
+ | Parameter | Type | Default | Description |
153
+ |---|---|---|---|
154
+ | `pyramid` | `Pyramid` | — | Source pyramid; level 0 is a **binary mask** (not validated — see [Notes](#notes)). Axes may be any subset of `tczyx`. |
155
+ | `tile_shape` | sequence of int | `None` | Tile size for `tilewise-ccl`, one entry per **spatial** axis. `None` → `(303,) * n_spatial`. |
156
+ | `connectivity` | int | `2` | Spatial `scipy.ndimage` connectivity (2D: 1=4-, 2=8-conn; 3D: 1=6-, 2=18-, 3=26-conn). |
157
+ | `n_workers` | int | `1` | Threads for each labeling metadata pass. |
158
+ | `properties` | bool | `False` | Also measure per-object `area` + `bbox` (free from the labeling pass) and stamp them onto the returned pyramid's `image-label` metadata (colors + properties). The return type is unchanged — always a `Pyramid`. |
159
+ | `verbose` | bool | `False` | Forwarded to `tilewise-ccl.label_array`. |
160
+
161
+ When `properties=True`, `label_pyramid` measures each object's **`area`** (voxel
162
+ count) and **`bbox`** (half-open bounding box, per axis, in the label's own
163
+ coordinates) and stamps them onto the returned pyramid's `image-label` metadata
164
+ (alongside deterministic per-object `colors`), so it is write-ready. For richer
165
+ per-object measurement (intensity / physical / shape features, filtering) pass the
166
+ label pyramid to `extract_features(...)`.
167
+
168
+ ### `write_label_pyramid(label_pyramid, source=None, name="labels_0", output=None, write_colors=True, write_props=True, overwrite=False, **write_kwargs)`
169
+
170
+ Write a label pyramid as an OME-Zarr **label image**, serialising the `image-label`
171
+ metadata already on the pyramid (built by `label_pyramid(..., properties=True)` or
172
+ `Pyramid.set_image_label`). This writer only persists it — it does not measure.
173
+ Returns the path written.
174
+
175
+ | Parameter | Type | Default | Description |
176
+ |---|---|---|---|
177
+ | `label_pyramid` | `Pyramid` | — | The label pyramid, carrying its `image-label` metadata. |
178
+ | `source` | str \| Path \| `Pyramid` | `None` | The source image the labels belong to. **Required when `output` is None**: labels go to `<source>/labels/<name>/`, registered in the source's `labels` group, with `image-label.source.image = "../../"`. |
179
+ | `name` | str | `"labels_0"` | Label-image name (the `labels/<name>` subgroup). |
180
+ | `output` | str \| Path | `None` | Explicit output path; overrides the default `labels/<name>` location (written standalone). |
181
+ | `write_colors` | bool | `True` | Include the pyramid's `colors` in the written metadata; `False` drops that key at write time (a lighter write for a very large object count). |
182
+ | `write_props` | bool | `True` | Include the pyramid's `properties` table (`label-value`, `area (pixels)`, `object-coordinates`); `False` drops it at write time. |
183
+ | `overwrite` | bool | `False` | Overwrite an existing label image. |
184
+ | `**write_kwargs` | | | Forwarded to `IO.write_labels` / `write_pyramid` (backend, workers, ...). |
185
+
186
+ The emitted `image-label` metadata (OME-NGFF
187
+ [labels spec](https://ngff.openmicroscopy.org/specifications/0.5/index.html#labels-metadata))
188
+ contains `version`, `colors` (`{label-value, rgba}`), `properties`
189
+ (`{label-value, "area (pixels)", "object-coordinates": {axis: [start, stop]}}`),
190
+ and `source`. Both NGFF 0.4 and 0.5 layouts are written correctly.
191
+
192
+ ---
193
+
194
+ ## Notes
195
+
196
+ - **Binary mask required, not validated.** The level-0 array must be a binary
197
+ mask; this is the caller's responsibility. Validating it would force a full
198
+ scan of a potentially huge array, so it is deliberately skipped. (Re-labeling
199
+ an already-labeled dataset will be possible later, once `tilewise-ccl` grows a
200
+ relabel function.)
201
+ - **Downsampling is nearest-neighbour (stride).** Categorical labels must not be
202
+ averaged; coarser levels are produced by striding, matching the source
203
+ pyramid's per-level shapes and scales.
204
+ - **Batch axes give globally-unique ids.** When multiple `(t, c)` volumes are
205
+ labeled, each is labeled independently and its ids are offset so the whole
206
+ output is a single, globally-unique label image.
207
+ - **Colors/properties at scale.** For very large object counts, writing explicit
208
+ per-object colors and properties bloats the metadata (pyrametric warns) — pass
209
+ `write_colors=False` / `write_props=False` to `write_label_pyramid` to drop them.
210
+ Viewers such as napari auto-randomise label colors when no `colors` list is
211
+ present, so skipping them is usually fine.
212
+ - **Memory.** Labeling and property computation are memory-bounded by tile size
213
+ (see `tilewise-ccl`); the only object-count-proportional cost is the in-memory
214
+ properties table.
215
+
216
+ ## See also
217
+
218
+ - [`tilewise-ccl`](../tilewise-ccl) — the storage-agnostic array-level engine
219
+ (`label_array`), with its own README and benchmarks.
@@ -0,0 +1,182 @@
1
+ # pyrametric
2
+
3
+ Lazy, memory-bounded **connected-components labeling and per-object
4
+ measurement** for OME-Zarr pyramids, with OME-NGFF label-image output.
5
+
6
+ `pyrametric` is the OME-Zarr-aware layer over [`tilewise-ccl`](../tilewise-ccl): it
7
+ labels the level-0 binary mask of a `Pyramid`, propagates the labels down the
8
+ resolution levels, measures per-object features, and writes the result as a
9
+ spec-compliant NGFF **label image** (`labels/<name>/` with `image-label`
10
+ metadata). It works on already-segmented masks — thresholding/segmentation
11
+ lives in [`pyrops`](../pyrops). It provides:
12
+
13
+ - **`label_pyramid`** — label a mask `Pyramid` → a lazy, writable label
14
+ `Pyramid` (optionally with a per-object properties table).
15
+ - **`extract_features`** / **`ObjectFeatures`** — regionprops-like per-object
16
+ measurement (area, bbox, moments, physical units, ...) over a label pyramid,
17
+ computed lazily and memory-bounded (validated against skimage in 2D and 3D).
18
+ - **`write_label_pyramid`** — write a label pyramid as an OME-Zarr label image
19
+ (colors, properties, `source` back-reference; default `labels/<name>/`
20
+ location).
21
+ - **`label_array`** — re-exported from `tilewise-ccl` for array-level labeling.
22
+
23
+ The heavy lifting (fast tile-wise connected components, memory-bounded, exact,
24
+ returning a lazy dask array) lives in `tilewise-ccl`; this package adds the
25
+ pyramid, axis-handling, measurement, and NGFF I/O concerns on top.
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install pyrametric
33
+ # or, from a checkout:
34
+ pip install -e .
35
+ ```
36
+
37
+ Depends on `tilewise-ccl`, `ome_zarr_pyramid`, plus `numpy`/`scipy`/`dask`/`zarr`.
38
+
39
+ ---
40
+
41
+ ## Quick start
42
+
43
+ The input's level 0 must already be a **binary mask** (background 0 + one
44
+ foreground value) — there is no thresholding here (binarize beforehand).
45
+
46
+ ```python
47
+ from ome_zarr_pyramid.core.io import IO
48
+ from pyrametric import label_pyramid, write_label_pyramid
49
+
50
+ io = IO()
51
+ mask_pyr = io.read_pyramid("mask.zarr") # level 0 = a binary mask
52
+
53
+ # label + propagate down the pyramid -> a lazy, writable label Pyramid
54
+ label_pyr = label_pyramid(mask_pyr, connectivity=2, n_workers=8)
55
+
56
+ # ...write it as a plain multiscale array,
57
+ io.write_pyramid(label_pyr, "labels.zarr", overwrite=True)
58
+ ```
59
+
60
+ ### With properties + an OME-Zarr label image
61
+
62
+ ```python
63
+ # also measure per-object properties (area + bounding box) - FREE from the labeling
64
+ # metadata pass; they are stamped onto the returned pyramid's image-label metadata
65
+ label_pyr = label_pyramid(mask_pyr, properties=True, n_workers=8)
66
+
67
+ # write a spec-compliant label image under the source's labels/ group:
68
+ # mask.zarr/labels/cells/ (multiscales + image-label: colors, properties, source)
69
+ write_label_pyramid(label_pyr, source="mask.zarr", name="cells")
70
+ ```
71
+
72
+ ### Attaching labels to the source image, written together
73
+
74
+ `Pyramid.add_image_label` attaches a label pyramid to its source **image** pyramid,
75
+ so the two travel together and a single `write_pyramid` emits the image plus its
76
+ `labels/<name>/` sub-group in one OME-Zarr store — the common "image + its
77
+ segmentation, side by side" layout that viewers open as one dataset:
78
+
79
+ ```python
80
+ image_pyr = io.read_pyramid("image.zarr") # the intensity image
81
+ mask_pyr = io.read_pyramid("mask.zarr") # its binary mask (segment/threshold upstream)
82
+
83
+ # label the mask, build its multiscale, name it, then attach it to the image
84
+ label_pyr = label_pyramid(mask_pyr, properties=True).downscale().rename("cells")
85
+ combined = image_pyr.add_image_label(label_pyr, name="cells")
86
+
87
+ io.write_pyramid(combined, "image_with_labels.zarr", overwrite=True)
88
+ ```
89
+
90
+ ### Labeling a sub-region
91
+
92
+ To label only part of the image, select it first with `Pyramid.isel` (any axis,
93
+ int/slice/list) — the label matches that sub-region's geometry:
94
+
95
+ ```python
96
+ label_pyr = label_pyramid(mask_pyr.isel(c=1, z=slice(100, 150)))
97
+ ```
98
+
99
+ ---
100
+
101
+ ## API
102
+
103
+ ### `label_pyramid(pyramid, tile_shape=None, connectivity=2, n_workers=1, properties=False, verbose=False)`
104
+
105
+ Label a binary-mask `Pyramid`'s level 0 and propagate the labels down the
106
+ resolution levels (nearest-neighbour / stride downsampling — the correct choice
107
+ for categorical labels). To label a sub-region, select it first with
108
+ `Pyramid.isel` (e.g. `mask.isel(c=1, z=slice(100, 150))`).
109
+
110
+ **Returns** a lazy label `Pyramid` (dtype int32 unless the object count exceeds
111
+ int32), mirroring the source's axes, units, per-level shapes and scales. With
112
+ `properties=True` the same pyramid additionally carries its OME `image-label`
113
+ metadata (per-object colors + an area/bbox properties table), so it is write-ready.
114
+
115
+ | Parameter | Type | Default | Description |
116
+ |---|---|---|---|
117
+ | `pyramid` | `Pyramid` | — | Source pyramid; level 0 is a **binary mask** (not validated — see [Notes](#notes)). Axes may be any subset of `tczyx`. |
118
+ | `tile_shape` | sequence of int | `None` | Tile size for `tilewise-ccl`, one entry per **spatial** axis. `None` → `(303,) * n_spatial`. |
119
+ | `connectivity` | int | `2` | Spatial `scipy.ndimage` connectivity (2D: 1=4-, 2=8-conn; 3D: 1=6-, 2=18-, 3=26-conn). |
120
+ | `n_workers` | int | `1` | Threads for each labeling metadata pass. |
121
+ | `properties` | bool | `False` | Also measure per-object `area` + `bbox` (free from the labeling pass) and stamp them onto the returned pyramid's `image-label` metadata (colors + properties). The return type is unchanged — always a `Pyramid`. |
122
+ | `verbose` | bool | `False` | Forwarded to `tilewise-ccl.label_array`. |
123
+
124
+ When `properties=True`, `label_pyramid` measures each object's **`area`** (voxel
125
+ count) and **`bbox`** (half-open bounding box, per axis, in the label's own
126
+ coordinates) and stamps them onto the returned pyramid's `image-label` metadata
127
+ (alongside deterministic per-object `colors`), so it is write-ready. For richer
128
+ per-object measurement (intensity / physical / shape features, filtering) pass the
129
+ label pyramid to `extract_features(...)`.
130
+
131
+ ### `write_label_pyramid(label_pyramid, source=None, name="labels_0", output=None, write_colors=True, write_props=True, overwrite=False, **write_kwargs)`
132
+
133
+ Write a label pyramid as an OME-Zarr **label image**, serialising the `image-label`
134
+ metadata already on the pyramid (built by `label_pyramid(..., properties=True)` or
135
+ `Pyramid.set_image_label`). This writer only persists it — it does not measure.
136
+ Returns the path written.
137
+
138
+ | Parameter | Type | Default | Description |
139
+ |---|---|---|---|
140
+ | `label_pyramid` | `Pyramid` | — | The label pyramid, carrying its `image-label` metadata. |
141
+ | `source` | str \| Path \| `Pyramid` | `None` | The source image the labels belong to. **Required when `output` is None**: labels go to `<source>/labels/<name>/`, registered in the source's `labels` group, with `image-label.source.image = "../../"`. |
142
+ | `name` | str | `"labels_0"` | Label-image name (the `labels/<name>` subgroup). |
143
+ | `output` | str \| Path | `None` | Explicit output path; overrides the default `labels/<name>` location (written standalone). |
144
+ | `write_colors` | bool | `True` | Include the pyramid's `colors` in the written metadata; `False` drops that key at write time (a lighter write for a very large object count). |
145
+ | `write_props` | bool | `True` | Include the pyramid's `properties` table (`label-value`, `area (pixels)`, `object-coordinates`); `False` drops it at write time. |
146
+ | `overwrite` | bool | `False` | Overwrite an existing label image. |
147
+ | `**write_kwargs` | | | Forwarded to `IO.write_labels` / `write_pyramid` (backend, workers, ...). |
148
+
149
+ The emitted `image-label` metadata (OME-NGFF
150
+ [labels spec](https://ngff.openmicroscopy.org/specifications/0.5/index.html#labels-metadata))
151
+ contains `version`, `colors` (`{label-value, rgba}`), `properties`
152
+ (`{label-value, "area (pixels)", "object-coordinates": {axis: [start, stop]}}`),
153
+ and `source`. Both NGFF 0.4 and 0.5 layouts are written correctly.
154
+
155
+ ---
156
+
157
+ ## Notes
158
+
159
+ - **Binary mask required, not validated.** The level-0 array must be a binary
160
+ mask; this is the caller's responsibility. Validating it would force a full
161
+ scan of a potentially huge array, so it is deliberately skipped. (Re-labeling
162
+ an already-labeled dataset will be possible later, once `tilewise-ccl` grows a
163
+ relabel function.)
164
+ - **Downsampling is nearest-neighbour (stride).** Categorical labels must not be
165
+ averaged; coarser levels are produced by striding, matching the source
166
+ pyramid's per-level shapes and scales.
167
+ - **Batch axes give globally-unique ids.** When multiple `(t, c)` volumes are
168
+ labeled, each is labeled independently and its ids are offset so the whole
169
+ output is a single, globally-unique label image.
170
+ - **Colors/properties at scale.** For very large object counts, writing explicit
171
+ per-object colors and properties bloats the metadata (pyrametric warns) — pass
172
+ `write_colors=False` / `write_props=False` to `write_label_pyramid` to drop them.
173
+ Viewers such as napari auto-randomise label colors when no `colors` list is
174
+ present, so skipping them is usually fine.
175
+ - **Memory.** Labeling and property computation are memory-bounded by tile size
176
+ (see `tilewise-ccl`); the only object-count-proportional cost is the in-memory
177
+ properties table.
178
+
179
+ ## See also
180
+
181
+ - [`tilewise-ccl`](../tilewise-ccl) — the storage-agnostic array-level engine
182
+ (`label_array`), with its own README and benchmarks.
@@ -0,0 +1,64 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pyrametric"
7
+ version = "0.0.1"
8
+ description = "Lazy, memory-bounded connected-components labeling and per-object measurement for OME-Zarr pyramids: label pyramids (from a mask), regionprops-like feature extraction, and label filtering (pyrops plugin; wraps tilewise-ccl)"
9
+ readme = "README.md"
10
+ # 3.11+ to match ome_zarr_pyramid and tilewise-ccl.
11
+ requires-python = ">=3.11"
12
+ license = {text = "MIT"}
13
+ authors = [
14
+ {name = "Bugra Oezdemir", email = "bugraa.ozdemir@gmail.com"},
15
+ ]
16
+ # Kept in sync with the GitHub repository topics.
17
+ keywords = [
18
+ "ome-zarr", "ngff", "connected-components", "labeling", "segmentation",
19
+ "regionprops", "image-pyramid", "bioimage", "microscopy", "out-of-core",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Science/Research",
24
+ "Intended Audience :: Developers",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Topic :: Scientific/Engineering",
32
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
33
+ "Topic :: Scientific/Engineering :: Image Processing",
34
+ ]
35
+ dependencies = [
36
+ # The labeling engine. >=0.0.4: 0.0.3 carried the whole-tile guard but pinned
37
+ # dyna-zarr>=0.0.5, which does NOT align an integer-indexed axis - so `labels[z]`
38
+ # raised "needs whole tiles" on a fresh install. 0.0.4 raises that pin to >=0.0.6.
39
+ "tilewise-ccl>=0.0.4",
40
+ "ome_zarr_pyramid>=0.0.7",
41
+ "scipy",
42
+ "numpy",
43
+ "dask",
44
+ "zarr",
45
+ ]
46
+
47
+ [project.optional-dependencies]
48
+ # pyrametric never imports dyna_zarr itself - it reaches the memory-bounded backend
49
+ # through ome_zarr_pyramid, so this just forwards that package's own extra.
50
+ dyna = ["ome_zarr_pyramid[dyna]>=0.0.7"]
51
+ dev = [
52
+ "pytest>=7.0",
53
+ # `ObjectFeatures.to_frame()` returns a DataFrame; the dyna extra lets the
54
+ # backend="dyna" tests run instead of falling back to dask.
55
+ "pandas",
56
+ "dyna-zarr>=0.0.6",
57
+ ]
58
+
59
+ [project.urls]
60
+ Homepage = "https://github.com/bugraoezdemir/pyrametric"
61
+
62
+ [tool.setuptools.packages.find]
63
+ where = ["src"]
64
+ exclude = ["tests*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ """pyrametric: connected-components labeling for OME-Zarr pyramids.
2
+
3
+ The array-level labeling algorithm lives in the standalone, storage-agnostic
4
+ `tilewise-ccl` package; `label_array` is re-exported here for convenience.
5
+
6
+ `label_pyramid` is the OME-Zarr-aware layer: it labels a `Pyramid`'s level-0
7
+ binary mask and propagates the labels down the multiscale levels, returning a
8
+ lazy label `Pyramid` aligned to the source.
9
+ """
10
+
11
+ __version__ = "0.0.1"
12
+ __author__ = "Bugra Oezdemir"
13
+
14
+ from tilewise_ccl import label_array
15
+
16
+ from pyrametric.object_features import ObjectFeatures, extract_features
17
+ from pyrametric.pyramid import label_pyramid
18
+ from pyrametric.write import write_label_pyramid
19
+
20
+ __all__ = [
21
+ "label_array",
22
+ "label_pyramid",
23
+ "write_label_pyramid",
24
+ "extract_features",
25
+ "ObjectFeatures", # filtering is now ObjectFeatures.filter_by_size / .filter_by_feature
26
+ ]