patchworks 0.6.0__tar.gz → 0.8.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.
- {patchworks-0.6.0 → patchworks-0.8.0}/PKG-INFO +1 -1
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/guide/ome_zarr_napari.md +19 -4
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/guide/performance.md +16 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/_core.py +28 -1
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/plugins/napari.py +27 -7
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/plugins/ome_zarr.py +67 -4
- {patchworks-0.6.0 → patchworks-0.8.0}/tests/test_napari.py +29 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/tests/test_ome_zarr.py +11 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/.github/workflows/docs.yml +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/.github/workflows/lint.yml +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/.github/workflows/release.yml +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/.gitignore +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/README.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/cliff.toml +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/chunks.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/cluster.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/io.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/plugins/napari.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/relabel.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/api/tile_process.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/custom.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/custom_method.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/standalone_merge.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/stardist.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/examples/stardist_2d.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/getting_started.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/guide/merging.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/guide/pitfalls.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/guide/skip_empty.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/guide/tiling.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/docs/index.md +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/mkdocs.yml +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/pyproject.toml +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/__init__.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/_chunks.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/_cluster.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/_io.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/_merge.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/_relabel.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-0.6.0 → patchworks-0.8.0}/tests/test_core.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Tiled processing of arbitrarily large images with globally consistent labels
|
|
5
5
|
Project-URL: Homepage, https://github.com/imcf/patchworks
|
|
6
6
|
Project-URL: Issues, https://github.com/imcf/patchworks/issues
|
|
@@ -50,6 +50,17 @@ to_ome_zarr("scan.czi", "scan.zarr", n_levels=5) # via bioio
|
|
|
50
50
|
to_ome_zarr("scan.ims", "scan.zarr") # Imaris, native HDF5
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
!!! note "Imaris pyramids: rebuild (default) or reuse"
|
|
54
|
+
`.ims` files carry their own resolution pyramid. By default `to_ome_zarr`
|
|
55
|
+
reads only the **full-resolution** level and **builds a fresh NGFF pyramid**
|
|
56
|
+
(XY-only, nearest-neighbour, calibrated) for consistency. Pass
|
|
57
|
+
`reuse_pyramid=True` to instead **copy the Imaris levels** as-is — faster,
|
|
58
|
+
no recompute, keeping each level's native scale:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
to_ome_zarr("scan.ims", "scan.zarr", reuse_pyramid=True)
|
|
62
|
+
```
|
|
63
|
+
|
|
53
64
|
### Pixel calibration
|
|
54
65
|
|
|
55
66
|
The physical voxel size is read from the input — bioio's `physical_pixel_sizes`,
|
|
@@ -96,13 +107,17 @@ write_labels("scan.zarr", my_labels, name="nuclei")
|
|
|
96
107
|
layer in one call. OME-ZARR pyramids are handed to napari as a lazy multi-scale
|
|
97
108
|
list, so even huge stores open instantly and only on-screen data is fetched.
|
|
98
109
|
|
|
110
|
+
Because `tile_process` writes labels **into** the store by default, you usually
|
|
111
|
+
need no `labels=` argument at all — `view_in_napari` auto-loads every label
|
|
112
|
+
image found under `scan.zarr/labels/`:
|
|
113
|
+
|
|
99
114
|
```python
|
|
100
115
|
from patchworks.plugins.napari import view_in_napari
|
|
101
116
|
|
|
102
|
-
#
|
|
103
|
-
view_in_napari("scan.zarr"
|
|
117
|
+
# auto-loads scan.zarr/labels/* as Labels layers:
|
|
118
|
+
view_in_napari("scan.zarr")
|
|
104
119
|
|
|
105
|
-
# or a separate plain label store written with write_to=:
|
|
120
|
+
# or point at a separate plain label store written with write_to=:
|
|
106
121
|
view_in_napari("scan.zarr", labels="labels.zarr")
|
|
107
122
|
```
|
|
108
123
|
|
|
@@ -120,7 +135,7 @@ from patchworks.plugins.napari import view_in_napari
|
|
|
120
135
|
tile_process("scan.zarr", fn, progress=True)
|
|
121
136
|
|
|
122
137
|
# 2. inspect image + labels together, straight from the one store
|
|
123
|
-
view_in_napari("scan.zarr"
|
|
138
|
+
view_in_napari("scan.zarr") # labels auto-loaded from scan.zarr/labels/
|
|
124
139
|
```
|
|
125
140
|
|
|
126
141
|
Plugging in a different segmentation method is just swapping `fn` — any
|
|
@@ -17,6 +17,22 @@ merge step are sized to the host automatically:
|
|
|
17
17
|
The RAM figure is read live via `psutil`; without it, a conservative default is
|
|
18
18
|
used instead of guessing high.
|
|
19
19
|
|
|
20
|
+
## Live progress dashboard (GPU runs)
|
|
21
|
+
|
|
22
|
+
A single-GPU run still gets a **Dask dashboard**: patchworks spins up a tiny
|
|
23
|
+
1-worker / 1-thread in-process cluster, which keeps GPU evaluations serial (no
|
|
24
|
+
VRAM contention) while exposing the dashboard so you can watch tiles stream
|
|
25
|
+
through. The URL is logged at the start of staging:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
INFO:patchworks._core:Dask dashboard for this run: http://127.0.0.1:8787/status
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This needs `distributed` (and `bokeh` for the UI) installed; if they are
|
|
32
|
+
missing, patchworks logs a warning and falls back to the threaded scheduler
|
|
33
|
+
(no dashboard, same result). A cluster you start yourself
|
|
34
|
+
(`make_local_cluster`) is used as-is instead.
|
|
35
|
+
|
|
20
36
|
## Overriding the worker count
|
|
21
37
|
|
|
22
38
|
```python
|
|
@@ -332,7 +332,31 @@ def tile_process(
|
|
|
332
332
|
import dask as _dask
|
|
333
333
|
|
|
334
334
|
_tile_nbytes = int(np.prod(labeled.chunksize)) * labeled.dtype.itemsize
|
|
335
|
-
|
|
335
|
+
_temp_cluster = None
|
|
336
|
+
_temp_client = None
|
|
337
|
+
if _active is None and use_gpu:
|
|
338
|
+
# Single-GPU runs still get a live Dask dashboard: a 1-worker /
|
|
339
|
+
# 1-thread in-process cluster keeps GPU evals serial (no VRAM
|
|
340
|
+
# contention) while exposing the dashboard for progress.
|
|
341
|
+
try:
|
|
342
|
+
from dask.distributed import Client, LocalCluster
|
|
343
|
+
|
|
344
|
+
_temp_cluster = LocalCluster(
|
|
345
|
+
n_workers=1, threads_per_worker=1, processes=False
|
|
346
|
+
)
|
|
347
|
+
_temp_client = Client(_temp_cluster)
|
|
348
|
+
logger.info(
|
|
349
|
+
"Dask dashboard for this run: %s",
|
|
350
|
+
_temp_client.dashboard_link,
|
|
351
|
+
)
|
|
352
|
+
except Exception as exc: # no distributed/bokeh → threaded fallback
|
|
353
|
+
logger.warning(
|
|
354
|
+
"Could not start a dashboard cluster (%s); "
|
|
355
|
+
"falling back to the threaded scheduler.",
|
|
356
|
+
exc,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if _distributed_client() is None:
|
|
336
360
|
_workers = (
|
|
337
361
|
max_workers
|
|
338
362
|
if max_workers is not None
|
|
@@ -363,6 +387,9 @@ def tile_process(
|
|
|
363
387
|
logger.info("Staging tiles to %s …", stage_path)
|
|
364
388
|
with _sched_ctx:
|
|
365
389
|
_stage_to_zarr(labeled, stage_path, "staged", progress)
|
|
390
|
+
if _temp_client is not None:
|
|
391
|
+
_temp_client.close()
|
|
392
|
+
_temp_cluster.close()
|
|
366
393
|
labeled = da.from_zarr(stage_path, component="staged")
|
|
367
394
|
|
|
368
395
|
# NB: no post-staging skip-count pass here — counting skipped tiles by
|
|
@@ -14,13 +14,12 @@ napari is an optional, GUI-heavy dependency. Install it with
|
|
|
14
14
|
Usage
|
|
15
15
|
-----
|
|
16
16
|
>>> from patchworks import tile_process
|
|
17
|
-
>>> from patchworks.plugins.ome_zarr import to_ome_zarr
|
|
18
17
|
>>> from patchworks.plugins.napari import view_in_napari
|
|
19
18
|
>>>
|
|
20
|
-
>>>
|
|
21
|
-
>>>
|
|
22
|
-
>>>
|
|
23
|
-
>>> view_in_napari("
|
|
19
|
+
>>> # labels are written into scan.zarr/labels/ by default …
|
|
20
|
+
>>> tile_process("scan.zarr", fn)
|
|
21
|
+
>>> # … so the viewer finds and overlays them with no labels= argument:
|
|
22
|
+
>>> view_in_napari("scan.zarr")
|
|
24
23
|
"""
|
|
25
24
|
|
|
26
25
|
from __future__ import annotations
|
|
@@ -86,6 +85,15 @@ def _resolve_image(
|
|
|
86
85
|
return source
|
|
87
86
|
|
|
88
87
|
|
|
88
|
+
def _inner_label_names(store: Union[str, Path]) -> list[str]:
|
|
89
|
+
"""Names registered under an OME-ZARR's NGFF ``labels/`` group, if any."""
|
|
90
|
+
try:
|
|
91
|
+
grp = zarr.open_group(f"{store}/labels", mode="r")
|
|
92
|
+
except Exception:
|
|
93
|
+
return []
|
|
94
|
+
return list(grp.attrs.get("labels", []))
|
|
95
|
+
|
|
96
|
+
|
|
89
97
|
def _resolve_labels(
|
|
90
98
|
source: Union[da.Array, str, Path], component: str
|
|
91
99
|
) -> Union[da.Array, list[da.Array]]:
|
|
@@ -123,7 +131,10 @@ def view_in_napari(
|
|
|
123
131
|
labels : da.Array, str, Path or None
|
|
124
132
|
Label array to overlay. A plain ``.zarr`` store written by
|
|
125
133
|
``tile_process`` is read from its ``labels_component``; an OME-ZARR
|
|
126
|
-
pyramid is shown multi-scale
|
|
134
|
+
pyramid is shown multi-scale. ``None`` (default) **auto-loads** every
|
|
135
|
+
label image stored inside the OME-ZARR under ``labels/<name>/`` — the
|
|
136
|
+
place ``tile_process`` writes them by default — each as its own Labels
|
|
137
|
+
layer. (Falls back to image-only if there are none.)
|
|
127
138
|
channel : int or None, optional
|
|
128
139
|
Channel to display from the image (``None`` keeps all channels).
|
|
129
140
|
labels_component : str, optional
|
|
@@ -145,7 +156,7 @@ def view_in_napari(
|
|
|
145
156
|
|
|
146
157
|
Examples
|
|
147
158
|
--------
|
|
148
|
-
>>> view_in_napari("scan.zarr"
|
|
159
|
+
>>> view_in_napari("scan.zarr") # auto-loads scan.zarr/labels/* # doctest: +SKIP
|
|
149
160
|
"""
|
|
150
161
|
napari = _require_napari()
|
|
151
162
|
|
|
@@ -161,6 +172,15 @@ def view_in_napari(
|
|
|
161
172
|
if labels is not None:
|
|
162
173
|
lab = _resolve_labels(labels, labels_component)
|
|
163
174
|
viewer.add_labels(lab, name=labels_name)
|
|
175
|
+
elif _is_zarr(image):
|
|
176
|
+
# No labels given → auto-overlay every label image stored inside the
|
|
177
|
+
# OME-ZARR under labels/<name>/ (the default place tile_process writes
|
|
178
|
+
# them), each as its own multi-scale Labels layer.
|
|
179
|
+
for name in _inner_label_names(image):
|
|
180
|
+
levels = _multiscale_levels(f"{image}/labels/{name}", None)
|
|
181
|
+
lab = [lvl.astype("int32") for lvl in levels]
|
|
182
|
+
viewer.add_labels(lab if len(lab) > 1 else lab[0], name=name)
|
|
183
|
+
logger.info("auto-loaded labels/%s from %s", name, image)
|
|
164
184
|
|
|
165
185
|
if show:
|
|
166
186
|
napari.run()
|
|
@@ -255,8 +255,8 @@ def _open_bioio(path: str, scene: int) -> tuple[da.Array, str, PixelSize]:
|
|
|
255
255
|
return arr, axes, pixel_size
|
|
256
256
|
|
|
257
257
|
|
|
258
|
-
def _open_imaris(path: str) -> tuple[da.Array, str, PixelSize]:
|
|
259
|
-
"""Open an Imaris ``.ims``
|
|
258
|
+
def _open_imaris(path: str, level: int = 0) -> tuple[da.Array, str, PixelSize]:
|
|
259
|
+
"""Open an Imaris ``.ims`` *level* lazily → ``(array, axes, pixel_size)``."""
|
|
260
260
|
try:
|
|
261
261
|
from imaris_ims_file_reader.ims import ims
|
|
262
262
|
except ImportError as exc:
|
|
@@ -265,8 +265,8 @@ def _open_imaris(path: str) -> tuple[da.Array, str, PixelSize]:
|
|
|
265
265
|
"Install it with:\n pip install 'patchworks[imaris]'"
|
|
266
266
|
) from exc
|
|
267
267
|
|
|
268
|
-
#
|
|
269
|
-
reader = ims(path, ResolutionLevelLock=
|
|
268
|
+
# The object is array-like and h5py-backed (lazy).
|
|
269
|
+
reader = ims(path, ResolutionLevelLock=level)
|
|
270
270
|
order = _DEFAULT_ORDER[len(_DEFAULT_ORDER) - reader.ndim :]
|
|
271
271
|
arr = da.from_array(reader, chunks=_default_chunks(reader.shape, order))
|
|
272
272
|
|
|
@@ -293,6 +293,46 @@ def _open_imaris(path: str) -> tuple[da.Array, str, PixelSize]:
|
|
|
293
293
|
return arr, axes, pixel_size
|
|
294
294
|
|
|
295
295
|
|
|
296
|
+
def _write_imaris_pyramid(
|
|
297
|
+
path: str,
|
|
298
|
+
out: str,
|
|
299
|
+
*,
|
|
300
|
+
chunks: Union[tuple[int, ...], None],
|
|
301
|
+
overwrite: bool,
|
|
302
|
+
) -> str:
|
|
303
|
+
"""Copy an Imaris file's own resolution levels into an OME-ZARR.
|
|
304
|
+
|
|
305
|
+
Each Imaris ``ResolutionLevel`` is written as a pyramid level with its own
|
|
306
|
+
physical scale, so no downsampling is recomputed. Lazy (h5py-backed) reads
|
|
307
|
+
stream straight to disk.
|
|
308
|
+
"""
|
|
309
|
+
from imaris_ims_file_reader.ims import ims
|
|
310
|
+
|
|
311
|
+
base = ims(path, ResolutionLevelLock=0)
|
|
312
|
+
n_levels = int(getattr(base, "ResolutionLevels", 1) or 1)
|
|
313
|
+
|
|
314
|
+
zarr.open_group(out, mode="w" if overwrite else "w-")
|
|
315
|
+
datasets: list[dict] = []
|
|
316
|
+
axes = ""
|
|
317
|
+
calibrated = False
|
|
318
|
+
for level in range(n_levels):
|
|
319
|
+
arr, axes, ps = _open_imaris(path, level=level)
|
|
320
|
+
scale = _base_scale(axes, ps)
|
|
321
|
+
calibrated = calibrated or bool(ps)
|
|
322
|
+
da.to_zarr(
|
|
323
|
+
arr.rechunk(chunks or _default_chunks(arr.shape, axes)),
|
|
324
|
+
out,
|
|
325
|
+
component=str(level),
|
|
326
|
+
overwrite=True,
|
|
327
|
+
)
|
|
328
|
+
datasets.append(_dataset(str(level), scale))
|
|
329
|
+
logger.info("imaris level %d copied: shape=%s", level, arr.shape)
|
|
330
|
+
_write_multiscales(
|
|
331
|
+
out, axes, datasets, Path(out).stem, calibrated=calibrated
|
|
332
|
+
)
|
|
333
|
+
return out
|
|
334
|
+
|
|
335
|
+
|
|
296
336
|
def _to_dask(
|
|
297
337
|
source: Union[da.Array, np.ndarray, str, Path],
|
|
298
338
|
axes: Union[str, None],
|
|
@@ -327,6 +367,7 @@ def to_ome_zarr(
|
|
|
327
367
|
n_levels: int = 5,
|
|
328
368
|
downscale: int = 2,
|
|
329
369
|
chunks: Union[tuple[int, ...], None] = None,
|
|
370
|
+
reuse_pyramid: bool = False,
|
|
330
371
|
overwrite: bool = False,
|
|
331
372
|
) -> str:
|
|
332
373
|
"""Write *source* as a pyramidal, calibrated OME-ZARR store.
|
|
@@ -359,6 +400,12 @@ def to_ome_zarr(
|
|
|
359
400
|
Per-level X/Y downsampling factor (default 2).
|
|
360
401
|
chunks : tuple of int, optional
|
|
361
402
|
Chunk shape for the written levels. ``None`` → a bounded default.
|
|
403
|
+
reuse_pyramid : bool, optional
|
|
404
|
+
*Imaris ``.ims`` only.* Copy the file's **own** resolution levels
|
|
405
|
+
instead of rebuilding the pyramid (faster, no recompute), keeping each
|
|
406
|
+
level's native scale. Ignored for other inputs; falls back to a
|
|
407
|
+
rebuild if the Imaris levels can't be read. Default ``False`` (rebuild,
|
|
408
|
+
for a consistent XY-only, nearest-neighbour NGFF pyramid).
|
|
362
409
|
overwrite : bool, optional
|
|
363
410
|
Overwrite an existing store at *out_path*.
|
|
364
411
|
|
|
@@ -378,6 +425,22 @@ def to_ome_zarr(
|
|
|
378
425
|
if n_levels < 1:
|
|
379
426
|
raise ValueError("n_levels must be >= 1")
|
|
380
427
|
|
|
428
|
+
# Reuse an Imaris file's own resolution pyramid instead of rebuilding it.
|
|
429
|
+
if (
|
|
430
|
+
reuse_pyramid
|
|
431
|
+
and isinstance(source, (str, Path))
|
|
432
|
+
and str(source).lower().endswith(".ims")
|
|
433
|
+
):
|
|
434
|
+
try:
|
|
435
|
+
return _write_imaris_pyramid(
|
|
436
|
+
str(source), str(out_path), chunks=chunks, overwrite=overwrite
|
|
437
|
+
)
|
|
438
|
+
except Exception as exc:
|
|
439
|
+
logger.warning(
|
|
440
|
+
"reuse_pyramid failed (%s); rebuilding the pyramid instead.",
|
|
441
|
+
exc,
|
|
442
|
+
)
|
|
443
|
+
|
|
381
444
|
arr, axes, detected = _to_dask(source, axes, scene)
|
|
382
445
|
if len(axes) != arr.ndim:
|
|
383
446
|
raise ValueError(
|
|
@@ -39,3 +39,32 @@ def test_require_napari_message(monkeypatch):
|
|
|
39
39
|
nplugin._require_napari()
|
|
40
40
|
else:
|
|
41
41
|
assert nplugin._require_napari() is napari
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_inner_label_discovery(tmp_path):
|
|
45
|
+
"""Labels written into a store are discoverable for auto-overlay."""
|
|
46
|
+
import numpy as np
|
|
47
|
+
|
|
48
|
+
from patchworks.plugins.ome_zarr import to_ome_zarr, write_labels
|
|
49
|
+
|
|
50
|
+
store = to_ome_zarr(
|
|
51
|
+
np.zeros((8, 8, 8), "uint16"), tmp_path / "scan.zarr", n_levels=2
|
|
52
|
+
)
|
|
53
|
+
write_labels(store, np.ones((8, 8, 8), "int32"), name="cells", n_levels=2)
|
|
54
|
+
|
|
55
|
+
assert nplugin._inner_label_names(store) == ["cells"]
|
|
56
|
+
levels = nplugin._multiscale_levels(f"{store}/labels/cells", None)
|
|
57
|
+
assert len(levels) == 2
|
|
58
|
+
assert levels[1].shape == (8, 4, 4) # Z preserved, XY downsampled
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_inner_label_discovery_none(tmp_path):
|
|
62
|
+
"""A store without labels yields an empty list (image-only view)."""
|
|
63
|
+
import numpy as np
|
|
64
|
+
|
|
65
|
+
from patchworks.plugins.ome_zarr import to_ome_zarr
|
|
66
|
+
|
|
67
|
+
store = to_ome_zarr(
|
|
68
|
+
np.zeros((8, 8, 8), "uint16"), tmp_path / "img.zarr", n_levels=1
|
|
69
|
+
)
|
|
70
|
+
assert nplugin._inner_label_names(store) == []
|
|
@@ -128,3 +128,14 @@ def test_write_labels_into_store(tmp_path):
|
|
|
128
128
|
assert load_ome_zarr(group, channel=None, level=1).shape == (8, 4, 4)
|
|
129
129
|
lg = zarr.open_group(group, mode="r")
|
|
130
130
|
assert lg.attrs["image-label"]["version"]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_reuse_pyramid_ignored_for_arrays(tmp_path):
|
|
134
|
+
"""reuse_pyramid only affects .ims inputs; arrays still rebuild."""
|
|
135
|
+
out = to_ome_zarr(
|
|
136
|
+
np.zeros((8, 8, 8), "uint16"),
|
|
137
|
+
tmp_path / "arr.zarr",
|
|
138
|
+
n_levels=2,
|
|
139
|
+
reuse_pyramid=True,
|
|
140
|
+
)
|
|
141
|
+
assert load_ome_zarr(out, channel=None, level=1).shape == (8, 4, 4)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|