patchworks 0.7.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.
Files changed (50) hide show
  1. {patchworks-0.7.0 → patchworks-0.8.0}/PKG-INFO +1 -1
  2. {patchworks-0.7.0 → patchworks-0.8.0}/docs/guide/ome_zarr_napari.md +10 -6
  3. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/plugins/ome_zarr.py +67 -4
  4. {patchworks-0.7.0 → patchworks-0.8.0}/tests/test_ome_zarr.py +11 -0
  5. {patchworks-0.7.0 → patchworks-0.8.0}/.github/workflows/docs.yml +0 -0
  6. {patchworks-0.7.0 → patchworks-0.8.0}/.github/workflows/lint.yml +0 -0
  7. {patchworks-0.7.0 → patchworks-0.8.0}/.github/workflows/release.yml +0 -0
  8. {patchworks-0.7.0 → patchworks-0.8.0}/.gitignore +0 -0
  9. {patchworks-0.7.0 → patchworks-0.8.0}/README.md +0 -0
  10. {patchworks-0.7.0 → patchworks-0.8.0}/cliff.toml +0 -0
  11. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/chunks.md +0 -0
  12. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/cluster.md +0 -0
  13. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/io.md +0 -0
  14. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/merge_tile_labels.md +0 -0
  15. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/plugins/cellpose.md +0 -0
  16. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/plugins/napari.md +0 -0
  17. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/plugins/ome_zarr.md +0 -0
  18. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/relabel.md +0 -0
  19. {patchworks-0.7.0 → patchworks-0.8.0}/docs/api/tile_process.md +0 -0
  20. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/cellpose_2d.md +0 -0
  21. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/cellpose_2d.py +0 -0
  22. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/cellpose_3d.md +0 -0
  23. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/cellpose_3d.py +0 -0
  24. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/custom.md +0 -0
  25. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/custom_method.py +0 -0
  26. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/standalone_merge.md +0 -0
  27. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/stardist.md +0 -0
  28. {patchworks-0.7.0 → patchworks-0.8.0}/docs/examples/stardist_2d.py +0 -0
  29. {patchworks-0.7.0 → patchworks-0.8.0}/docs/getting_started.md +0 -0
  30. {patchworks-0.7.0 → patchworks-0.8.0}/docs/guide/gpu_distributed.md +0 -0
  31. {patchworks-0.7.0 → patchworks-0.8.0}/docs/guide/merging.md +0 -0
  32. {patchworks-0.7.0 → patchworks-0.8.0}/docs/guide/performance.md +0 -0
  33. {patchworks-0.7.0 → patchworks-0.8.0}/docs/guide/pitfalls.md +0 -0
  34. {patchworks-0.7.0 → patchworks-0.8.0}/docs/guide/skip_empty.md +0 -0
  35. {patchworks-0.7.0 → patchworks-0.8.0}/docs/guide/tiling.md +0 -0
  36. {patchworks-0.7.0 → patchworks-0.8.0}/docs/index.md +0 -0
  37. {patchworks-0.7.0 → patchworks-0.8.0}/mkdocs.yml +0 -0
  38. {patchworks-0.7.0 → patchworks-0.8.0}/pyproject.toml +0 -0
  39. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/__init__.py +0 -0
  40. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/_chunks.py +0 -0
  41. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/_cluster.py +0 -0
  42. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/_core.py +0 -0
  43. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/_io.py +0 -0
  44. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/_merge.py +0 -0
  45. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/_relabel.py +0 -0
  46. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/plugins/__init__.py +0 -0
  47. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/plugins/cellpose.py +0 -0
  48. {patchworks-0.7.0 → patchworks-0.8.0}/src/patchworks/plugins/napari.py +0 -0
  49. {patchworks-0.7.0 → patchworks-0.8.0}/tests/test_core.py +0 -0
  50. {patchworks-0.7.0 → patchworks-0.8.0}/tests/test_napari.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchworks
3
- Version: 0.7.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,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 are rebuilt, not reused"
54
- `.ims` files carry their own resolution pyramid, but `to_ome_zarr` reads
55
- only the **full-resolution** level and **builds a fresh NGFF pyramid** from
56
- it. This guarantees a consistent pyramid (XY-only, nearest-neighbour,
57
- calibrated) rather than inheriting Imaris's own downsampling scheme. It
58
- costs some extra compute, but the build is lazy and OOM-safe.
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`` file lazily → ``(array, axes, pixel_size)``."""
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
- # Full-resolution level; the object is array-like and h5py-backed (lazy).
269
- reader = ims(path, ResolutionLevelLock=0)
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(
@@ -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