patchworks 1.2.2__tar.gz → 1.3.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 (86) hide show
  1. {patchworks-1.2.2 → patchworks-1.3.1}/PKG-INFO +1 -1
  2. patchworks-1.3.1/docs/api/postprocess.md +3 -0
  3. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/dog.md +18 -0
  4. {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/ome_zarr_napari.md +10 -0
  5. {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/snakemake.md +55 -6
  6. {patchworks-1.2.2 → patchworks-1.3.1}/mkdocs.yml +1 -0
  7. {patchworks-1.2.2 → patchworks-1.3.1}/pyproject.toml +6 -5
  8. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/__init__.py +2 -0
  9. patchworks-1.3.1/src/patchworks/_postprocess.py +101 -0
  10. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/napari.py +2 -2
  11. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/ome_zarr.py +4 -2
  12. patchworks-1.3.1/tests/test_postprocess.py +41 -0
  13. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/config.yaml +1 -0
  14. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/config_cilia.yaml +1 -0
  15. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/_pw.py +28 -1
  16. {patchworks-1.2.2 → patchworks-1.3.1}/.github/workflows/docs.yml +0 -0
  17. {patchworks-1.2.2 → patchworks-1.3.1}/.github/workflows/lint.yml +0 -0
  18. {patchworks-1.2.2 → patchworks-1.3.1}/.github/workflows/release.yml +0 -0
  19. {patchworks-1.2.2 → patchworks-1.3.1}/.gitignore +0 -0
  20. {patchworks-1.2.2 → patchworks-1.3.1}/.markdownlint-cli2.yaml +0 -0
  21. {patchworks-1.2.2 → patchworks-1.3.1}/LICENSE +0 -0
  22. {patchworks-1.2.2 → patchworks-1.3.1}/README.md +0 -0
  23. {patchworks-1.2.2 → patchworks-1.3.1}/cliff.toml +0 -0
  24. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/chunks.md +0 -0
  25. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/cluster.md +0 -0
  26. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/io.md +0 -0
  27. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/merge_tile_labels.md +0 -0
  28. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/plugins/cellpose.md +0 -0
  29. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/plugins/dog.md +0 -0
  30. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/plugins/napari.md +0 -0
  31. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/plugins/ome_zarr.md +0 -0
  32. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/relabel.md +0 -0
  33. {patchworks-1.2.2 → patchworks-1.3.1}/docs/api/tile_process.md +0 -0
  34. {patchworks-1.2.2 → patchworks-1.3.1}/docs/assets/logo.png +0 -0
  35. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/cellpose_2d.md +0 -0
  36. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/cellpose_2d.py +0 -0
  37. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/cellpose_3d.md +0 -0
  38. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/cellpose_3d.py +0 -0
  39. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/custom.md +0 -0
  40. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/custom_method.py +0 -0
  41. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/dog.py +0 -0
  42. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/standalone_merge.md +0 -0
  43. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/stardist.md +0 -0
  44. {patchworks-1.2.2 → patchworks-1.3.1}/docs/examples/stardist_2d.py +0 -0
  45. {patchworks-1.2.2 → patchworks-1.3.1}/docs/getting_started.md +0 -0
  46. {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/gpu_distributed.md +0 -0
  47. {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/merging.md +0 -0
  48. {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/performance.md +0 -0
  49. {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/pitfalls.md +0 -0
  50. {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/skip_empty.md +0 -0
  51. {patchworks-1.2.2 → patchworks-1.3.1}/docs/guide/tiling.md +0 -0
  52. {patchworks-1.2.2 → patchworks-1.3.1}/docs/index.md +0 -0
  53. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_chunks.py +0 -0
  54. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_cluster.py +0 -0
  55. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_core.py +0 -0
  56. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_distributed.py +0 -0
  57. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_io.py +0 -0
  58. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_merge.py +0 -0
  59. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_relabel.py +0 -0
  60. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/_relations.py +0 -0
  61. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/__init__.py +0 -0
  62. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/cellpose.py +0 -0
  63. {patchworks-1.2.2 → patchworks-1.3.1}/src/patchworks/plugins/dog.py +0 -0
  64. {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_core.py +0 -0
  65. {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_distributed.py +0 -0
  66. {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_dog.py +0 -0
  67. {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_napari.py +0 -0
  68. {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_ome_zarr.py +0 -0
  69. {patchworks-1.2.2 → patchworks-1.3.1}/tests/test_relations.py +0 -0
  70. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/README.md +0 -0
  71. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/Snakefile +0 -0
  72. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/config_cyto.yaml +0 -0
  73. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/config_nuclei.yaml +0 -0
  74. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/config/multi.yaml +0 -0
  75. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/pixi.toml +0 -0
  76. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/profile/slurm/config.yaml +0 -0
  77. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/rules/common.smk +0 -0
  78. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/rules/convert.smk +0 -0
  79. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/rules/merge.smk +0 -0
  80. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/rules/segment.smk +0 -0
  81. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/convert.py +0 -0
  82. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/fetch_model.py +0 -0
  83. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/merge.py +0 -0
  84. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/prepare_tiles.py +0 -0
  85. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/run_multi.py +0 -0
  86. {patchworks-1.2.2 → patchworks-1.3.1}/workflow/scripts/segment_tile.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchworks
3
- Version: 1.2.2
3
+ Version: 1.3.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
@@ -0,0 +1,3 @@
1
+ # dilate_labels
2
+
3
+ ::: patchworks.dilate_labels
@@ -81,6 +81,24 @@ result = tile_process(IMAGE, fn, tile_shape=(1, 1024, 1024), overlap=32)
81
81
  so edge tiles keep enough context (a plain intensity/threshold halo is
82
82
  too thin).
83
83
 
84
+ ## Growing the labels afterwards
85
+
86
+ DoG spots/threads are often thin — grow each label by a few pixels with
87
+ [`dilate_labels`](../api/postprocess.md):
88
+
89
+ ```python
90
+ from patchworks import tile_process, dilate_labels
91
+ from patchworks.plugins.dog import dog_label_fn
92
+
93
+ fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02)
94
+ fn = dilate_labels(fn, iterations=2)
95
+ tile_process(IMAGE, fn, tile_shape=(1, 1024, 1024), overlap=8, write_to=OUTPUT)
96
+ ```
97
+
98
+ On the cluster, set `dilate: 2` in the YAML config instead — it applies to
99
+ `method: "custom"` (this plugin) the same way it does for `cellpose`/
100
+ `threshold`, see [Growing labels after segmentation](../guide/snakemake.md#growing-labels-afterwards-dilation).
101
+
84
102
  ## Using it in the Snakemake workflow
85
103
 
86
104
  No dedicated wiring needed — `patchworks.plugins.dog` exposes a `segment(tile, **kwargs)`
@@ -152,6 +152,16 @@ just one channel instead: `view_in_napari("scan.zarr", channel=0)`.
152
152
  napari ships in `patchworks[all]`, or install just it with
153
153
  `pip install "patchworks[napari]"`.
154
154
 
155
+ !!! tip "Measuring all the objects"
156
+ Once labels are loaded, use
157
+ [napari-chunked-regionprops](https://github.com/imcf/napari-chunked-regionprops)'s
158
+ "Measure" dock widget for area/centroid/intensity stats — it works
159
+ out-of-core straight off the Labels layer's backing dask/zarr array, so
160
+ it scales to the same huge label images `tile_process` writes, unlike
161
+ plain `skimage.measure.regionprops`. Bundled in `patchworks[napari]`. See
162
+ [Measurements](snakemake.md#measurements-fast-whole-volume-regionprops)
163
+ for the non-interactive/headless equivalent.
164
+
155
165
  ## End-to-end
156
166
 
157
167
  ```python
@@ -62,8 +62,9 @@ skip_empty: true # skip background tiles
62
62
  empty_threshold: null # null → Otsu
63
63
 
64
64
  # segmentation
65
- method: "cellpose" # "cellpose" (GPU) or "threshold" (no GPU)
65
+ method: "cellpose" # "cellpose" (GPU), "threshold" (no GPU), "custom"
66
66
  label_name: "cellpose" # name under image.zarr/labels/
67
+ dilate: 0 # optional: pixels to grow labels by, any method
67
68
  cellpose:
68
69
  model: "cyto3"
69
70
  diameter: 30
@@ -77,6 +78,18 @@ pyramid_downscale: 2
77
78
  sequential_labels: true # renumber labels to a contiguous 1..N
78
79
  ```
79
80
 
81
+ !!! tip "Growing labels after segmentation"
82
+ `dilate: N` grows every label by `N` pixels once segmentation finishes,
83
+ regardless of `method` (`cellpose`, `threshold`, or `custom`). It runs
84
+ per-tile, before the overlap halo is trimmed and tiles are merged, so
85
+ dilated labels still stitch correctly across tile boundaries — just make
86
+ sure `overlap` covers the dilation amount plus the usual object-diameter
87
+ halo. `0` (default) disables it. Under the hood this wraps whatever
88
+ segmentation function `method` builds with
89
+ [`patchworks.dilate_labels`](../api/postprocess.md); see [Custom
90
+ segmentation function](#custom-segmentation-function) below for using it
91
+ directly from Python instead of via config.
92
+
80
93
  !!! tip "Tile size vs runtime"
81
94
  `tile_shape: "auto"` sizes each tile to your GPU's VRAM. Smaller tiles =
82
95
  more (faster) jobs; very large 3-D tiles are slow. Keep `do_3D: false` (2-D
@@ -348,10 +361,20 @@ needs `pip install "patchworks[dog]"` in the segment jobs' environment.
348
361
  ## Measurements (fast, whole-volume regionprops)
349
362
 
350
363
  `skimage.measure.regionprops` needs the full labelled + intensity array in
351
- RAM — fine for one tile, not for a hundred-thousand-object OME-ZARR. Use
352
- [`dask-image`](https://image.dask.org)'s `ndmeasure`, which computes directly
353
- on the dask/zarr-backed arrays, chunk-parallel, without materializing the
354
- volume:
364
+ RAM — fine for one tile, not for a hundred-thousand-object OME-ZARR.
365
+
366
+ **Interactively, in napari**, this is what
367
+ [napari-chunked-regionprops](https://github.com/imcf/napari-chunked-regionprops)
368
+ is for — its "Measure" dock widget computes area/centroid/intensity stats
369
+ directly off a Labels layer's dask/zarr-backed array, out-of-core, and scales
370
+ with chunk count rather than object count. It's the best fit for measuring
371
+ *every* object in a store this size, not just a cropped region — see
372
+ [View image + labels in napari](ome_zarr_napari.md#view-image--labels-in-napari).
373
+ Bundled in `patchworks[napari]`.
374
+
375
+ **Headless/scripted**, use [`dask-image`](https://image.dask.org)'s
376
+ `ndmeasure`, which computes directly on the dask/zarr-backed arrays,
377
+ chunk-parallel, without materializing the volume:
355
378
 
356
379
  ```bash
357
380
  pip install dask-image
@@ -379,7 +402,10 @@ For interactively inspecting individual cells by clicking in the viewer
379
402
  (not all objects at once), the
380
403
  [napari-skimage-regionprops](https://github.com/haesleinhuepf/napari-skimage-regionprops)
381
404
  plugin's table widget works well — point it at a cropped region rather than
382
- the full volume, since it loads its input fully into memory.
405
+ the full volume, since it loads its input fully into memory. For measuring
406
+ *all* objects at once, use
407
+ [napari-chunked-regionprops](https://github.com/imcf/napari-chunked-regionprops)
408
+ instead (see above) — it doesn't have this in-memory limitation.
383
409
 
384
410
  ## Custom segmentation function
385
411
 
@@ -433,6 +459,29 @@ custom:
433
459
  sigma: 1.5
434
460
  ```
435
461
 
462
+ ### Growing labels afterwards (dilation)
463
+
464
+ To grow every label by a few pixels after segmentation — any method, not
465
+ just `custom` — set `dilate: N` in the config (see the tip above), or wrap
466
+ your function directly with
467
+ [`patchworks.dilate_labels`](../api/postprocess.md) when calling the API
468
+ yourself:
469
+
470
+ ```python
471
+ from patchworks import tile_process, dilate_labels
472
+ from patchworks.plugins.dog import dog_label_fn
473
+
474
+ fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02)
475
+ fn = dilate_labels(fn, iterations=2) # grow each label by 2 px, then run
476
+ result = tile_process("image.zarr", fn, tile_shape=(1, 2048, 2048),
477
+ overlap=8, write_to="labels.zarr")
478
+ ```
479
+
480
+ `dilate_labels` wraps any `(tile) -> labels` function — the same contract
481
+ described above — so it works with `dog_label_fn`, `cellpose_fn`, or your
482
+ own `segment`. It dilates each tile's labels before the halo is trimmed and
483
+ tiles are merged, so `overlap` must still cover the dilation amount.
484
+
436
485
  ### Real example: StarDist 3-D, with model caching
437
486
 
438
487
  Heavy models must be loaded **once**, not per tile. On SLURM each tile is its
@@ -54,6 +54,7 @@ nav:
54
54
  - API Reference:
55
55
  - tile_process: api/tile_process.md
56
56
  - merge_tile_labels: api/merge_tile_labels.md
57
+ - dilate_labels: api/postprocess.md
57
58
  - Tile sizing: api/chunks.md
58
59
  - I/O helpers: api/io.md
59
60
  - Relabelling: api/relabel.md
@@ -72,11 +72,12 @@ imaris = ["imaris-ims-file-reader"]
72
72
  # - lxml-html-clean: napari's notebook_display imports lxml.html.clean, split
73
73
  # into a separate package in lxml >= 5.2 (else ImportError on Viewer()).
74
74
  # - glasbey: distinct high-contrast label LUTs for view_in_napari.
75
- # - napari-chunked-regionprops: out-of-core regionprops-style measurements
76
- # (area/centroid/intensity stats) for huge Labels layers, straight off
77
- # their backing dask/zarr arrays the "Measure" dock widget. Formerly
78
- # napari-dask-ndmeasure; renamed when its engine dropped dask_image.ndmeasure
79
- # for a chunk-local map/merge that scales with chunk count, not object count.
75
+ # - napari-chunked-regionprops (https://github.com/imcf/napari-chunked-regionprops):
76
+ # out-of-core regionprops-style measurements (area/centroid/intensity
77
+ # stats) for huge Labels layers, straight off their backing dask/zarr
78
+ # arrays the "Measure" dock widget. Formerly napari-dask-ndmeasure;
79
+ # renamed when its engine dropped dask_image.ndmeasure for a chunk-local
80
+ # map/merge that scales with chunk count, not object count.
80
81
  # - pyqt6 < 6.10: PyQt6-Qt6 6.10.2's Windows wheel fails to import (`DLL load
81
82
  # failed while importing QtWidgets: The specified procedure could not be
82
83
  # found`) — confirmed unrelated to napari/qtpy, reproduces on a bare
@@ -35,6 +35,7 @@ from ._core import tile_process
35
35
  from ._distributed import create_stage, spatial_tiles, stage_tile
36
36
  from ._io import estimate_empty_tiles, load_ome_zarr
37
37
  from ._merge import merge_tile_labels
38
+ from ._postprocess import dilate_labels
38
39
  from ._relabel import relabel_sequential_array, relabel_sequential_zarr
39
40
  from ._relations import label_relations
40
41
 
@@ -57,4 +58,5 @@ __all__ = [
57
58
  "spatial_tiles",
58
59
  "create_stage",
59
60
  "stage_tile",
61
+ "dilate_labels",
60
62
  ]
@@ -0,0 +1,101 @@
1
+ """Generic post-segmentation wrappers for patchworks.
2
+
3
+ These wrap any ``fn(tile) -> labels`` callable (a plugin, a custom function,
4
+ whatever ``method`` in the Snakemake workflow builds) so the same
5
+ post-processing applies regardless of which segmentation method produced the
6
+ labels.
7
+
8
+ Usage
9
+ -----
10
+ >>> from patchworks import tile_process, dilate_labels
11
+ >>> from patchworks.plugins.dog import dog_label_fn
12
+ >>>
13
+ >>> fn = dog_label_fn(low_sigma=1.0, high_sigma=3.0, threshold=0.02)
14
+ >>> fn = dilate_labels(fn, iterations=2)
15
+ >>> result = tile_process("image.zarr", fn, tile_shape=(1, 2048, 2048),
16
+ ... overlap=8, write_to="labels.zarr")
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from functools import partial
22
+ from typing import Callable
23
+
24
+ import numpy as np
25
+
26
+
27
+ def dilate_labels(
28
+ fn: Callable[[np.ndarray], np.ndarray],
29
+ iterations: int = 1,
30
+ *,
31
+ use_gpu: bool = False,
32
+ ) -> Callable[[np.ndarray], np.ndarray]:
33
+ """Wrap a segmentation callable to grow its labels after each tile.
34
+
35
+ Applies a single-pass grey dilation to whatever ``fn`` returns, before
36
+ ``tile_process``/``stage_tile`` trim the overlap halo and merge across
37
+ tile boundaries — so dilated labels still stitch correctly at tile
38
+ edges.
39
+
40
+ Parameters
41
+ ----------
42
+ fn : Callable[[np.ndarray], np.ndarray]
43
+ Any segmentation function with the ``tile_process``/``stage_tile``
44
+ contract (one tile in, integer label array out).
45
+ iterations : int, optional
46
+ Pixels to grow each label by (grey-dilation footprint size
47
+ ``2 * iterations + 1``, single pass). Default 1. Values ``<= 0``
48
+ disable dilation — ``fn`` is returned unwrapped.
49
+ use_gpu : bool, optional
50
+ Dilate via cupyx instead of scipy. Independent of whatever backend
51
+ ``fn`` itself uses internally.
52
+
53
+ Returns
54
+ -------
55
+ Callable[[np.ndarray], np.ndarray]
56
+ Picklable function ready for ``tile_process``/``stage_tile``. If
57
+ ``iterations <= 0``, this is ``fn`` itself.
58
+ """
59
+ if iterations <= 0:
60
+ return fn
61
+ return partial(_run, fn=fn, iterations=iterations, use_gpu=use_gpu)
62
+
63
+
64
+ def _run(
65
+ block: np.ndarray,
66
+ fn: Callable[[np.ndarray], np.ndarray],
67
+ iterations: int,
68
+ use_gpu: bool,
69
+ ) -> np.ndarray:
70
+ """Run ``fn`` on ``block``, then grow the resulting labels.
71
+
72
+ Parameters
73
+ ----------
74
+ block : np.ndarray
75
+ One image tile.
76
+ fn : Callable[[np.ndarray], np.ndarray]
77
+ Segmentation function to run first.
78
+ iterations : int
79
+ Pixels to grow each label by.
80
+ use_gpu : bool
81
+ Dilate via cupyx instead of scipy.
82
+
83
+ Returns
84
+ -------
85
+ np.ndarray
86
+ Dilated integer label array, same shape as ``fn``'s output.
87
+ """
88
+ labels = fn(block)
89
+ size = 2 * iterations + 1
90
+
91
+ if use_gpu:
92
+ import cupy as cp
93
+ from cupyx.scipy.ndimage import grey_dilation
94
+
95
+ labels = cp.asnumpy(grey_dilation(cp.asarray(labels), size=size))
96
+ else:
97
+ from scipy.ndimage import grey_dilation
98
+
99
+ labels = grey_dilation(labels, size=size)
100
+
101
+ return labels
@@ -187,8 +187,8 @@ def _label_hint(path: Union[str, Path]) -> dict[str, Any]:
187
187
  during the merge) — the exact id set is then ``range(1, n_objects +
188
188
  1)`` by construction, with no scan needed. Passed through as a Labels
189
189
  layer's ``metadata`` so a downstream consumer (e.g.
190
- napari-chunked-regionprops) can use it instead of re-deriving the id
191
- set from the array itself.
190
+ napari-chunked-regionprops, https://github.com/imcf/napari-chunked-regionprops)
191
+ can use it instead of re-deriving the id set from the array itself.
192
192
 
193
193
  Parameters
194
194
  ----------
@@ -1083,8 +1083,10 @@ def register_labels(
1083
1083
  ``sequential_labels=True``, which means ``ids == range(1, n_objects
1084
1084
  + 1)`` by construction). When given, written into the label group's
1085
1085
  attrs as ``n_objects``/``sequential_labels`` so a downstream reader
1086
- (e.g. napari-chunked-regionprops) can use the known id set instead
1087
- of re-deriving it with a full-volume scan of its own.
1086
+ (e.g. napari-chunked-regionprops,
1087
+ https://github.com/imcf/napari-chunked-regionprops) can use the
1088
+ known id set instead of re-deriving it with a full-volume scan of
1089
+ its own.
1088
1090
 
1089
1091
  Returns
1090
1092
  -------
@@ -0,0 +1,41 @@
1
+ """Self-contained tests for the dilate_labels post-processing wrapper."""
2
+
3
+ import pickle
4
+
5
+ import numpy as np
6
+
7
+
8
+ def _make_blob_labels(shape=(1, 64, 64)):
9
+ labels = np.zeros(shape, dtype="int32")
10
+ labels[0, 28:36, 28:36] = 1
11
+ return labels
12
+
13
+
14
+ def test_dilate_labels_grows_mask():
15
+ from patchworks import dilate_labels
16
+
17
+ fn = lambda tile: _make_blob_labels(tile.shape) # noqa: E731
18
+ plain = fn(np.zeros((1, 64, 64)))
19
+ dilated = dilate_labels(fn, iterations=2)(np.zeros((1, 64, 64)))
20
+
21
+ assert (dilated > 0).sum() > (plain > 0).sum()
22
+
23
+
24
+ def test_dilate_labels_zero_iterations_is_noop():
25
+ from patchworks import dilate_labels
26
+
27
+ fn = lambda tile: _make_blob_labels(tile.shape) # noqa: E731
28
+
29
+ assert dilate_labels(fn, iterations=0) is fn
30
+
31
+
32
+ def test_dilate_labels_picklable():
33
+ from patchworks.plugins.dog import dog_label_fn
34
+
35
+ from patchworks import dilate_labels
36
+
37
+ fn = dilate_labels(
38
+ dog_label_fn(low_sigma=1.0, high_sigma=4.0, threshold=0.01),
39
+ iterations=2,
40
+ )
41
+ pickle.loads(pickle.dumps(fn))
@@ -30,6 +30,7 @@ empty_threshold: null # null → Otsu; or a number
30
30
 
31
31
  # ---- segmentation -----------------------------------------------------------
32
32
  method: "cellpose" # "cellpose" (GPU), "threshold" (no GPU; testing), "custom"
33
+ # dilate: 2 # optional: pixels to grow labels by after segmentation, any method
33
34
  # Also namespaces this run's intermediate files under work_dir/<label_name>/,
34
35
  # so a second segmentation (different label_name, e.g. nuclei vs cytoplasm)
35
36
  # can safely target the same work_dir — see docs/guide/snakemake.md
@@ -25,6 +25,7 @@ skip_empty: true
25
25
  empty_threshold: null
26
26
 
27
27
  method: "custom"
28
+ # dilate: 2 # optional: pixels to grow labels by after segmentation
28
29
  label_name: "cilia_labels"
29
30
  custom:
30
31
  module: "patchworks.plugins.dog"
@@ -133,7 +133,34 @@ def build_fn(cfg):
133
133
  cfg : dict
134
134
  Snakemake config. ``method`` selects ``"cellpose"`` (default), a simple
135
135
  ``"threshold"`` (testing / no-GPU), or ``"custom"`` to import your own
136
- function (``cfg["custom"] = {module, function, kwargs}``).
136
+ function (``cfg["custom"] = {module, function, kwargs}``). Optional
137
+ ``cfg["dilate"]``: int, pixels to grow labels by after segmentation
138
+ (via ``patchworks.dilate_labels``), applied regardless of ``method``.
139
+ Omitted/0 disables dilation.
140
+
141
+ Returns
142
+ -------
143
+ callable
144
+ ``(ndarray) -> ndarray`` returning integer labels.
145
+ """
146
+ fn = _build_method_fn(cfg)
147
+
148
+ dilate = cfg.get("dilate")
149
+ if dilate:
150
+ from patchworks import dilate_labels
151
+
152
+ fn = dilate_labels(fn, iterations=dilate)
153
+
154
+ return fn
155
+
156
+
157
+ def _build_method_fn(cfg):
158
+ """Build the per-tile segmentation function for ``cfg["method"]``.
159
+
160
+ Parameters
161
+ ----------
162
+ cfg : dict
163
+ Snakemake config, see :func:`build_fn`.
137
164
 
138
165
  Returns
139
166
  -------
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes