patchworks 2.6.0__tar.gz → 2.6.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.
Files changed (101) hide show
  1. {patchworks-2.6.0 → patchworks-2.6.1}/PKG-INFO +1 -1
  2. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/snakemake.md +4 -3
  3. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_chunks.py +22 -2
  4. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_core.py +29 -0
  5. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/config/config_cyto.yaml +5 -3
  6. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/scripts/prepare_tiles.py +9 -1
  7. {patchworks-2.6.0 → patchworks-2.6.1}/.github/workflows/docs.yml +0 -0
  8. {patchworks-2.6.0 → patchworks-2.6.1}/.github/workflows/lint.yml +0 -0
  9. {patchworks-2.6.0 → patchworks-2.6.1}/.github/workflows/release.yml +0 -0
  10. {patchworks-2.6.0 → patchworks-2.6.1}/.gitignore +0 -0
  11. {patchworks-2.6.0 → patchworks-2.6.1}/.markdownlint-cli2.yaml +0 -0
  12. {patchworks-2.6.0 → patchworks-2.6.1}/LICENSE +0 -0
  13. {patchworks-2.6.0 → patchworks-2.6.1}/README.md +0 -0
  14. {patchworks-2.6.0 → patchworks-2.6.1}/cliff.toml +0 -0
  15. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/chunks.md +0 -0
  16. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/cluster.md +0 -0
  17. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/io.md +0 -0
  18. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/merge_tile_labels.md +0 -0
  19. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/plugins/cellpose.md +0 -0
  20. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/plugins/dog.md +0 -0
  21. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/plugins/napari.md +0 -0
  22. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/plugins/ome_zarr.md +0 -0
  23. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/postprocess.md +0 -0
  24. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/relabel.md +0 -0
  25. {patchworks-2.6.0 → patchworks-2.6.1}/docs/api/tile_process.md +0 -0
  26. {patchworks-2.6.0 → patchworks-2.6.1}/docs/assets/logo.png +0 -0
  27. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/cellpose_2d.md +0 -0
  28. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/cellpose_2d.py +0 -0
  29. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/cellpose_3d.md +0 -0
  30. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/cellpose_3d.py +0 -0
  31. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/custom.md +0 -0
  32. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/custom_method.py +0 -0
  33. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/dog.md +0 -0
  34. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/dog.py +0 -0
  35. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/standalone_merge.md +0 -0
  36. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/stardist.md +0 -0
  37. {patchworks-2.6.0 → patchworks-2.6.1}/docs/examples/stardist_2d.py +0 -0
  38. {patchworks-2.6.0 → patchworks-2.6.1}/docs/getting_started.md +0 -0
  39. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/custom_segmentation.md +0 -0
  40. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/gpu_distributed.md +0 -0
  41. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/label_relations.md +0 -0
  42. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/measurements.md +0 -0
  43. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/merging.md +0 -0
  44. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/ome_zarr_napari.md +0 -0
  45. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/performance.md +0 -0
  46. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/pitfalls.md +0 -0
  47. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/skip_empty.md +0 -0
  48. {patchworks-2.6.0 → patchworks-2.6.1}/docs/guide/tiling.md +0 -0
  49. {patchworks-2.6.0 → patchworks-2.6.1}/docs/index.md +0 -0
  50. {patchworks-2.6.0 → patchworks-2.6.1}/mkdocs.yml +0 -0
  51. {patchworks-2.6.0 → patchworks-2.6.1}/pyproject.toml +0 -0
  52. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/__init__.py +0 -0
  53. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_cluster.py +0 -0
  54. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_core.py +0 -0
  55. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_distributed.py +0 -0
  56. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_gpu.py +0 -0
  57. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_io.py +0 -0
  58. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_merge.py +0 -0
  59. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_notify.py +0 -0
  60. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_occupancy.py +0 -0
  61. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_postprocess.py +0 -0
  62. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_progress.py +0 -0
  63. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_relabel.py +0 -0
  64. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/_relations.py +0 -0
  65. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/plugins/__init__.py +0 -0
  66. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/plugins/cellpose.py +0 -0
  67. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/plugins/dog.py +0 -0
  68. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/plugins/napari.py +0 -0
  69. {patchworks-2.6.0 → patchworks-2.6.1}/src/patchworks/plugins/ome_zarr.py +0 -0
  70. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_allocation.py +0 -0
  71. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_distributed.py +0 -0
  72. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_dog.py +0 -0
  73. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_gpu.py +0 -0
  74. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_napari.py +0 -0
  75. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_notify.py +0 -0
  76. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_occupancy.py +0 -0
  77. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_ome_zarr.py +0 -0
  78. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_postprocess.py +0 -0
  79. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_progress.py +0 -0
  80. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_relations.py +0 -0
  81. {patchworks-2.6.0 → patchworks-2.6.1}/tests/test_run_multi.py +0 -0
  82. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/README.md +0 -0
  83. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/Snakefile +0 -0
  84. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/config/common.yaml +0 -0
  85. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/config/config.yaml +0 -0
  86. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/config/config_cilia.yaml +0 -0
  87. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/config/config_nuclei.yaml +0 -0
  88. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/config/multi.yaml +0 -0
  89. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/pixi.toml +0 -0
  90. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/profile/slurm/config.yaml +0 -0
  91. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/rules/common.smk +0 -0
  92. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/rules/convert.smk +0 -0
  93. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/rules/merge.smk +0 -0
  94. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/rules/segment.smk +0 -0
  95. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/scripts/_pw.py +0 -0
  96. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/scripts/build_occupancy.py +0 -0
  97. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/scripts/convert.py +0 -0
  98. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/scripts/fetch_model.py +0 -0
  99. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/scripts/merge.py +0 -0
  100. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/scripts/run_multi.py +0 -0
  101. {patchworks-2.6.0 → patchworks-2.6.1}/workflow/scripts/segment_tile.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchworks
3
- Version: 2.6.0
3
+ Version: 2.6.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
@@ -321,9 +321,10 @@ occupancy map and the staged labels are byte-for-byte what a single-channel
321
321
  run produces, and `merge` and `label_relations` need no changes. Two things
322
322
  follow from that:
323
323
 
324
- - A tile holds twice the bytes, so a hand-set `tile_shape` sized to fill a GPU
325
- may need halving. `tile_shape: "auto"` sizes from the single-channel array
326
- and does not yet know about the pair.
324
+ - A tile holds twice the bytes. `tile_shape: "auto"` accounts for this it is
325
+ told the tile carries two channels and shrinks each spatial side by ~1/√2,
326
+ so the tile still fits the same VRAM budget. A **hand-set** `tile_shape`
327
+ sized to fill a GPU has no such protection and needs halving yourself.
327
328
  - The translation is version-specific. Cellpose 3 gets `channels: [1, 2]`
328
329
  (1-based into the channel axis, `0` = grayscale); Cellpose 4 (cpsam) dropped
329
330
  `channels` entirely and simply reads both. Either is overridable by setting
@@ -264,6 +264,7 @@ def auto_tile_shape(
264
264
  gpu_memory: int | None = None,
265
265
  available_memory: int | None = None,
266
266
  n_workers: int | None = None,
267
+ n_channels: int = 1,
267
268
  verbose: bool = False,
268
269
  ) -> tuple[int, ...]:
269
270
  """Balanced tile shape for general-purpose 3-D processing.
@@ -287,6 +288,10 @@ def auto_tile_shape(
287
288
  Available host RAM in bytes; auto-queried when None.
288
289
  n_workers:
289
290
  Number of parallel workers (divides the RAM budget).
291
+ n_channels:
292
+ Channels each tile carries (default 1). Above 1 the per-voxel cost
293
+ scales with it, so the tile shrinks accordingly -- e.g. the workflow's
294
+ ``nuclei_channel`` hands Cellpose a cyto+nuclei pair.
290
295
  verbose:
291
296
  Log the chosen shape and estimated tile size.
292
297
 
@@ -302,7 +307,12 @@ def auto_tile_shape(
302
307
  (128, 512, 512)
303
308
  """
304
309
  n_workers = n_workers or cpu_allocation()
305
- itemsize = np.dtype(dtype).itemsize
310
+ # A tile holds n_channels planes per voxel (e.g. Cellpose's
311
+ # cyto+nuclei pair), so the per-voxel cost -- and every budget
312
+ # derived from it below -- scales with them.
313
+ if n_channels < 1:
314
+ raise ValueError(f"n_channels must be >= 1; got {n_channels!r}")
315
+ itemsize = np.dtype(dtype).itemsize * n_channels
306
316
  n_spatial = min(3, len(shape))
307
317
 
308
318
  if use_gpu:
@@ -359,6 +369,7 @@ def auto_tile_shape_cellpose(
359
369
  n_workers: int | None = None,
360
370
  model_memory_bytes: int = 2 * 1024**3,
361
371
  cellpose_memory_factor: int = 20,
372
+ n_channels: int = 1,
362
373
  verbose: bool = False,
363
374
  ) -> tuple[int, ...]:
364
375
  """Cellpose-optimised tile shape.
@@ -391,6 +402,10 @@ def auto_tile_shape_cellpose(
391
402
  Memory consumed by the Cellpose model weights (default 2 GiB).
392
403
  cellpose_memory_factor:
393
404
  Cellpose allocates roughly this multiple of raw input bytes (default 20×).
405
+ n_channels:
406
+ Channels each tile carries (default 1). Above 1 the per-voxel cost
407
+ scales with it, so the tile shrinks accordingly -- e.g. the workflow's
408
+ ``nuclei_channel`` hands Cellpose a cyto+nuclei pair.
394
409
  verbose:
395
410
  Log the chosen shape and memory estimates.
396
411
 
@@ -406,7 +421,12 @@ def auto_tile_shape_cellpose(
406
421
  (1, 2048, 2048)
407
422
  """
408
423
  n_workers = n_workers or cpu_allocation()
409
- itemsize = np.dtype(dtype).itemsize
424
+ # A tile holds n_channels planes per voxel (e.g. Cellpose's
425
+ # cyto+nuclei pair), so the per-voxel cost -- and every budget
426
+ # derived from it below -- scales with them.
427
+ if n_channels < 1:
428
+ raise ValueError(f"n_channels must be >= 1; got {n_channels!r}")
429
+ itemsize = np.dtype(dtype).itemsize * n_channels
410
430
 
411
431
  if use_gpu:
412
432
  total_mem = gpu_memory if gpu_memory is not None else _get_gpu_memory()
@@ -389,3 +389,32 @@ def test_channel_selection_respects_the_stores_axes(tmp_path):
389
389
  store4 = str(tmp_path / "czyx.zarr")
390
390
  to_ome_zarr(vol4, store4, axes="czyx", n_levels=1, progress=False)
391
391
  assert load_ome_zarr(store4, channel=1, level=0).shape == (8, 64, 64)
392
+
393
+
394
+ def test_auto_tile_shape_charges_for_extra_channels():
395
+ """A 2-channel tile must fit the same byte budget, not twice it.
396
+
397
+ `nuclei_channel` doubles what a tile holds while the tile geometry stays
398
+ single-channel, so a sizer blind to it hands the GPU a tile needing twice
399
+ the VRAM it budgeted for.
400
+ """
401
+ import pytest
402
+
403
+ from patchworks import auto_tile_shape, auto_tile_shape_cellpose
404
+
405
+ shape, dtype = (128, 2048, 2048), "uint16"
406
+
407
+ one = auto_tile_shape(shape, dtype)
408
+ two = auto_tile_shape(shape, dtype, n_channels=2)
409
+ # Same bytes overall: 2 channels of roughly half the area each.
410
+ assert np.prod(two) * 2 <= np.prod(one)
411
+ assert np.prod(two) * 2 >= np.prod(one) * 0.9
412
+
413
+ kw = dict(diameter=30, do_3D=True, use_gpu=True, gpu_memory=24 * 1024**3)
414
+ cp_one = auto_tile_shape_cellpose(shape, dtype, **kw)
415
+ cp_two = auto_tile_shape_cellpose(shape, dtype, n_channels=2, **kw)
416
+ assert np.prod(cp_two) * 2 <= np.prod(cp_one)
417
+ assert cp_two[0] == cp_one[0] # do_3D still pins z to the full extent
418
+
419
+ with pytest.raises(ValueError, match="n_channels"):
420
+ auto_tile_shape(shape, dtype, n_channels=0)
@@ -14,9 +14,11 @@ channel: 0 # cytoplasm/membrane channel
14
14
  #
15
15
  # Only the segment step reads it: the tile geometry, the occupancy map and the
16
16
  # staged labels all stay exactly as they are for a single channel -- the pair
17
- # is stacked on a leading axis that is carried into each tile, not tiled. It
18
- # does double the bytes a tile holds, so a hand-set `tile_shape` sized to the
19
- # GPU may need halving.
17
+ # is stacked on a leading axis that is carried into each tile, not tiled.
18
+ #
19
+ # It does double the bytes a tile holds. `tile_shape: "auto"` is told about
20
+ # that and shrinks each spatial side by ~1/sqrt(2) to fit the same VRAM
21
+ # budget; the hand-set tile_shape in common.yaml is not, so halve it yourself.
20
22
  #
21
23
  # Cellpose 3 turns this into channels: [1, 2]; Cellpose 4 (cpsam) dropped
22
24
  # `channels` and just reads both. Override either by setting `channels:` or
@@ -44,6 +44,10 @@ if ts == "auto":
44
44
  # "GPU memory query failed" fallback). None => the built-in 8 GiB default.
45
45
  gpu_gb = cfg.get("gpu_memory_gb")
46
46
  gpu_bytes = int(gpu_gb * 1024**3) if gpu_gb else None
47
+ # `image` is single-channel here (the geometry is spatial), but segment
48
+ # reads nuclei_channel alongside it, so a tile costs twice the bytes.
49
+ # Without this the sizer would hand the GPU a tile it cannot hold.
50
+ n_channels = 2 if cfg.get("nuclei_channel") is not None else 1
47
51
  if method == "cellpose":
48
52
  cp = cfg["cellpose"]
49
53
  sizer = partial(
@@ -52,6 +56,7 @@ if ts == "auto":
52
56
  use_gpu=cp.get("gpu", True),
53
57
  diameter=cp.get("diameter"),
54
58
  gpu_memory=gpu_bytes,
59
+ n_channels=n_channels,
55
60
  )
56
61
  else:
57
62
  # cfg["cellpose"] used to be read unconditionally here, so a DoG or
@@ -59,7 +64,10 @@ if ts == "auto":
59
64
  # 'cellpose'. The Cellpose estimator's memory model wouldn't apply to
60
65
  # them anyway.
61
66
  sizer = partial(
62
- auto_tile_shape, use_gpu=gpu_bytes is not None, gpu_memory=gpu_bytes
67
+ auto_tile_shape,
68
+ use_gpu=gpu_bytes is not None,
69
+ gpu_memory=gpu_bytes,
70
+ n_channels=n_channels,
63
71
  )
64
72
  tile_shape = tuple(sizer(image.shape, image.dtype))
65
73
  else:
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