|
20 | 20 | from patchworks._chunks import _get_available_memory |
21 | 21 | from patchworks.plugins.ome_zarr import register_labels |
22 | 22 |
|
23 | | -from _pw import stage_path, start_log |
| 23 | +from _pw import load_tiles_json, stage_path, start_log |
24 | 24 |
|
25 | 25 | start_log(snakemake.log[0]) # noqa: F821 |
26 | 26 | cfg = snakemake.config # noqa: F821 |
|
29 | 29 | image_store = str(Path(work_dir) / "image.zarr") |
30 | 30 | label_group = f"{image_store}/labels/{label_name}" |
31 | 31 |
|
32 | | -staged = zarr.open_group(stage_path(work_dir, label_name), mode="r")["staged"] |
| 32 | +# prepare recorded where the segment jobs wrote: the label group's level 0 |
| 33 | +# (merged in place) or a scratch stage store. |
| 34 | +manifest = load_tiles_json(snakemake.input.tiles) # noqa: F821 |
| 35 | +target_path = manifest["target_path"] |
| 36 | +target_component = manifest.get("target_component", "staged") |
| 37 | +in_place = bool(manifest.get("in_place", False)) |
| 38 | + |
| 39 | +staged = zarr.open_group(target_path, mode="r")[target_component] |
33 | 40 |
|
34 | 41 | # Size the relabel pool against what this job was actually granted, not the |
35 | 42 | # node. Each worker holds roughly a few copies of one chunk, so the RAM budget |
|
49 | 56 | # counts in lets the merge compute global id ranges by a cumulative sum, |
50 | 57 | # replacing a full read+write of the store that existed only to renumber it. |
51 | 58 | label_counts = {} |
52 | | -for marker in snakemake.input: # noqa: F821 |
| 59 | +for marker in snakemake.input.markers: # noqa: F821 |
53 | 60 | for index, n in json.loads(Path(marker).read_text())["counts"].items(): |
54 | 61 | label_counts[int(index)] = int(n) |
55 | 62 |
|
56 | | -# Merge straight into the label group's level 0. Writing to a scratch |
57 | | -# _merged.zarr and letting write_labels copy it across cost a full extra |
58 | | -# read+write of the volume plus the scratch store's disk; register_labels |
59 | | -# already expects level 0 to exist and only adds the pyramid and metadata. |
60 | | -root = zarr.open_group(image_store, mode="a") |
61 | | -parent = root.require_group("labels") |
62 | | -if label_name in parent: |
63 | | - del parent[label_name] |
64 | | -parent.require_group(label_name) |
65 | | - |
66 | | -# Level 0 keeps napari-friendly chunks even when tiles are much larger; the |
67 | | -# cap has to divide the tile shape so merge workers still write whole chunks. |
68 | | -out_chunks = capped_output_chunks(staged.chunks, (16, 1024, 1024)) |
| 63 | +if in_place: |
| 64 | + # The tiles already sit in labels/<name>/0, so the merge rewrites them |
| 65 | + # where they are: no scratch store, and one full write of the volume less. |
| 66 | + # Safe because the boundary scan finishes before any chunk is rewritten. |
| 67 | + out_chunks = None |
| 68 | +else: |
| 69 | + # Level 0 keeps napari-friendly chunks even when tiles are much larger; |
| 70 | + # the cap must divide the tile so workers still write whole chunks. |
| 71 | + root = zarr.open_group(image_store, mode="a") |
| 72 | + parent = root.require_group("labels") |
| 73 | + if label_name in parent: |
| 74 | + del parent[label_name] |
| 75 | + parent.require_group(label_name) |
| 76 | + out_chunks = capped_output_chunks(staged.chunks, (16, 1024, 1024)) |
69 | 77 |
|
70 | 78 | _, n_objects = merge_tile_labels( |
71 | | - stage_path(work_dir, label_name), |
72 | | - write_to=label_group, |
73 | | - input_component="staged", |
74 | | - output_component="0", |
| 79 | + target_path, |
| 80 | + write_to=label_group if not in_place else target_path, |
| 81 | + input_component=target_component, |
| 82 | + output_component="0" if not in_place else target_component, |
75 | 83 | output_chunks=out_chunks, |
76 | 84 | sequential_labels=cfg.get("sequential_labels", True), |
77 | 85 | n_workers=cfg.get("merge_workers") or default_workers, |
|
88 | 96 | n_objects=n_objects, |
89 | 97 | ) |
90 | 98 |
|
91 | | -shutil.rmtree(stage_path(work_dir, label_name), ignore_errors=True) |
92 | | -# Also drop the checkpoint's completion sentinel (stage.zarr.done): the |
93 | | -# "prepare" rule's stage=touch(STAGE_OK) output must not outlive the store it |
94 | | -# claims exists, or a future rerun (e.g. re-segmenting for new labels) skips |
95 | | -# "prepare" and "segment" tries to open a stage.zarr that's already gone. |
| 99 | +if not in_place: |
| 100 | + shutil.rmtree(stage_path(work_dir, label_name), ignore_errors=True) |
| 101 | +# Drop the checkpoint's completion sentinel (stage.zarr.done): the "prepare" |
| 102 | +# rule's stage=touch(STAGE_OK) output must not outlive what it claims exists, |
| 103 | +# or a future rerun (e.g. re-segmenting for new labels) skips "prepare" and |
| 104 | +# "segment" writes into a target that is already gone or already merged. |
96 | 105 | Path(f"{stage_path(work_dir, label_name)}.done").unlink(missing_ok=True) |
97 | 106 | print(f"[patchworks] labels written to {group}") |
98 | 107 | open(snakemake.output[0], "w").close() # noqa: F821 |
0 commit comments