patchworks 2.6.3__tar.gz → 2.6.4__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-2.6.3 → patchworks-2.6.4}/PKG-INFO +1 -1
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/snakemake.md +12 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_run_multi.py +26 -0
- patchworks-2.6.4/workflow/scripts/relate.py +149 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/scripts/run_multi.py +67 -90
- {patchworks-2.6.3 → patchworks-2.6.4}/.github/workflows/docs.yml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/.github/workflows/lint.yml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/.github/workflows/release.yml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/.gitignore +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/.markdownlint-cli2.yaml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/LICENSE +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/README.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/cliff.toml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/chunks.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/cluster.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/io.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/plugins/dog.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/plugins/napari.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/postprocess.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/relabel.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/api/tile_process.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/assets/logo.png +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/custom.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/custom_method.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/dog.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/dog.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/standalone_merge.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/stardist.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/examples/stardist_2d.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/getting_started.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/custom_segmentation.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/label_relations.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/measurements.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/merging.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/performance.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/pitfalls.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/skip_empty.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/guide/tiling.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/docs/index.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/mkdocs.yml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/pyproject.toml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/__init__.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_chunks.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_cluster.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_core.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_distributed.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_gpu.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_io.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_merge.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_notify.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_occupancy.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_postprocess.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_progress.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_relabel.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/_relations.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/plugins/dog.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_allocation.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_core.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_distributed.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_dog.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_gpu.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_napari.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_notify.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_occupancy.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_ome_zarr.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_postprocess.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_progress.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/tests/test_relations.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/README.md +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/Snakefile +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/config/common.yaml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/config/config.yaml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/config/config_cilia.yaml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/config/multi.yaml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/pixi.toml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/rules/common.smk +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/rules/convert.smk +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/rules/merge.smk +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/rules/segment.smk +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/scripts/_pw.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/scripts/build_occupancy.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/scripts/convert.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/scripts/merge.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-2.6.3 → patchworks-2.6.4}/workflow/scripts/segment_tile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.4
|
|
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
|
|
@@ -451,6 +451,18 @@ the GPU partition stays busy instead of idling through every config's
|
|
|
451
451
|
`prepare` and multi-hour `merge` in turn. A config that fails does **not**
|
|
452
452
|
abort the others; you get a per-config status and a non-zero exit.
|
|
453
453
|
|
|
454
|
+
!!! tip "The relate step runs on the cluster too, under `multi-slurm`"
|
|
455
|
+
`label_relations()` streams every chunk of two full-resolution label
|
|
456
|
+
volumes — real CPU/IO work, not orchestration. Under `multi-slurm` it is
|
|
457
|
+
submitted as its own `srun` job (`scripts/relate.py`) instead of running
|
|
458
|
+
in the driver process on the login node, the same fix already applied to
|
|
459
|
+
the occupancy map. Tune its allocation with `--relate-partition`,
|
|
460
|
+
`--relate-mem`, `--relate-cpus` and `--relate-time` (defaults: `scicore`,
|
|
461
|
+
`32G`, `8`, `180` minutes) — these are wide-margin guesses, not measured
|
|
462
|
+
numbers, so raise them for a very large or very object-dense pair. Under
|
|
463
|
+
plain `multi` (no `--profile`), it still runs locally, in-process, as
|
|
464
|
+
before.
|
|
465
|
+
|
|
454
466
|
!!! tip "After a killed run"
|
|
455
467
|
Snakemake only releases its lock on a clean exit, so a run that was killed
|
|
456
468
|
(Ctrl-C, an SSH drop, an OOM) leaves the directory locked. Each phase has
|
|
@@ -160,6 +160,32 @@ def test_occupancy_is_not_rebuilt_by_the_driver():
|
|
|
160
160
|
assert "occupancy.zarr" in src
|
|
161
161
|
|
|
162
162
|
|
|
163
|
+
def test_relate_is_submitted_via_slurm_under_profile():
|
|
164
|
+
"""The relate step must never run in-process on the submit host.
|
|
165
|
+
|
|
166
|
+
Same failure mode as the occupancy map: label_relations() streams every
|
|
167
|
+
chunk of two full-resolution label volumes. A prior fix moved the map
|
|
168
|
+
build off the login node; the relate step made the identical mistake and
|
|
169
|
+
hung there for the same reason until this fix.
|
|
170
|
+
"""
|
|
171
|
+
src = (_workflow_dir() / "scripts" / "run_multi.py").read_text()
|
|
172
|
+
assert "from relate import run_relations" in src
|
|
173
|
+
assert '"srun"' in src
|
|
174
|
+
assert "label_relations(" not in src
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_relate_script_has_the_real_bookkeeping():
|
|
178
|
+
"""relate.py must be the actual implementation, not a stub.
|
|
179
|
+
|
|
180
|
+
Submitting the wrong (or a trimmed-down) script would silently produce a
|
|
181
|
+
workbook missing the unmatched-label rows the docstring promises.
|
|
182
|
+
"""
|
|
183
|
+
src = (_workflow_dir() / "scripts" / "relate.py").read_text()
|
|
184
|
+
assert "def run_relations(" in src
|
|
185
|
+
assert "label_relations" in src
|
|
186
|
+
assert "openpyxl" in src
|
|
187
|
+
|
|
188
|
+
|
|
163
189
|
def test_auto_tile_shape_with_a_lone_nuclei_channel_is_refused():
|
|
164
190
|
"""Matching `tile_shape` *values* are not enough when one config is 2-ch.
|
|
165
191
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Compute label_relations for configured pairs and write .xlsx.
|
|
2
|
+
|
|
3
|
+
Split out of run_multi.py so this step can be submitted as its own SLURM job
|
|
4
|
+
instead of running in-process on the login node. It streams every chunk of
|
|
5
|
+
two full-resolution label volumes -- real CPU/IO work, not orchestration --
|
|
6
|
+
same reasoning as the occupancy-map fix (see run_multi.py's phase A comment).
|
|
7
|
+
|
|
8
|
+
Usage (called by run_multi.py under --profile, but also runnable standalone,
|
|
9
|
+
e.g. under srun):
|
|
10
|
+
python scripts/relate.py --work-dir /path/to/work_dir \
|
|
11
|
+
--image-store /path/to/work_dir/image.zarr \
|
|
12
|
+
--relations '[{"a": "nuclei_labels", "b": "cyto_labels", "output": "nuclei_to_cyto.xlsx"}]'
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _label_ids(image_store: str, name: str) -> list[int]:
|
|
23
|
+
"""Ids present in a label image, without scanning the volume.
|
|
24
|
+
|
|
25
|
+
The merge writes n_objects/sequential_labels into the label group's
|
|
26
|
+
attrs precisely so consumers don't have to re-derive the id set; the ids
|
|
27
|
+
are 1..n_objects by construction. Fall back to a full scan only for a
|
|
28
|
+
label group written before those attrs existed.
|
|
29
|
+
"""
|
|
30
|
+
import dask.array as da
|
|
31
|
+
import zarr
|
|
32
|
+
|
|
33
|
+
attrs = dict(zarr.open_group(f"{image_store}/labels/{name}").attrs)
|
|
34
|
+
if attrs.get("sequential_labels") and attrs.get("n_objects") is not None:
|
|
35
|
+
return list(range(1, int(attrs["n_objects"]) + 1))
|
|
36
|
+
print(
|
|
37
|
+
f"[relate] {name}: no n_objects attr, falling back to a full scan "
|
|
38
|
+
"for its id set",
|
|
39
|
+
flush=True,
|
|
40
|
+
)
|
|
41
|
+
arr = da.from_zarr(image_store, component=f"labels/{name}/0")
|
|
42
|
+
return sorted(int(x) for x in da.unique(arr[arr > 0]).compute())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run_relations(
|
|
46
|
+
work_dir: str, image_store: str, relations: list[dict]
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Compute and write every configured relation pair as an .xlsx workbook.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
work_dir : str
|
|
53
|
+
Directory relation workbooks are written into (a relation's
|
|
54
|
+
``output``, when relative, resolves against this).
|
|
55
|
+
image_store : str
|
|
56
|
+
The shared ``image.zarr`` holding every config's ``labels/<name>``.
|
|
57
|
+
relations : list of dict
|
|
58
|
+
Each ``{"a": ..., "b": ..., "output": ...}`` (``output`` optional,
|
|
59
|
+
defaults to ``<a>_to_<b>.xlsx``), matching ``multi.yaml``'s
|
|
60
|
+
``relations:`` list.
|
|
61
|
+
"""
|
|
62
|
+
import dask.array as da
|
|
63
|
+
import openpyxl
|
|
64
|
+
|
|
65
|
+
from patchworks import label_relations
|
|
66
|
+
|
|
67
|
+
for rel in relations:
|
|
68
|
+
a_name, b_name = rel["a"], rel["b"]
|
|
69
|
+
out_path = Path(work_dir) / rel.get(
|
|
70
|
+
"output", f"{a_name}_to_{b_name}.xlsx"
|
|
71
|
+
)
|
|
72
|
+
print(f"[relate] relating {a_name} -> {b_name} …", flush=True)
|
|
73
|
+
a = da.from_zarr(image_store, component=f"labels/{a_name}/0")
|
|
74
|
+
b = da.from_zarr(image_store, component=f"labels/{b_name}/0")
|
|
75
|
+
table = label_relations(a, b)
|
|
76
|
+
|
|
77
|
+
# label_relations() only returns a-objects that touch a b-object.
|
|
78
|
+
# Pull the full id sets so unmatched a-objects (zero overlap) and
|
|
79
|
+
# b-objects with no matches at all still get a row -- otherwise
|
|
80
|
+
# they'd silently vanish instead of counting as zero.
|
|
81
|
+
a_ids = _label_ids(image_store, a_name)
|
|
82
|
+
b_ids = _label_ids(image_store, b_name)
|
|
83
|
+
|
|
84
|
+
per_b = {b_id: {"count": 0, "overlap_voxels": 0} for b_id in b_ids}
|
|
85
|
+
for m in table.values():
|
|
86
|
+
agg = per_b.get(m["match"])
|
|
87
|
+
if agg is not None:
|
|
88
|
+
agg["count"] += 1
|
|
89
|
+
agg["overlap_voxels"] += m["overlap_voxels"]
|
|
90
|
+
|
|
91
|
+
wb = openpyxl.Workbook()
|
|
92
|
+
ws_a = wb.active
|
|
93
|
+
ws_a.title = a_name[:31] # Excel sheet-name length limit
|
|
94
|
+
ws_a.append(
|
|
95
|
+
[
|
|
96
|
+
f"{a_name}_id",
|
|
97
|
+
f"{b_name}_id",
|
|
98
|
+
"overlap_voxels",
|
|
99
|
+
"overlap_fraction",
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
for a_id in a_ids:
|
|
103
|
+
m = table.get(a_id)
|
|
104
|
+
if m is None:
|
|
105
|
+
ws_a.append([a_id, None, 0, 0]) # no overlap -- still counted
|
|
106
|
+
else:
|
|
107
|
+
ws_a.append(
|
|
108
|
+
[
|
|
109
|
+
a_id,
|
|
110
|
+
m["match"],
|
|
111
|
+
m["overlap_voxels"],
|
|
112
|
+
m["overlap_fraction"],
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
ws_b = wb.create_sheet(title=b_name[:31])
|
|
117
|
+
ws_b.append(
|
|
118
|
+
[f"{b_name}_id", f"{a_name}_count", "total_overlap_voxels"]
|
|
119
|
+
)
|
|
120
|
+
for b_id in b_ids:
|
|
121
|
+
agg = per_b[b_id]
|
|
122
|
+
ws_b.append([b_id, agg["count"], agg["overlap_voxels"]])
|
|
123
|
+
|
|
124
|
+
wb.save(out_path)
|
|
125
|
+
print(
|
|
126
|
+
f"[relate] wrote {out_path} "
|
|
127
|
+
f"({len(a_ids)} {a_name}, {len(b_ids)} {b_name})",
|
|
128
|
+
flush=True,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main() -> None:
|
|
133
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
134
|
+
parser.add_argument("--work-dir", required=True)
|
|
135
|
+
parser.add_argument("--image-store", required=True)
|
|
136
|
+
parser.add_argument(
|
|
137
|
+
"--relations",
|
|
138
|
+
required=True,
|
|
139
|
+
help=(
|
|
140
|
+
"JSON list of {a, b, output} dicts, matching multi.yaml's "
|
|
141
|
+
"relations:"
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
args = parser.parse_args()
|
|
145
|
+
run_relations(args.work_dir, args.image_store, json.loads(args.relations))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
main()
|
|
@@ -20,12 +20,16 @@ Once all segmentations succeed, each configured relation pair is computed via
|
|
|
20
20
|
patchworks.label_relations and written as an Excel workbook in work_dir,
|
|
21
21
|
with two sheets: one row per a-object (unmatched ones included, with an
|
|
22
22
|
empty b-id and zeros) and one row per b-object (a-object count + total
|
|
23
|
-
overlap, including b-objects with zero matches).
|
|
23
|
+
overlap, including b-objects with zero matches). Under --profile, this runs
|
|
24
|
+
as a submitted SLURM job (see scripts/relate.py) rather than in-process here
|
|
25
|
+
-- same reasoning as the occupancy-map fix: it streams entire label volumes,
|
|
26
|
+
which is real work, not orchestration, and does not belong on the login node.
|
|
24
27
|
"""
|
|
25
28
|
|
|
26
29
|
from __future__ import annotations
|
|
27
30
|
|
|
28
31
|
import argparse
|
|
32
|
+
import json
|
|
29
33
|
import re
|
|
30
34
|
import subprocess
|
|
31
35
|
import sys
|
|
@@ -337,6 +341,28 @@ def main() -> None:
|
|
|
337
341
|
"otherwise look identical: no email either way."
|
|
338
342
|
),
|
|
339
343
|
)
|
|
344
|
+
parser.add_argument(
|
|
345
|
+
"--relate-partition",
|
|
346
|
+
default="scicore",
|
|
347
|
+
help="SLURM partition for the relate step under --profile (default: scicore)",
|
|
348
|
+
)
|
|
349
|
+
parser.add_argument(
|
|
350
|
+
"--relate-mem",
|
|
351
|
+
default="32G",
|
|
352
|
+
help="srun --mem for the relate step under --profile (default: 32G)",
|
|
353
|
+
)
|
|
354
|
+
parser.add_argument(
|
|
355
|
+
"--relate-cpus",
|
|
356
|
+
type=int,
|
|
357
|
+
default=8,
|
|
358
|
+
help="srun --cpus-per-task for the relate step under --profile (default: 8)",
|
|
359
|
+
)
|
|
360
|
+
parser.add_argument(
|
|
361
|
+
"--relate-time",
|
|
362
|
+
type=int,
|
|
363
|
+
default=180,
|
|
364
|
+
help="srun --time in minutes for the relate step under --profile (default: 180)",
|
|
365
|
+
)
|
|
340
366
|
args = parser.parse_args()
|
|
341
367
|
|
|
342
368
|
workflow_dir = Path(__file__).resolve().parent.parent
|
|
@@ -467,96 +493,47 @@ def main() -> None:
|
|
|
467
493
|
if args.dry_run or not relations:
|
|
468
494
|
return
|
|
469
495
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
"
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
496
|
+
if args.profile:
|
|
497
|
+
# Real CPU/IO work -- tens of thousands of zarr chunk reads for a
|
|
498
|
+
# full-resolution label volume -- not orchestration, so (like the
|
|
499
|
+
# occupancy map) it does not belong in this driver process on the
|
|
500
|
+
# login node. Submit it as its own job instead.
|
|
501
|
+
cmd = [
|
|
502
|
+
"srun",
|
|
503
|
+
"--partition",
|
|
504
|
+
args.relate_partition,
|
|
505
|
+
"--mem",
|
|
506
|
+
args.relate_mem,
|
|
507
|
+
"--cpus-per-task",
|
|
508
|
+
str(args.relate_cpus),
|
|
509
|
+
"--time",
|
|
510
|
+
str(args.relate_time),
|
|
511
|
+
"--job-name",
|
|
512
|
+
"pw-relate",
|
|
513
|
+
sys.executable,
|
|
514
|
+
str(workflow_dir / "scripts" / "relate.py"),
|
|
515
|
+
"--work-dir",
|
|
516
|
+
work_dir,
|
|
517
|
+
"--image-store",
|
|
518
|
+
image_store,
|
|
519
|
+
"--relations",
|
|
520
|
+
json.dumps(relations),
|
|
521
|
+
]
|
|
522
|
+
rc = _run(cmd, workflow_dir)
|
|
523
|
+
if rc != 0:
|
|
524
|
+
print(
|
|
525
|
+
f"[run_multi] ERROR: relate step failed (exit {rc}). "
|
|
526
|
+
"Segmentations already succeeded -- only the relation "
|
|
527
|
+
"workbook(s) are missing. Re-run with the same --config to "
|
|
528
|
+
"retry just this step.",
|
|
529
|
+
file=sys.stderr,
|
|
530
|
+
)
|
|
531
|
+
sys.exit(rc)
|
|
532
|
+
return
|
|
498
533
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
"output", f"{a_name}_to_{b_name}.xlsx"
|
|
503
|
-
)
|
|
504
|
-
print(f"[run_multi] relating {a_name} -> {b_name} …", flush=True)
|
|
505
|
-
a = da.from_zarr(image_store, component=f"labels/{a_name}/0")
|
|
506
|
-
b = da.from_zarr(image_store, component=f"labels/{b_name}/0")
|
|
507
|
-
table = label_relations(a, b)
|
|
508
|
-
|
|
509
|
-
# label_relations() only returns a-objects that touch a b-object.
|
|
510
|
-
# Pull the full id sets so unmatched a-objects (zero overlap) and
|
|
511
|
-
# b-objects with no matches at all still get a row -- otherwise
|
|
512
|
-
# they'd silently vanish instead of counting as zero.
|
|
513
|
-
a_ids = _label_ids(a_name)
|
|
514
|
-
b_ids = _label_ids(b_name)
|
|
515
|
-
|
|
516
|
-
per_b = {b_id: {"count": 0, "overlap_voxels": 0} for b_id in b_ids}
|
|
517
|
-
for m in table.values():
|
|
518
|
-
agg = per_b.get(m["match"])
|
|
519
|
-
if agg is not None:
|
|
520
|
-
agg["count"] += 1
|
|
521
|
-
agg["overlap_voxels"] += m["overlap_voxels"]
|
|
522
|
-
|
|
523
|
-
wb = openpyxl.Workbook()
|
|
524
|
-
ws_a = wb.active
|
|
525
|
-
ws_a.title = a_name[:31] # Excel sheet-name length limit
|
|
526
|
-
ws_a.append(
|
|
527
|
-
[
|
|
528
|
-
f"{a_name}_id",
|
|
529
|
-
f"{b_name}_id",
|
|
530
|
-
"overlap_voxels",
|
|
531
|
-
"overlap_fraction",
|
|
532
|
-
]
|
|
533
|
-
)
|
|
534
|
-
for a_id in a_ids:
|
|
535
|
-
m = table.get(a_id)
|
|
536
|
-
if m is None:
|
|
537
|
-
ws_a.append([a_id, None, 0, 0]) # no overlap -- still counted
|
|
538
|
-
else:
|
|
539
|
-
ws_a.append(
|
|
540
|
-
[
|
|
541
|
-
a_id,
|
|
542
|
-
m["match"],
|
|
543
|
-
m["overlap_voxels"],
|
|
544
|
-
m["overlap_fraction"],
|
|
545
|
-
]
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
ws_b = wb.create_sheet(title=b_name[:31])
|
|
549
|
-
ws_b.append([f"{b_name}_id", f"{a_name}_count", "total_overlap_voxels"])
|
|
550
|
-
for b_id in b_ids:
|
|
551
|
-
agg = per_b[b_id]
|
|
552
|
-
ws_b.append([b_id, agg["count"], agg["overlap_voxels"]])
|
|
553
|
-
|
|
554
|
-
wb.save(out_path)
|
|
555
|
-
print(
|
|
556
|
-
f"[run_multi] wrote {out_path} "
|
|
557
|
-
f"({len(a_ids)} {a_name}, {len(b_ids)} {b_name})",
|
|
558
|
-
flush=True,
|
|
559
|
-
)
|
|
534
|
+
from relate import run_relations
|
|
535
|
+
|
|
536
|
+
run_relations(work_dir, image_store, relations)
|
|
560
537
|
|
|
561
538
|
|
|
562
539
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|