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.
- {patchworks-3.0.0 → patchworks-3.1.0}/.github/min-versions.txt +1 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/PKG-INFO +16 -1
- {patchworks-3.0.0 → patchworks-3.1.0}/README.md +10 -0
- patchworks-3.1.0/docs/api/review.md +19 -0
- patchworks-3.1.0/docs/assets/review_panel.png +0 -0
- patchworks-3.1.0/docs/assets/review_position.png +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/cli.md +15 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/label_relations.md +8 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/measurements.md +20 -0
- patchworks-3.1.0/docs/guide/review.md +261 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/snakemake.md +22 -3
- {patchworks-3.0.0 → patchworks-3.1.0}/mkdocs.yml +2 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/pyproject.toml +13 -3
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/__init__.py +12 -0
- patchworks-3.1.0/src/patchworks/_review.py +1243 -0
- patchworks-3.1.0/src/patchworks/_tables.py +786 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/cli.py +170 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/napari.py +12 -9
- patchworks-3.1.0/src/patchworks/plugins/review.py +1056 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_cli.py +69 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_launcher.py +26 -1
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_napari.py +9 -2
- patchworks-3.1.0/tests/test_position.py +194 -0
- patchworks-3.1.0/tests/test_review.py +168 -0
- patchworks-3.1.0/tests/test_review_napari.py +161 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_run_multi.py +138 -1
- patchworks-3.1.0/tests/test_tables.py +130 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/config.yaml +7 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/multi.yaml +23 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/app.py +83 -4
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/launcher_core.py +25 -1
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/_pw.py +13 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/merge.py +15 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/relate.py +71 -79
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/run_multi.py +108 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/.github/workflows/docs.yml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/.github/workflows/lint.yml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/.github/workflows/release.yml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/.github/workflows/test.yml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/.gitignore +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/.markdownlint-cli2.yaml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/.pre-commit-config.yaml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/LICENSE +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/benchmarks/bench.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/benchmarks/compare.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/cliff.toml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/chunks.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/cluster.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/io.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/plugins/dog.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/plugins/napari.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/postprocess.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/provenance.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/relabel.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/seams.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/tile_process.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/api/volume_filter.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/assets/logo.png +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/custom.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/custom_method.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/dog.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/dog.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/standalone_merge.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/stardist.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/examples/stardist_2d.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/getting_started.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/custom_segmentation.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/launcher.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/merging.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/performance.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/pitfalls.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/skip_empty.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/guide/tiling.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/docs/index.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_autotune.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_chunks.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_cluster.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_core.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_distributed.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_gpu.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_io.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_merge.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_notify.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_occupancy.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_postprocess.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_progress.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_provenance.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_relabel.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_relations.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_seams.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/_volume_filter.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/dog.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/src/patchworks/py.typed +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.4/image.schema +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.4/label.schema +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.4/ome.schema +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.5/_version.schema +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.5/image.schema +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.5/label.schema +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/0.5/ome.schema +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/ngff_schemas/README.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_allocation.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_autotune.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_cellpose.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_core.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_distributed.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_dog.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_gpu.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_notify.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_occupancy.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_ome_zarr.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_postprocess.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_progress.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_pw.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_relations.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_remote.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_seams.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/tests/test_volume_filter.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/README.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/Snakefile +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/common.yaml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/config_cilia.yaml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/README.md +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/clusters.yaml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/launcher/requirements.txt +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/pixi.toml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/rules/common.smk +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/rules/convert.smk +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/rules/merge.smk +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/rules/segment.smk +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/build_occupancy.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/convert.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/export_iso.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/reshard_store.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/segment_tile.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/scripts/view.py +0 -0
- {patchworks-3.0.0 → patchworks-3.1.0}/workflow/viewer/pixi.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 3.
|
|
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
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+

|
|
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
|
+

|
|
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/
|
|
613
|
-
image.zarr/labels/<name>/
|
|
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)
|
|
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 ->
|
|
114
|
-
#
|
|
115
|
-
workflow = [
|
|
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",
|