patchworks 3.0.0__tar.gz → 3.1.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 (154) hide show
  1. {patchworks-3.0.0 → patchworks-3.1.0}/.github/min-versions.txt +1 -0
  2. {patchworks-3.0.0 → patchworks-3.1.0}/PKG-INFO +16 -1
  3. {patchworks-3.0.0 → patchworks-3.1.0}/README.md +10 -0
  4. patchworks-3.1.0/docs/api/review.md +19 -0
  5. patchworks-3.1.0/docs/assets/review_panel.png +0 -0
  6. patchworks-3.1.0/docs/assets/review_position.png +0 -0
  7. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/cli.md +15 -0
  8. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/label_relations.md +8 -0
  9. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/measurements.md +20 -0
  10. patchworks-3.1.0/docs/guide/review.md +261 -0
  11. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/snakemake.md +22 -3
  12. {patchworks-3.0.0 → patchworks-3.1.0}/mkdocs.yml +2 -0
  13. {patchworks-3.0.0 → patchworks-3.1.0}/pyproject.toml +13 -3
  14. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/__init__.py +12 -0
  15. patchworks-3.1.0/src/patchworks/_review.py +1243 -0
  16. patchworks-3.1.0/src/patchworks/_tables.py +786 -0
  17. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/cli.py +170 -0
  18. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/napari.py +12 -9
  19. patchworks-3.1.0/src/patchworks/plugins/review.py +1056 -0
  20. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_cli.py +69 -0
  21. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_launcher.py +26 -1
  22. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_napari.py +9 -2
  23. patchworks-3.1.0/tests/test_position.py +194 -0
  24. patchworks-3.1.0/tests/test_review.py +168 -0
  25. patchworks-3.1.0/tests/test_review_napari.py +161 -0
  26. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_run_multi.py +138 -1
  27. patchworks-3.1.0/tests/test_tables.py +130 -0
  28. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/config.yaml +7 -0
  29. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/multi.yaml +23 -0
  30. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/app.py +83 -4
  31. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/launcher_core.py +25 -1
  32. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/_pw.py +13 -0
  33. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/merge.py +15 -0
  34. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/relate.py +71 -79
  35. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/run_multi.py +108 -0
  36. {patchworks-3.0.0 → patchworks-3.1.0}/.github/workflows/docs.yml +0 -0
  37. {patchworks-3.0.0 → patchworks-3.1.0}/.github/workflows/lint.yml +0 -0
  38. {patchworks-3.0.0 → patchworks-3.1.0}/.github/workflows/release.yml +0 -0
  39. {patchworks-3.0.0 → patchworks-3.1.0}/.github/workflows/test.yml +0 -0
  40. {patchworks-3.0.0 → patchworks-3.1.0}/.gitignore +0 -0
  41. {patchworks-3.0.0 → patchworks-3.1.0}/.markdownlint-cli2.yaml +0 -0
  42. {patchworks-3.0.0 → patchworks-3.1.0}/.pre-commit-config.yaml +0 -0
  43. {patchworks-3.0.0 → patchworks-3.1.0}/LICENSE +0 -0
  44. {patchworks-3.0.0 → patchworks-3.1.0}/benchmarks/bench.py +0 -0
  45. {patchworks-3.0.0 → patchworks-3.1.0}/benchmarks/compare.py +0 -0
  46. {patchworks-3.0.0 → patchworks-3.1.0}/cliff.toml +0 -0
  47. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/chunks.md +0 -0
  48. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/cluster.md +0 -0
  49. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/io.md +0 -0
  50. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/merge_tile_labels.md +0 -0
  51. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/plugins/cellpose.md +0 -0
  52. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/plugins/dog.md +0 -0
  53. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/plugins/napari.md +0 -0
  54. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/plugins/ome_zarr.md +0 -0
  55. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/postprocess.md +0 -0
  56. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/provenance.md +0 -0
  57. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/relabel.md +0 -0
  58. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/seams.md +0 -0
  59. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/tile_process.md +0 -0
  60. {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/volume_filter.md +0 -0
  61. {patchworks-3.0.0 → patchworks-3.1.0}/docs/assets/logo.png +0 -0
  62. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/cellpose_2d.md +0 -0
  63. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/cellpose_2d.py +0 -0
  64. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/cellpose_3d.md +0 -0
  65. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/cellpose_3d.py +0 -0
  66. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/custom.md +0 -0
  67. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/custom_method.py +0 -0
  68. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/dog.md +0 -0
  69. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/dog.py +0 -0
  70. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/standalone_merge.md +0 -0
  71. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/stardist.md +0 -0
  72. {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/stardist_2d.py +0 -0
  73. {patchworks-3.0.0 → patchworks-3.1.0}/docs/getting_started.md +0 -0
  74. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/custom_segmentation.md +0 -0
  75. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/gpu_distributed.md +0 -0
  76. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/launcher.md +0 -0
  77. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/merging.md +0 -0
  78. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/ome_zarr_napari.md +0 -0
  79. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/performance.md +0 -0
  80. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/pitfalls.md +0 -0
  81. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/skip_empty.md +0 -0
  82. {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/tiling.md +0 -0
  83. {patchworks-3.0.0 → patchworks-3.1.0}/docs/index.md +0 -0
  84. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_autotune.py +0 -0
  85. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_chunks.py +0 -0
  86. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_cluster.py +0 -0
  87. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_core.py +0 -0
  88. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_distributed.py +0 -0
  89. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_gpu.py +0 -0
  90. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_io.py +0 -0
  91. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_merge.py +0 -0
  92. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_notify.py +0 -0
  93. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_occupancy.py +0 -0
  94. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_postprocess.py +0 -0
  95. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_progress.py +0 -0
  96. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_provenance.py +0 -0
  97. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_relabel.py +0 -0
  98. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_relations.py +0 -0
  99. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_seams.py +0 -0
  100. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_volume_filter.py +0 -0
  101. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/__init__.py +0 -0
  102. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/cellpose.py +0 -0
  103. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/dog.py +0 -0
  104. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/ome_zarr.py +0 -0
  105. {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/py.typed +0 -0
  106. {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.4/image.schema +0 -0
  107. {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.4/label.schema +0 -0
  108. {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.4/ome.schema +0 -0
  109. {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.5/_version.schema +0 -0
  110. {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.5/image.schema +0 -0
  111. {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.5/label.schema +0 -0
  112. {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.5/ome.schema +0 -0
  113. {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/README.md +0 -0
  114. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_allocation.py +0 -0
  115. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_autotune.py +0 -0
  116. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_cellpose.py +0 -0
  117. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_core.py +0 -0
  118. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_distributed.py +0 -0
  119. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_dog.py +0 -0
  120. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_gpu.py +0 -0
  121. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_notify.py +0 -0
  122. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_occupancy.py +0 -0
  123. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_ome_zarr.py +0 -0
  124. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_postprocess.py +0 -0
  125. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_progress.py +0 -0
  126. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_pw.py +0 -0
  127. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_relations.py +0 -0
  128. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_remote.py +0 -0
  129. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_seams.py +0 -0
  130. {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_volume_filter.py +0 -0
  131. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/README.md +0 -0
  132. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/Snakefile +0 -0
  133. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/common.yaml +0 -0
  134. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/config_cilia.yaml +0 -0
  135. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/config_cyto.yaml +0 -0
  136. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/config_nuclei.yaml +0 -0
  137. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/README.md +0 -0
  138. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/clusters.yaml +0 -0
  139. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/requirements.txt +0 -0
  140. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/pixi.toml +0 -0
  141. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/profile/slurm/config.yaml +0 -0
  142. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/rules/common.smk +0 -0
  143. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/rules/convert.smk +0 -0
  144. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/rules/merge.smk +0 -0
  145. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/rules/segment.smk +0 -0
  146. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/build_occupancy.py +0 -0
  147. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/convert.py +0 -0
  148. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/export_iso.py +0 -0
  149. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/fetch_model.py +0 -0
  150. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/prepare_tiles.py +0 -0
  151. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/reshard_store.py +0 -0
  152. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/segment_tile.py +0 -0
  153. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/view.py +0 -0
  154. {patchworks-3.0.0 → patchworks-3.1.0}/workflow/viewer/pixi.toml +0 -0
@@ -4,3 +4,4 @@ dask==2024.10.0
4
4
  numpy==1.26.0
5
5
  zarr==3.1.3
6
6
  scipy==1.11.1
7
+ pandas==2.0.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: patchworks
3
- Version: 3.0.0
3
+ Version: 3.1.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
@@ -62,6 +62,7 @@ Provides-Extra: dev
62
62
  Requires-Dist: jsonschema>=4.18; extra == 'dev'
63
63
  Requires-Dist: mypy>=1.10; extra == 'dev'
64
64
  Requires-Dist: openpyxl; extra == 'dev'
65
+ Requires-Dist: pandas>=2.0; extra == 'dev'
65
66
  Requires-Dist: psutil; extra == 'dev'
66
67
  Requires-Dist: pytest; extra == 'dev'
67
68
  Requires-Dist: pytest-cov; extra == 'dev'
@@ -92,8 +93,12 @@ Requires-Dist: numpy<2.5; extra == 'napari'
92
93
  Requires-Dist: pyqt6<6.10; extra == 'napari'
93
94
  Provides-Extra: remote
94
95
  Requires-Dist: fsspec[gcs,http,s3]; extra == 'remote'
96
+ Provides-Extra: review
97
+ Requires-Dist: openpyxl; extra == 'review'
98
+ Requires-Dist: pandas>=2.0; extra == 'review'
95
99
  Provides-Extra: workflow
96
100
  Requires-Dist: openpyxl; extra == 'workflow'
101
+ Requires-Dist: pandas>=2.0; extra == 'workflow'
97
102
  Requires-Dist: snakemake-executor-plugin-slurm; extra == 'workflow'
98
103
  Requires-Dist: snakemake>=8; extra == 'workflow'
99
104
  Description-Content-Type: text/markdown
@@ -284,6 +289,16 @@ Pyramids downsample **X/Y only** (Z kept full-res) and are built level-by-level
284
289
  from disk, so terabyte volumes convert in bounded RAM. See the
285
290
  [OME-ZARR & napari guide](https://imcf.one/patchworks/guide/ome_zarr_napari/).
286
291
 
292
+ ### Check and correct the result
293
+
294
+ Every label image comes with an object table (size, position, which cell
295
+ each nucleus or cilium is in). `patchworks review scan.zarr` opens napari on
296
+ the objects most likely to be wrong — a cilium in no cell, a cell with two
297
+ nuclei, a nucleus cut in two at a tile seam — one at a time; one key
298
+ accepts, rejects, reassigns or joins. The corrections flow into the tables
299
+ and workbooks. See
300
+ [Reviewing and correcting results](https://imcf.one/patchworks/guide/review/).
301
+
287
302
  ---
288
303
 
289
304
  ## From the command line
@@ -184,6 +184,16 @@ Pyramids downsample **X/Y only** (Z kept full-res) and are built level-by-level
184
184
  from disk, so terabyte volumes convert in bounded RAM. See the
185
185
  [OME-ZARR & napari guide](https://imcf.one/patchworks/guide/ome_zarr_napari/).
186
186
 
187
+ ### Check and correct the result
188
+
189
+ Every label image comes with an object table (size, position, which cell
190
+ each nucleus or cilium is in). `patchworks review scan.zarr` opens napari on
191
+ the objects most likely to be wrong — a cilium in no cell, a cell with two
192
+ nuclei, a nucleus cut in two at a tile seam — one at a time; one key
193
+ accepts, rejects, reassigns or joins. The corrections flow into the tables
194
+ and workbooks. See
195
+ [Reviewing and correcting results](https://imcf.one/patchworks/guide/review/).
196
+
187
197
  ---
188
198
 
189
199
  ## From the command line
@@ -0,0 +1,19 @@
1
+ # Object tables and review
2
+
3
+ See [Reviewing and correcting results](../guide/review.md) for the workflow.
4
+
5
+ ::: patchworks.measure_objects
6
+
7
+ ::: patchworks.compute_table
8
+
9
+ ::: patchworks.relate_tables
10
+
11
+ ::: patchworks._tables.nearest_parents
12
+
13
+ ::: patchworks._tables.shape_columns
14
+
15
+ ::: patchworks.read_table
16
+
17
+ ::: patchworks.Review
18
+
19
+ ::: patchworks.plugins.review.review_in_napari
@@ -19,8 +19,23 @@ patchworks seams scan.zarr/labels/labels --tile-shape 16,1024,1024
19
19
 
20
20
  # Look at the result (needs patchworks[napari])
21
21
  patchworks view scan.zarr
22
+
23
+ # One row per object; which cell each nucleus is in
24
+ patchworks tables scan.zarr --relate nuclei:cells
25
+
26
+ # Look at the likely mistakes one by one and fix them (napari)
27
+ patchworks review scan.zarr --expect cells:nuclei=1
22
28
  ```
23
29
 
30
+ `patchworks review` without a window: `--summary` (counts and error
31
+ estimate), `--export DIR --format csv|xlsx|parquet` (corrected tables),
32
+ `--workbooks DIR` (relation workbooks), `--write-labels NAME` (a label image
33
+ with the corrections applied). `--position CHILD:PARENT=APICAL` classifies
34
+ children as apical/basal/lateral/central (APICAL: `+z`, or a label image to
35
+ point away from, such as the nuclei); `patchworks tables --max-distance UM`
36
+ gives a child touching no parent the nearest one. See
37
+ [Reviewing and correcting results](review.md).
38
+
24
39
  ## Segmentation methods
25
40
 
26
41
  | `--method` | What it does | Main flags |
@@ -53,6 +53,14 @@ with open("nuclei_to_cell.csv", "w", newline="") as f:
53
53
  w.writerow([nucleus_id, m["match"], m["overlap_voxels"], m["overlap_fraction"]])
54
54
  ```
55
55
 
56
+ To keep the result with the labels instead, as columns of the object
57
+ tables (the corrections made in
58
+ [`patchworks review`](review.md) then apply to it):
59
+
60
+ ```bash
61
+ patchworks tables results/image.zarr --relate nuclei_labels:cyto_labels
62
+ ```
63
+
56
64
  On the cluster, producing the two label stores in the first place is a
57
65
  matter of running the workflow twice against the same `work_dir` — see
58
66
  [Running two segmentations](snakemake.md#running-two-segmentations-eg-nuclei-cytoplasm).
@@ -3,6 +3,26 @@
3
3
  `skimage.measure.regionprops` needs the full labelled + intensity array in
4
4
  RAM — fine for one tile, not for a hundred-thousand-object OME-ZARR.
5
5
 
6
+ ## Already measured: the object tables
7
+
8
+ The workflow measures every object once, right after the merge: size
9
+ (`area_voxels`, `area_um3`), centroid, bounding box and spread (second
10
+ moments, from which the corrected view derives `length_um`, `elongation`
11
+ and the main axis), plus mean/std intensity for the channels listed in
12
+ `table_channels`. The table is stored
13
+ with the labels (`image.zarr/labels/<name>/table`):
14
+
15
+ ```python
16
+ from patchworks import read_table
17
+
18
+ cells = read_table("results/image.zarr/labels/cyto_labels") # pandas
19
+ ```
20
+
21
+ For a store without one, `patchworks tables results/image.zarr --channels 0,1`
22
+ adds them. With review corrections applied, and as csv files that
23
+ napari-chunked-regionprops loads directly: `patchworks review
24
+ results/image.zarr --export tables/` (see [Reviewing](review.md)).
25
+
6
26
  ## Interactively, in napari
7
27
 
8
28
  [napari-chunked-regionprops](https://github.com/imcf/napari-chunked-regionprops)
@@ -0,0 +1,261 @@
1
+ # Reviewing and correcting results
2
+
3
+ A segmentation of a whole tissue has tens of thousands of objects, and some
4
+ of them are wrong. Scrolling through the volume hoping to spot them doesn't
5
+ work. `patchworks review` shows you the objects **most likely to be wrong**,
6
+ one at a time, and lets you fix each one with a single key. Your
7
+ corrections go straight into the result tables and workbooks, and no voxel
8
+ is rewritten unless you ask.
9
+
10
+ ```bash
11
+ pip install "patchworks[napari]" # on the machine you review on
12
+ patchworks review results/image.zarr
13
+ ```
14
+
15
+ ![The review panel](../assets/review_panel.png)
16
+
17
+ *A cilium only 25% inside its cell. The view jumps to it, shows it with its
18
+ cell outlined, and hides everything else. Press G if it is right, H to give
19
+ it the right cell, W if it isn't a cilium at all.*
20
+
21
+ ## What gets flagged
22
+
23
+ Each object in the queue shows why it is there:
24
+
25
+ | Flag | Means | Typical cause |
26
+ | --- | --- | --- |
27
+ | *not inside any cell* | the object overlaps no parent object | debris, a missed cell, a cilium on the lumen side |
28
+ | *only 30% inside cell #88* | less than `min_overlap` of it is inside its parent | wrong cell picked at a boundary, or a merge of two objects |
29
+ | *0 nuclei (expected 1)* | a parent holds an unexpected number of children | a cell split in two, two cells merged, a missed nucleus |
30
+ | *meets #412 exactly at a tile seam (z)* | two objects touch face to face on a tile boundary | one object the tiling cut in two, or two neighbours; press J to join them |
31
+ | *unusually large (6.2x the median)* | the size is far from the rest (robust z-score > 3.5) | two objects merged, or a fragment |
32
+ | *outside cell #12, 0.8 µm away* | touches no parent, but one is within `max_distance_um` | a cilium beside its cell (worth a look, lower priority) |
33
+ | *position unclear: its cell has no nucleus to orient it* | a position rule needs the nucleus, and the cell has none | a missed nucleus, or a cell cut by the image edge |
34
+
35
+ The most suspicious objects come first. "Children" and "parents" come from
36
+ the relations of a multi run (e.g. `cilia_labels → cyto_labels`); the size
37
+ and seam flags apply to every label image.
38
+
39
+ **Expected counts** are yours to state, since only you know that a cell has
40
+ one nucleus and zero to two cilia. Put them in `multi.yaml`, and the
41
+ workflow checks them before it starts and stores them with the results:
42
+
43
+ ```yaml
44
+ review:
45
+ expect:
46
+ cyto_labels:
47
+ nuclei_labels: 1 # exactly one
48
+ cilia_labels: [0, 2] # zero to two
49
+ min_overlap: 0.5 # or per pair: {cilia_labels: {cyto_labels: 0.3}}
50
+ ```
51
+
52
+ Or pass them when you open the review:
53
+ `patchworks review image.zarr --expect cyto_labels:nuclei_labels=1 cyto_labels:cilia_labels=0-2`.
54
+
55
+ ## Deciding
56
+
57
+ | Key | Decision | Effect on the results |
58
+ | --- | --- | --- |
59
+ | **G** | correct as it is | kept; marked `ok` |
60
+ | **W** | wrong: not a real object | dropped from every table and count |
61
+ | **H**, then click | belongs to another parent: click the right one (the background means "none") | its parent id changes; both parents' counts follow |
62
+ | **J** | join with the suggested object (a seam split) | the two become one object: sizes added, centroid and intensities weighted |
63
+ | **Shift+J**, then click | join with the object you click | same |
64
+ | **N** / **Shift+N** | skip / go back | nothing |
65
+ | **U** | undo the decision about this object | back to unreviewed |
66
+
67
+ Every decision is saved immediately, in the store, next to the object
68
+ table. Close napari whenever you like: the next `patchworks review`
69
+ continues where you stopped, and the queue leaves out what has been
70
+ decided. Corrections chain as you would expect. For example, joining two
71
+ halves of a cell moves the cilia of both halves to the joined cell.
72
+
73
+ ## Seeing what belongs to what
74
+
75
+ Outside the queue, you can look at any object:
76
+
77
+ - **Click any object** in the image: the panel switches to it and shows it
78
+ with its parent (outlined) and its children. Click a cell to see its
79
+ nuclei and cilia, a cilium to see its cell. A click reads the image at
80
+ full resolution, and a thin object is hit even a couple of voxels off,
81
+ so cilia are easy to pick.
82
+ - **Go to #**: type an object's id and press Enter.
83
+ - **Hover** over any object: napari's status bar shows its parent, its
84
+ number of children, its position and its review status.
85
+
86
+ Options in the panel change the view:
87
+
88
+ - **Show only this object and its relatives** (on by default) hides
89
+ everything else. Untick it to see the neighbourhood.
90
+ - **Colour these objects by their parent** adds a layer where every child
91
+ has its parent's colour: all cilia of one cell share a colour, so an
92
+ assignment error stands out as a different colour.
93
+ - **Colour these objects by position** colours each cilium by its class:
94
+ apical, basal, lateral or central.
95
+ - **Side view** shows z against x, with z up, through the object. Apical
96
+ and basal are then seen at a glance.
97
+
98
+ Orange rings mark the flagged objects still open. They stay visible in 3D,
99
+ where napari's coarse 3D level hides small objects.
100
+
101
+ ## Where a cilium sits: apical, basal, lateral, central
102
+
103
+ ![A cilium classified as apical, in side view](../assets/review_position.png)
104
+
105
+ *Side view, coloured by position: an apical cilium (green) standing out of
106
+ the top of its cell, the nucleus at the bottom; next door, a basal cilium
107
+ (blue).*
108
+
109
+ Each cilium is classified by the cell surface its **base** is nearest to:
110
+
111
+ | Class | The base is nearest to |
112
+ | --- | --- |
113
+ | `apical` | the top of the cell |
114
+ | `basal` | the bottom of the cell |
115
+ | `lateral` | the side wall |
116
+ | `central` | none of them: deeper than half-way from every surface |
117
+
118
+ "Top" needs an apical direction per cell. It can be a fixed direction:
119
+ `+z` if apical is up the stack, as for a monolayer imaged from below. Or it
120
+ can point **away from the nucleus**, for epithelia whose nuclei sit
121
+ basally; then each cell gets its own axis, whatever its tilt. The base of
122
+ a cilium is its end nearer the cell's centre, since a cilium grows out
123
+ from its base.
124
+
125
+ ```yaml
126
+ # multi.yaml
127
+ review:
128
+ position:
129
+ cilia_labels:
130
+ parent: cyto_labels
131
+ apical: nuclei_labels # away from the nucleus; or "+z", "-z", ...
132
+ central_depth: 0.5 # optional
133
+ ```
134
+
135
+ Or when opening the review:
136
+ `patchworks review image.zarr --position cilia_labels:cyto_labels=nuclei_labels`.
137
+
138
+ The corrected tables get the class (`position`), where the base sits
139
+ (`position_axial`: -1 basal … +1 apical; `position_radial`: 0 on the axis …
140
+ 1 at the side) and the cilium's angle to the apical axis
141
+ (`angle_to_axis_deg`: 0 along it, 90 across it). Each cell gets its counts
142
+ per class (`n_cilia_labels_apical`, …), as does the relation workbook.
143
+
144
+ The cell's shape comes from its moments, i.e. an equivalent cylinder, so
145
+ this is a classification, not a surface distance. It is reliable for
146
+ columnar and cuboidal cells, and less so for very irregular ones. Check it
147
+ the usual way: **Colour these objects by position** plus **Side view**. If
148
+ one is wrong, the **Position is:** buttons correct it. A correction counts
149
+ as a classification fix, not a segmentation error, so it does not enter
150
+ the error rate. A cell without a nucleus cannot be oriented; its cilia are
151
+ `unknown` and flagged.
152
+
153
+ ## Cilia next to their cell, not on it
154
+
155
+ A cilium can lie against its cell without overlapping it, and would then
156
+ count as belonging to no cell. With `max_distance_um` on a relation, such
157
+ an object gets the **nearest** cell within that distance. The distance is
158
+ exact, in µm, with anisotropic voxels taken into account, and is recorded
159
+ in `cyto_labels_distance_um`. These objects are flagged with a lower
160
+ priority ("outside cell #12, 0.8 µm away").
161
+
162
+ ```yaml
163
+ relations:
164
+ - a: cilia_labels
165
+ b: cyto_labels
166
+ output: cilia_to_cell.xlsx
167
+ max_distance_um: 1.0
168
+ ```
169
+
170
+ For an existing store:
171
+ `patchworks tables image.zarr --relate cilia_labels:cyto_labels --max-distance 1`.
172
+
173
+ ## How good is the segmentation?
174
+
175
+ Pick the queue **Random sample (error rate)** and review objects in the
176
+ order it gives. The panel reports the fraction found wrong, with a 95%
177
+ confidence interval, e.g. *Error rate 3.0% (95% CI 1.0–8.5%) from 100
178
+ random objects*. Stop when the interval is narrow enough for what you need.
179
+ The estimate is unbiased because the order is random and fixed. Objects you
180
+ already decided from the flagged queue count too: a decision about an
181
+ object is true however it came up.
182
+
183
+ ## Using the corrections
184
+
185
+ The workbooks and tables are always read *with* the decisions applied:
186
+
187
+ - **The relation workbooks** of a multi run:
188
+ `patchworks review image.zarr --workbooks results/` writes every one
189
+ (`<child>_to_<parent>.xlsx`) from the corrected tables, with a `qc`
190
+ column (`ok`, `fixed`, or blank if not reviewed). Re-running the same
191
+ `run_multi` command does it too: a workbook older than your decisions is
192
+ rewritten, straight from the tables, without re-reading any labels.
193
+ - **Export** the corrected tables, one file per label image, from the
194
+ panel or with `patchworks review image.zarr --export results/tables
195
+ --format xlsx` (or `csv`, `parquet`). A csv loads straight into
196
+ [napari-chunked-regionprops](measurements.md) ("Reload previous
197
+ results").
198
+ - **Write corrected labels** (panel button, or `--write-labels
199
+ nuclei_labels`) writes `labels/nuclei_labels_reviewed`, the label image
200
+ with the decisions applied to the voxels. You only need this for figures,
201
+ or for tools that read label images only.
202
+
203
+ `patchworks review image.zarr --summary` prints the counts and the error
204
+ estimate without opening napari.
205
+
206
+ ## Where the tables come from
207
+
208
+ Every label image the workflow writes gets an **object table**: one row per
209
+ object with its size (`area_voxels`, `area_um3`), centroid, bounding box,
210
+ its spread (`cov_*`, the second moments) and, for a multi run, the parent
211
+ it sits in (`cyto_labels_id`, `cyto_labels_overlap`). The corrected view
212
+ adds each object's shape from its spread: `length_um` (for a straight rod
213
+ the true length; shorter for a curved one), `elongation` (1 round, large
214
+ rod-like) and its main axis (`axis_z`, `axis_y`, `axis_x`). The table lives
215
+ inside the label group:
216
+
217
+ ```text
218
+ image.zarr/labels/cilia_labels/
219
+ 0/ 1/ 2/ … the label pyramid
220
+ table/ the object table (one zarr array per column)
221
+ ```
222
+
223
+ So it travels with the labels, including in a zip bundle, and it is
224
+ replaced whenever the labels are. A table computed from older labels is
225
+ recognised and ignored, never shown against the wrong segmentation.
226
+ Measuring costs one read of the labels, after the merge. Add intensity
227
+ columns with `table_channels: [0, 2]`, or turn tables off with
228
+ `object_table: false` (see the [workflow config](snakemake.md)).
229
+
230
+ For a store that has none, for example a run made before tables existed,
231
+ or labels from elsewhere:
232
+
233
+ ```bash
234
+ patchworks tables image.zarr --relate nuclei_labels:cyto_labels cilia_labels:cyto_labels
235
+ ```
236
+
237
+ In Python:
238
+
239
+ ```python
240
+ from patchworks import Review, read_table
241
+
242
+ cells = read_table("image.zarr/labels/cyto_labels") # pandas, as computed
243
+ rv = Review("image.zarr", expect={"cyto_labels": {"nuclei_labels": 1}})
244
+ rv.flags("cyto_labels")[:5] # the worst five
245
+ rv.decide("nuclei_labels", 17, "wrong")
246
+ rv.effective("cyto_labels") # with corrections
247
+ ```
248
+
249
+ ## Where to review
250
+
251
+ On any machine with a screen and napari, reading the store the cluster
252
+ wrote:
253
+
254
+ - **Copy the store** to your computer (tables and decisions live in it), or
255
+ open it on a mounted cluster filesystem. napari reads only what is on
256
+ screen, so a network mount works.
257
+ - The store must be **writable**, since that is where the decisions are
258
+ saved. A `.zip` bundle is read-only, so unzip it first.
259
+ - The decisions are small. If you reviewed a copy, `--workbooks` and
260
+ `--export` work on that copy directly; there is no need to copy anything
261
+ back.
@@ -609,10 +609,15 @@ Everything is under `work_dir`:
609
609
 
610
610
  ```text
611
611
  results/
612
- image.zarr/ # converted, pyramidal OME-ZARR
613
- image.zarr/labels/<name>/ # the segmentation (multi-scale, calibrated)
612
+ image.zarr/ # converted, pyramidal OME-ZARR
613
+ image.zarr/labels/<name>/ # the segmentation (multi-scale, calibrated)
614
+ image.zarr/labels/<name>/table/ # one row per object (object_table: true)
614
615
  ```
615
616
 
617
+ To look at the likely mistakes and correct them, on any machine with napari:
618
+ `patchworks review /scratch/results/image.zarr` — see
619
+ [Reviewing and correcting results](review.md).
620
+
616
621
  The labels live **inside** the image store. View image + labels together:
617
622
 
618
623
  ```python
@@ -916,7 +921,9 @@ abort the others; you get a per-config status and a non-zero exit.
916
921
  that would exceed your cluster quota.
917
922
 
918
923
  Each `output:` is an Excel workbook (`openpyxl`, part of the `workflow`
919
- extra) with two sheets:
924
+ extra), written from the [object tables](review.md#where-the-tables-come-from)
925
+ with any review corrections applied, with two sheets (plus a `qc` column
926
+ each):
920
927
 
921
928
  | Sheet | One row per | Columns |
922
929
  | --- | --- | --- |
@@ -943,6 +950,18 @@ cyto_labels` and `cilia_labels -> nuclei_labels`) so you can use whichever
943
950
  fits a given dataset. See `config/config_cilia.yaml`. Its deconvolution step
944
951
  needs `pip install "patchworks[dog]"` in the segment jobs' environment.
945
952
 
953
+ A `review:` block in `multi.yaml` states what `patchworks review` should
954
+ flag: how many of each child a parent should hold, and how far inside it a
955
+ child must be. It can also classify children by where they sit in their
956
+ parent: apical, basal, lateral or central, see
957
+ [Where a cilium sits](review.md#where-a-cilium-sits-apical-basal-lateral-central).
958
+ A relation with `max_distance_um` gives a child touching no parent the
959
+ nearest one. All of it is checked before anything runs, and stored with
960
+ the results. See [Reviewing](review.md#what-gets-flagged).
961
+
962
+ A sheet that would exceed Excel's 1,048,576 rows is written as a csv file
963
+ instead (`<stem>_<name>.csv`), and the log says so.
964
+
946
965
  ## Email notifications
947
966
 
948
967
  Set an address and the workflow mails you when the long steps finish or fail:
@@ -43,6 +43,7 @@ nav:
43
43
  - Performance & memory: guide/performance.md
44
44
  - Custom segmentation function: guide/custom_segmentation.md
45
45
  - Relating labels across segmentations: guide/label_relations.md
46
+ - Reviewing and correcting results: guide/review.md
46
47
  - Measurements: guide/measurements.md
47
48
  - OME-ZARR & napari: guide/ome_zarr_napari.md
48
49
  - Cluster workflow (Snakemake): guide/snakemake.md
@@ -65,6 +66,7 @@ nav:
65
66
  - I/O helpers: api/io.md
66
67
  - Relabelling: api/relabel.md
67
68
  - Seam report: api/seams.md
69
+ - Object tables and review: api/review.md
68
70
  - Provenance: api/provenance.md
69
71
  - Cluster helpers: api/cluster.md
70
72
  - Plugins:
@@ -110,9 +110,17 @@ napari = [
110
110
  "pyqt6<6.10",
111
111
  ]
112
112
  # workflow runs the Snakemake pipeline (per-tile SLURM jobs across GPUs).
113
- # openpyxl -> scripts/run_multi.py writes label_relations() output as an
114
- # Excel workbook (per-object + per-container sheets), not a plain CSV.
115
- workflow = ["snakemake>=8", "snakemake-executor-plugin-slurm", "openpyxl"]
113
+ # pandas + openpyxl -> the relate step writes each relation as an Excel
114
+ # workbook (per-object + per-container sheets) from the object tables.
115
+ workflow = [
116
+ "snakemake>=8",
117
+ "snakemake-executor-plugin-slurm",
118
+ "openpyxl",
119
+ "pandas>=2.0",
120
+ ]
121
+ # review: the object tables' corrected view and exports, without napari
122
+ # (`patchworks review --summary/--export`). The napari panel needs [napari].
123
+ review = ["pandas>=2.0", "openpyxl"]
116
124
  # jsonschema validates what we write against the vendored official
117
125
  # OME-NGFF schemas (tests/ngff_schemas/); without it that one test skips.
118
126
  # openpyxl -> tests/test_run_multi.py checks the relations workbook.
@@ -126,6 +134,8 @@ dev = [
126
134
  "tqdm",
127
135
  "jsonschema>=4.18",
128
136
  "openpyxl",
137
+ # pandas -> tests/test_review.py (object tables, the corrected view)
138
+ "pandas>=2.0",
129
139
  "ruff==0.15.18",
130
140
  "mypy>=1.10",
131
141
  ]
@@ -63,7 +63,14 @@ from ._postprocess import dilate_labels, fill_holes, open_labels
63
63
  from ._provenance import provenance, read_provenance
64
64
  from ._relabel import relabel_sequential_array, relabel_sequential_zarr
65
65
  from ._relations import label_relations
66
+ from ._review import Review
66
67
  from ._seams import seam_report
68
+ from ._tables import (
69
+ compute_table,
70
+ measure_objects,
71
+ read_table,
72
+ relate_tables,
73
+ )
67
74
  from ._volume_filter import (
68
75
  filter_labels_by_size,
69
76
  max_voxels_for_volume,
@@ -97,6 +104,11 @@ __all__ = [
97
104
  "relabel_sequential_array",
98
105
  "relabel_sequential_zarr",
99
106
  "label_relations",
107
+ "measure_objects",
108
+ "compute_table",
109
+ "read_table",
110
+ "relate_tables",
111
+ "Review",
100
112
  "seam_report",
101
113
  "suggest_overlap",
102
114
  "object_f1",