patchworks 2.6.4__tar.gz → 2.6.5__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.4 → patchworks-2.6.5}/PKG-INFO +1 -1
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/snakemake.md +12 -3
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_run_multi.py +72 -12
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/scripts/run_multi.py +119 -15
- {patchworks-2.6.4 → patchworks-2.6.5}/.github/workflows/docs.yml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/.github/workflows/lint.yml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/.github/workflows/release.yml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/.gitignore +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/.markdownlint-cli2.yaml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/LICENSE +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/README.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/cliff.toml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/chunks.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/cluster.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/io.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/plugins/dog.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/plugins/napari.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/postprocess.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/relabel.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/api/tile_process.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/assets/logo.png +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/custom.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/custom_method.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/dog.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/dog.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/standalone_merge.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/stardist.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/examples/stardist_2d.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/getting_started.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/custom_segmentation.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/label_relations.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/measurements.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/merging.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/performance.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/pitfalls.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/skip_empty.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/guide/tiling.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/docs/index.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/mkdocs.yml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/pyproject.toml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/__init__.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_chunks.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_cluster.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_core.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_distributed.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_gpu.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_io.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_merge.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_notify.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_occupancy.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_postprocess.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_progress.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_relabel.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/_relations.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/plugins/dog.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_allocation.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_core.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_distributed.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_dog.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_gpu.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_napari.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_notify.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_occupancy.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_ome_zarr.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_postprocess.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_progress.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/tests/test_relations.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/README.md +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/Snakefile +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/config/common.yaml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/config/config.yaml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/config/config_cilia.yaml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/config/config_cyto.yaml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/config/config_nuclei.yaml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/config/multi.yaml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/pixi.toml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/profile/slurm/config.yaml +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/rules/common.smk +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/rules/convert.smk +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/rules/merge.smk +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/rules/segment.smk +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/scripts/_pw.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/scripts/build_occupancy.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/scripts/convert.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/scripts/merge.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/workflow/scripts/relate.py +0 -0
- {patchworks-2.6.4 → patchworks-2.6.5}/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.5
|
|
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
|
|
@@ -398,12 +398,21 @@ results/image.zarr/labels/nuclei_labels/
|
|
|
398
398
|
results/image.zarr/labels/cyto_labels/
|
|
399
399
|
```
|
|
400
400
|
|
|
401
|
-
!!! tip "Keep `
|
|
402
|
-
Different segmentations of the same image can use different `channel`
|
|
403
|
-
`cellpose:` settings freely, but keep `
|
|
401
|
+
!!! tip "Keep `level` identical across configs; `tile_shape` usually takes care of itself"
|
|
402
|
+
Different segmentations of the same image can use different `channel`
|
|
403
|
+
and `cellpose:` settings freely, but keep `level` the same across
|
|
404
404
|
configs — the label arrays then share the exact same chunk layout, which
|
|
405
405
|
[`label_relations()`](label_relations.md) requires.
|
|
406
406
|
|
|
407
|
+
`tile_shape` is a little more forgiving under `run_multi.py`: leaving it
|
|
408
|
+
on `"auto"` (the default) would normally size a `nuclei_channel` config's
|
|
409
|
+
tile smaller than a single-channel one (the sizer charges per channel),
|
|
410
|
+
breaking that same requirement — `run_multi.py` detects this and pins
|
|
411
|
+
every config to the smallest computed tile automatically, so mixed
|
|
412
|
+
single-/two-channel configs under `"auto"` just work. Setting an
|
|
413
|
+
explicit `tile_shape` yourself still requires it to be identical across
|
|
414
|
+
configs, same as before.
|
|
415
|
+
|
|
407
416
|
See [Relating labels across segmentations](label_relations.md) for what
|
|
408
417
|
`label_relations()` returns and how to save it yourself — the cluster
|
|
409
418
|
workflow's own automation is below.
|
|
@@ -186,28 +186,88 @@ def test_relate_script_has_the_real_bookkeeping():
|
|
|
186
186
|
assert "openpyxl" in src
|
|
187
187
|
|
|
188
188
|
|
|
189
|
-
def
|
|
190
|
-
"""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
def test_mixed_nuclei_channel_auto_passes_validation():
|
|
190
|
+
"""A channel-count mismatch under `tile_shape: "auto"` is no longer
|
|
191
|
+
|
|
192
|
+
refused at validation time -- it's resolved automatically instead (see
|
|
193
|
+
`_resolve_shared_tile_shape`), which needs the converted image's real
|
|
194
|
+
shape/dtype and so can only run after phase A, not from
|
|
195
|
+
`_validate_configs()`. This used to `sys.exit` here; asserting that
|
|
196
|
+
would now be testing the wrong layer.
|
|
196
197
|
"""
|
|
197
198
|
paths = [Path("a.yaml"), Path("b.yaml")]
|
|
198
199
|
base = {"work_dir": "/w", "tile_shape": "auto", "level": 0}
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
mixed = [
|
|
201
202
|
{**base, "label_name": "a", "channel": 0, "nuclei_channel": 1},
|
|
202
203
|
{**base, "label_name": "b", "channel": 2},
|
|
203
204
|
]
|
|
204
|
-
|
|
205
|
-
_validate_configs(paths, bad)
|
|
205
|
+
assert _validate_configs(paths, mixed) == "/w"
|
|
206
206
|
|
|
207
207
|
# Same pair with one explicit shape is fine: both get that tile.
|
|
208
|
-
pinned = [{**c, "tile_shape": [16, 512, 512]} for c in
|
|
208
|
+
pinned = [{**c, "tile_shape": [16, 512, 512]} for c in mixed]
|
|
209
209
|
assert _validate_configs(paths, pinned) == "/w"
|
|
210
210
|
|
|
211
211
|
# And "auto" is fine when every config carries the same channel count.
|
|
212
|
-
both = [{**
|
|
212
|
+
both = [{**mixed[0]}, {**mixed[1], "nuclei_channel": 3}]
|
|
213
213
|
assert _validate_configs(paths, both) == "/w"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_resolve_shared_tile_shape_pins_the_smallest_candidate(
|
|
217
|
+
tmp_path, monkeypatch
|
|
218
|
+
):
|
|
219
|
+
"""The shared tile must be the tightest of every config's own budget.
|
|
220
|
+
|
|
221
|
+
A larger tile than some config's own sizer output would ask that config
|
|
222
|
+
for more memory than its settings were judged to need -- only the
|
|
223
|
+
smallest candidate is safe for every config at once.
|
|
224
|
+
"""
|
|
225
|
+
import run_multi
|
|
226
|
+
|
|
227
|
+
class _FakeImage:
|
|
228
|
+
shape = (10, 100, 100)
|
|
229
|
+
dtype = "uint16"
|
|
230
|
+
|
|
231
|
+
calls = []
|
|
232
|
+
|
|
233
|
+
def _fake_load_ome_zarr(store, *, channel, level):
|
|
234
|
+
calls.append((store, channel, level))
|
|
235
|
+
return _FakeImage()
|
|
236
|
+
|
|
237
|
+
# One tile per config, matched up by call order (channel 0 then 1).
|
|
238
|
+
fake_tiles = [(8, 64, 64), (4, 32, 32)]
|
|
239
|
+
|
|
240
|
+
def _fake_sizer_cellpose(shape, dtype, **kwargs):
|
|
241
|
+
return fake_tiles[len(calls) - 1]
|
|
242
|
+
|
|
243
|
+
monkeypatch.setattr(
|
|
244
|
+
"patchworks.load_ome_zarr", _fake_load_ome_zarr, raising=False
|
|
245
|
+
)
|
|
246
|
+
monkeypatch.setattr(
|
|
247
|
+
"patchworks.auto_tile_shape_cellpose",
|
|
248
|
+
_fake_sizer_cellpose,
|
|
249
|
+
raising=False,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
seg_cfgs = [
|
|
253
|
+
{
|
|
254
|
+
"channel": 0,
|
|
255
|
+
"level": 0,
|
|
256
|
+
"method": "cellpose",
|
|
257
|
+
"cellpose": {"do_3D": True, "gpu": True},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"channel": 1,
|
|
261
|
+
"level": 0,
|
|
262
|
+
"nuclei_channel": 2,
|
|
263
|
+
"method": "cellpose",
|
|
264
|
+
"cellpose": {"do_3D": True, "gpu": True},
|
|
265
|
+
},
|
|
266
|
+
]
|
|
267
|
+
out = run_multi._resolve_shared_tile_shape(
|
|
268
|
+
seg_cfgs, "/w/image.zarr", str(tmp_path)
|
|
269
|
+
)
|
|
270
|
+
assert out == tmp_path / ".multi_tile_shape.generated.yaml"
|
|
271
|
+
written = yaml.safe_load(out.read_text())
|
|
272
|
+
assert written == {"tile_shape": [4, 32, 32]} # the smaller candidate
|
|
273
|
+
assert len(calls) == 2
|
|
@@ -54,6 +54,7 @@ def _snakemake_cmd(
|
|
|
54
54
|
extra: list[str] | None = None,
|
|
55
55
|
jobname_prefix: str | None = None,
|
|
56
56
|
common: Path | None = None,
|
|
57
|
+
extra_configfiles: list[Path] | None = None,
|
|
57
58
|
) -> list[str]:
|
|
58
59
|
"""Build one snakemake invocation.
|
|
59
60
|
|
|
@@ -66,10 +67,17 @@ def _snakemake_cmd(
|
|
|
66
67
|
settings every config shares -- the input, the work_dir, everything
|
|
67
68
|
``convert`` reads -- live in one file and the per-config file carries only
|
|
68
69
|
what actually differs.
|
|
70
|
+
|
|
71
|
+
*extra_configfiles*, when given, are appended after *configfile* and so
|
|
72
|
+
win over both it and *common* -- used to pin a driver-computed value
|
|
73
|
+
(e.g. a resolved ``tile_shape``) across every config without editing any
|
|
74
|
+
config file on disk.
|
|
69
75
|
"""
|
|
70
76
|
configfiles = [str(configfile.resolve())]
|
|
71
77
|
if common is not None:
|
|
72
78
|
configfiles.insert(0, str(common.resolve()))
|
|
79
|
+
if extra_configfiles:
|
|
80
|
+
configfiles += [str(p.resolve()) for p in extra_configfiles]
|
|
73
81
|
cmd = [
|
|
74
82
|
"snakemake",
|
|
75
83
|
"-s",
|
|
@@ -234,21 +242,12 @@ def _validate_configs(paths: list[Path], cfgs: list[dict]) -> str:
|
|
|
234
242
|
|
|
235
243
|
# `tile_shape: "auto"` is identical as a *value* across configs while
|
|
236
244
|
# producing different tiles: the sizer charges per channel, so a config
|
|
237
|
-
# with nuclei_channel gets a smaller one. The label groups then
|
|
238
|
-
# chunk layout and label_relations
|
|
239
|
-
# run.
|
|
240
|
-
#
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
problems.append(
|
|
244
|
-
'tile_shape: "auto" sizes a nuclei_channel config smaller '
|
|
245
|
-
"(a tile carries two channels), so the label groups would "
|
|
246
|
-
"not share a chunk layout and label_relations would fail "
|
|
247
|
-
f"after every segmentation had run; got "
|
|
248
|
-
f"{_spread('nuclei_channel')}. Set one explicit tile_shape in "
|
|
249
|
-
"the file `common:` points at, sized for the two-channel "
|
|
250
|
-
"config."
|
|
251
|
-
)
|
|
245
|
+
# with nuclei_channel gets a smaller one. The label groups would then
|
|
246
|
+
# disagree on chunk layout and label_relations would raise -- after every
|
|
247
|
+
# segmentation had run. That used to be a hard error asking for a manual
|
|
248
|
+
# explicit tile_shape; main() now resolves and pins one automatically
|
|
249
|
+
# (see _resolve_shared_tile_shape), once the converted image exists to
|
|
250
|
+
# size against, so there is nothing to check here anymore.
|
|
252
251
|
|
|
253
252
|
# Phase A converts once, from the first config. Anything `convert` reads
|
|
254
253
|
# out of a later config is therefore silently ignored -- someone setting
|
|
@@ -292,6 +291,92 @@ def _validate_configs(paths: list[Path], cfgs: list[dict]) -> str:
|
|
|
292
291
|
return work_dirs.pop()
|
|
293
292
|
|
|
294
293
|
|
|
294
|
+
def _resolve_shared_tile_shape(
|
|
295
|
+
seg_cfgs: list[dict], image_store: str, work_dir: str
|
|
296
|
+
) -> Path:
|
|
297
|
+
"""Auto-size ``tile_shape`` once, shared across every config.
|
|
298
|
+
|
|
299
|
+
``tile_shape: "auto"`` resolves differently per config when
|
|
300
|
+
``nuclei_channel`` differs -- the sizer charges per channel, so a
|
|
301
|
+
two-channel config gets a smaller tile. Left alone, the label groups
|
|
302
|
+
would end up with different chunk layouts and ``label_relations`` would
|
|
303
|
+
raise, after every segmentation had already run.
|
|
304
|
+
|
|
305
|
+
Computes what each config's own settings (channel count, ``do_3D``,
|
|
306
|
+
``diameter``, GPU budget) would actually produce, then pins every config
|
|
307
|
+
to the *smallest* of them by voxel count -- the most memory-constrained
|
|
308
|
+
case, and safe for every config since a smaller tile only ever asks for
|
|
309
|
+
less memory than that config's own budget allows, never more. Configs
|
|
310
|
+
can therefore use different ``do_3D``/``diameter``/channel-count
|
|
311
|
+
settings and still end up with one shared, valid ``tile_shape``.
|
|
312
|
+
|
|
313
|
+
Only called once the converted image exists (the sizer needs its real
|
|
314
|
+
shape/dtype), so this runs from ``main()`` after phase A, not from
|
|
315
|
+
``_validate_configs()``.
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
Path
|
|
320
|
+
A generated one-key YAML file (``tile_shape: [...]``), meant to be
|
|
321
|
+
passed as an ``extra_configfiles`` entry to ``_snakemake_cmd`` so it
|
|
322
|
+
overrides every config's own (or common's) ``tile_shape`` value.
|
|
323
|
+
"""
|
|
324
|
+
from functools import partial
|
|
325
|
+
|
|
326
|
+
import numpy as np
|
|
327
|
+
from patchworks import (
|
|
328
|
+
auto_tile_shape,
|
|
329
|
+
auto_tile_shape_cellpose,
|
|
330
|
+
load_ome_zarr,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
candidates = []
|
|
334
|
+
for cfg in seg_cfgs:
|
|
335
|
+
image = load_ome_zarr(
|
|
336
|
+
image_store, channel=cfg["channel"], level=int(cfg.get("level", 0))
|
|
337
|
+
)
|
|
338
|
+
gpu_gb = cfg.get("gpu_memory_gb")
|
|
339
|
+
gpu_bytes = int(gpu_gb * 1024**3) if gpu_gb else None
|
|
340
|
+
n_channels = 2 if cfg.get("nuclei_channel") is not None else 1
|
|
341
|
+
method = cfg.get("method", "cellpose")
|
|
342
|
+
if method == "cellpose":
|
|
343
|
+
cp = cfg["cellpose"]
|
|
344
|
+
sizer = partial(
|
|
345
|
+
auto_tile_shape_cellpose,
|
|
346
|
+
do_3D=cp.get("do_3D", False),
|
|
347
|
+
use_gpu=cp.get("gpu", True),
|
|
348
|
+
diameter=cp.get("diameter"),
|
|
349
|
+
gpu_memory=gpu_bytes,
|
|
350
|
+
n_channels=n_channels,
|
|
351
|
+
)
|
|
352
|
+
else:
|
|
353
|
+
sizer = partial(
|
|
354
|
+
auto_tile_shape,
|
|
355
|
+
use_gpu=gpu_bytes is not None,
|
|
356
|
+
gpu_memory=gpu_bytes,
|
|
357
|
+
n_channels=n_channels,
|
|
358
|
+
)
|
|
359
|
+
candidates.append(tuple(int(x) for x in sizer(image.shape, image.dtype)))
|
|
360
|
+
|
|
361
|
+
tile_shape = min(candidates, key=lambda t: int(np.prod(t)))
|
|
362
|
+
print(
|
|
363
|
+
f'[run_multi] tile_shape: "auto" resolves differently across '
|
|
364
|
+
f"configs (nuclei_channel differs); pinning every config to the "
|
|
365
|
+
f"smallest computed tile {list(tile_shape)} so the label arrays "
|
|
366
|
+
f"share a chunk layout. Candidates were {[list(c) for c in candidates]}.",
|
|
367
|
+
flush=True,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
override_path = Path(work_dir) / ".multi_tile_shape.generated.yaml"
|
|
371
|
+
override_path.write_text(
|
|
372
|
+
"# Generated by run_multi.py -- pins tile_shape across configs so\n"
|
|
373
|
+
"# label_relations sees matching chunk layouts. Safe to delete; it\n"
|
|
374
|
+
"# is regenerated on the next multi-config run.\n"
|
|
375
|
+
f"tile_shape: {list(tile_shape)}\n"
|
|
376
|
+
)
|
|
377
|
+
return override_path
|
|
378
|
+
|
|
379
|
+
|
|
295
380
|
def _resolve(workflow_dir: Path, path_str: str) -> Path:
|
|
296
381
|
path = Path(path_str)
|
|
297
382
|
return path if path.is_absolute() else workflow_dir / path
|
|
@@ -453,6 +538,24 @@ def main() -> None:
|
|
|
453
538
|
)
|
|
454
539
|
sys.exit(rc)
|
|
455
540
|
|
|
541
|
+
# tile_shape: "auto" resolves differently per config when nuclei_channel
|
|
542
|
+
# differs (the sizer charges per channel) -- pin every config to one
|
|
543
|
+
# shared, computed value so the label arrays end up with matching chunk
|
|
544
|
+
# layouts. Needs the just-converted image, so this can only happen here,
|
|
545
|
+
# not in _validate_configs(). Dry runs never reach a real image.zarr.
|
|
546
|
+
tile_override = None
|
|
547
|
+
if not args.dry_run and {
|
|
548
|
+
cfg.get("tile_shape", "auto") for cfg in seg_cfgs
|
|
549
|
+
} == {"auto"}:
|
|
550
|
+
channel_counts = {
|
|
551
|
+
2 if cfg.get("nuclei_channel") is not None else 1
|
|
552
|
+
for cfg in seg_cfgs
|
|
553
|
+
}
|
|
554
|
+
if len(channel_counts) > 1:
|
|
555
|
+
tile_override = _resolve_shared_tile_shape(
|
|
556
|
+
seg_cfgs, image_store, work_dir
|
|
557
|
+
)
|
|
558
|
+
|
|
456
559
|
# Phase B: the segmentations touch disjoint files under
|
|
457
560
|
# work_dir/<label_name>/, so run them together and let the GPU partition
|
|
458
561
|
# stay busy instead of idling through each config's prepare and merge.
|
|
@@ -470,6 +573,7 @@ def main() -> None:
|
|
|
470
573
|
# Names the config in squeue, so concurrent runs are tellable apart.
|
|
471
574
|
jobname_prefix=slurm_jobname_prefix(cfg["label_name"]),
|
|
472
575
|
common=common_path,
|
|
576
|
+
extra_configfiles=[tile_override] if tile_override else None,
|
|
473
577
|
)
|
|
474
578
|
print(f"[run_multi] $ {' '.join(cmd)}", flush=True)
|
|
475
579
|
procs.append((cfg_path.name, subprocess.Popen(cmd, cwd=workflow_dir)))
|
|
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
|
|
File without changes
|