patchworks 0.7.0__tar.gz → 0.8.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.
- {patchworks-0.7.0 → patchworks-0.8.1}/PKG-INFO +1 -1
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/guide/ome_zarr_napari.md +10 -6
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/plugins/ome_zarr.py +97 -7
- {patchworks-0.7.0 → patchworks-0.8.1}/tests/test_ome_zarr.py +11 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/.github/workflows/docs.yml +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/.github/workflows/lint.yml +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/.github/workflows/release.yml +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/.gitignore +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/README.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/cliff.toml +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/chunks.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/cluster.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/io.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/plugins/napari.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/relabel.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/api/tile_process.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/custom.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/custom_method.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/standalone_merge.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/stardist.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/examples/stardist_2d.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/getting_started.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/guide/merging.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/guide/performance.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/guide/pitfalls.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/guide/skip_empty.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/guide/tiling.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/docs/index.md +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/mkdocs.yml +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/pyproject.toml +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/__init__.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/_chunks.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/_cluster.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/_core.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/_io.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/_merge.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/_relabel.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/tests/test_core.py +0 -0
- {patchworks-0.7.0 → patchworks-0.8.1}/tests/test_napari.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
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,12 +50,16 @@ 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
|
|
54
|
-
`.ims` files carry their own resolution pyramid
|
|
55
|
-
only the **full-resolution** level and **builds a fresh NGFF pyramid**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
```
|
|
59
63
|
|
|
60
64
|
### Pixel calibration
|
|
61
65
|
|
|
@@ -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,11 +265,38 @@ 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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
268
|
+
# Read straight from the underlying HDF5 datasets. The reader's own
|
|
269
|
+
# __getitem__ squeezes singletons and pads to chunk boundaries, which both
|
|
270
|
+
# break da.from_array; the raw h5py datasets slice exactly and keep their
|
|
271
|
+
# native chunking. Imaris stores one 3-D (Z, Y, X) Data array per
|
|
272
|
+
# (timepoint, channel), padded to a chunk multiple — crop to the true
|
|
273
|
+
# extent the reader reports.
|
|
274
|
+
import h5py
|
|
275
|
+
|
|
276
|
+
reader = ims(path, ResolutionLevelLock=level)
|
|
277
|
+
n_t = int(getattr(reader, "TimePoints", 1) or 1)
|
|
278
|
+
n_c = int(getattr(reader, "Channels", 1) or 1)
|
|
279
|
+
z, y, x = (int(s) for s in reader.shape[-3:])
|
|
280
|
+
|
|
281
|
+
# Open our own h5py handle (the reader closes its own on GC). The Dataset
|
|
282
|
+
# objects keep this File alive for the lifetime of the dask graph, and
|
|
283
|
+
# da.from_array's default read lock makes the (thread-unsafe) h5py reads
|
|
284
|
+
# safe under the threaded scheduler.
|
|
285
|
+
hf = h5py.File(path, "r")
|
|
286
|
+
t_stack = []
|
|
287
|
+
for t in range(n_t):
|
|
288
|
+
c_stack = []
|
|
289
|
+
for c in range(n_c):
|
|
290
|
+
ds = hf[
|
|
291
|
+
f"DataSet/ResolutionLevel {level}/TimePoint {t}/"
|
|
292
|
+
f"Channel {c}/Data"
|
|
293
|
+
]
|
|
294
|
+
c_stack.append(da.from_array(ds, chunks=ds.chunks)[:z, :y, :x])
|
|
295
|
+
t_stack.append(da.stack(c_stack, axis=0)) # (c, z, y, x)
|
|
296
|
+
arr = da.stack(t_stack, axis=0) # (t, c, z, y, x)
|
|
297
|
+
|
|
298
|
+
# Drop singleton non-spatial axes for a tidy result.
|
|
299
|
+
order = "tczyx"
|
|
273
300
|
keep = [
|
|
274
301
|
i
|
|
275
302
|
for i, name in enumerate(order)
|
|
@@ -293,6 +320,46 @@ def _open_imaris(path: str) -> tuple[da.Array, str, PixelSize]:
|
|
|
293
320
|
return arr, axes, pixel_size
|
|
294
321
|
|
|
295
322
|
|
|
323
|
+
def _write_imaris_pyramid(
|
|
324
|
+
path: str,
|
|
325
|
+
out: str,
|
|
326
|
+
*,
|
|
327
|
+
chunks: Union[tuple[int, ...], None],
|
|
328
|
+
overwrite: bool,
|
|
329
|
+
) -> str:
|
|
330
|
+
"""Copy an Imaris file's own resolution levels into an OME-ZARR.
|
|
331
|
+
|
|
332
|
+
Each Imaris ``ResolutionLevel`` is written as a pyramid level with its own
|
|
333
|
+
physical scale, so no downsampling is recomputed. Lazy (h5py-backed) reads
|
|
334
|
+
stream straight to disk.
|
|
335
|
+
"""
|
|
336
|
+
from imaris_ims_file_reader.ims import ims
|
|
337
|
+
|
|
338
|
+
base = ims(path, ResolutionLevelLock=0)
|
|
339
|
+
n_levels = int(getattr(base, "ResolutionLevels", 1) or 1)
|
|
340
|
+
|
|
341
|
+
zarr.open_group(out, mode="w" if overwrite else "w-")
|
|
342
|
+
datasets: list[dict] = []
|
|
343
|
+
axes = ""
|
|
344
|
+
calibrated = False
|
|
345
|
+
for level in range(n_levels):
|
|
346
|
+
arr, axes, ps = _open_imaris(path, level=level)
|
|
347
|
+
scale = _base_scale(axes, ps)
|
|
348
|
+
calibrated = calibrated or bool(ps)
|
|
349
|
+
da.to_zarr(
|
|
350
|
+
arr.rechunk(chunks or _default_chunks(arr.shape, axes)),
|
|
351
|
+
out,
|
|
352
|
+
component=str(level),
|
|
353
|
+
overwrite=True,
|
|
354
|
+
)
|
|
355
|
+
datasets.append(_dataset(str(level), scale))
|
|
356
|
+
logger.info("imaris level %d copied: shape=%s", level, arr.shape)
|
|
357
|
+
_write_multiscales(
|
|
358
|
+
out, axes, datasets, Path(out).stem, calibrated=calibrated
|
|
359
|
+
)
|
|
360
|
+
return out
|
|
361
|
+
|
|
362
|
+
|
|
296
363
|
def _to_dask(
|
|
297
364
|
source: Union[da.Array, np.ndarray, str, Path],
|
|
298
365
|
axes: Union[str, None],
|
|
@@ -327,6 +394,7 @@ def to_ome_zarr(
|
|
|
327
394
|
n_levels: int = 5,
|
|
328
395
|
downscale: int = 2,
|
|
329
396
|
chunks: Union[tuple[int, ...], None] = None,
|
|
397
|
+
reuse_pyramid: bool = False,
|
|
330
398
|
overwrite: bool = False,
|
|
331
399
|
) -> str:
|
|
332
400
|
"""Write *source* as a pyramidal, calibrated OME-ZARR store.
|
|
@@ -359,6 +427,12 @@ def to_ome_zarr(
|
|
|
359
427
|
Per-level X/Y downsampling factor (default 2).
|
|
360
428
|
chunks : tuple of int, optional
|
|
361
429
|
Chunk shape for the written levels. ``None`` → a bounded default.
|
|
430
|
+
reuse_pyramid : bool, optional
|
|
431
|
+
*Imaris ``.ims`` only.* Copy the file's **own** resolution levels
|
|
432
|
+
instead of rebuilding the pyramid (faster, no recompute), keeping each
|
|
433
|
+
level's native scale. Ignored for other inputs; falls back to a
|
|
434
|
+
rebuild if the Imaris levels can't be read. Default ``False`` (rebuild,
|
|
435
|
+
for a consistent XY-only, nearest-neighbour NGFF pyramid).
|
|
362
436
|
overwrite : bool, optional
|
|
363
437
|
Overwrite an existing store at *out_path*.
|
|
364
438
|
|
|
@@ -378,6 +452,22 @@ def to_ome_zarr(
|
|
|
378
452
|
if n_levels < 1:
|
|
379
453
|
raise ValueError("n_levels must be >= 1")
|
|
380
454
|
|
|
455
|
+
# Reuse an Imaris file's own resolution pyramid instead of rebuilding it.
|
|
456
|
+
if (
|
|
457
|
+
reuse_pyramid
|
|
458
|
+
and isinstance(source, (str, Path))
|
|
459
|
+
and str(source).lower().endswith(".ims")
|
|
460
|
+
):
|
|
461
|
+
try:
|
|
462
|
+
return _write_imaris_pyramid(
|
|
463
|
+
str(source), str(out_path), chunks=chunks, overwrite=overwrite
|
|
464
|
+
)
|
|
465
|
+
except Exception as exc:
|
|
466
|
+
logger.warning(
|
|
467
|
+
"reuse_pyramid failed (%s); rebuilding the pyramid instead.",
|
|
468
|
+
exc,
|
|
469
|
+
)
|
|
470
|
+
|
|
381
471
|
arr, axes, detected = _to_dask(source, axes, scene)
|
|
382
472
|
if len(axes) != arr.ndim:
|
|
383
473
|
raise ValueError(
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|