patchworks 0.11.7__tar.gz → 0.11.8__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-0.11.7 → patchworks-0.11.8}/.gitignore +2 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/PKG-INFO +5 -3
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/guide/snakemake.md +140 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/pyproject.toml +5 -1
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/config/config.yaml +11 -4
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/profile/slurm/config.yaml +6 -3
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/scripts/_pw.py +19 -2
- patchworks-0.11.7/workflow/.snakemake/iocache/latest.pkl +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/.github/workflows/docs.yml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/.github/workflows/lint.yml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/.github/workflows/release.yml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/.markdownlint-cli2.yaml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/README.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/cliff.toml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/chunks.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/cluster.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/io.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/merge_tile_labels.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/plugins/cellpose.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/plugins/napari.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/plugins/ome_zarr.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/relabel.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/api/tile_process.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/cellpose_2d.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/cellpose_2d.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/cellpose_3d.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/cellpose_3d.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/custom.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/custom_method.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/standalone_merge.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/stardist.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/examples/stardist_2d.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/getting_started.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/guide/gpu_distributed.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/guide/merging.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/guide/ome_zarr_napari.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/guide/performance.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/guide/pitfalls.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/guide/skip_empty.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/guide/tiling.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/docs/index.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/mkdocs.yml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/__init__.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/_chunks.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/_cluster.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/_core.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/_distributed.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/_io.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/_merge.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/_relabel.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/plugins/__init__.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/plugins/cellpose.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/plugins/napari.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/src/patchworks/plugins/ome_zarr.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/tests/test_core.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/tests/test_distributed.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/tests/test_napari.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/tests/test_ome_zarr.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/README.md +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/Snakefile +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/pixi.toml +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/rules/common.smk +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/rules/convert.smk +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/rules/merge.smk +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/rules/segment.smk +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/scripts/convert.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/scripts/fetch_model.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/scripts/merge.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/scripts/prepare_tiles.py +0 -0
- {patchworks-0.11.7 → patchworks-0.11.8}/workflow/scripts/segment_tile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchworks
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.8
|
|
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
|
|
@@ -30,7 +30,8 @@ Requires-Dist: bioio-nd2; extra == 'all'
|
|
|
30
30
|
Requires-Dist: bioio-ome-tiff; extra == 'all'
|
|
31
31
|
Requires-Dist: bioio-tifffile; extra == 'all'
|
|
32
32
|
Requires-Dist: imaris-ims-file-reader; extra == 'all'
|
|
33
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: lxml-html-clean; extra == 'all'
|
|
34
|
+
Requires-Dist: napari[all]>=0.5.5; extra == 'all'
|
|
34
35
|
Requires-Dist: nvidia-ml-py; extra == 'all'
|
|
35
36
|
Requires-Dist: psutil; extra == 'all'
|
|
36
37
|
Requires-Dist: scikit-image; extra == 'all'
|
|
@@ -62,7 +63,8 @@ Provides-Extra: io
|
|
|
62
63
|
Requires-Dist: psutil; extra == 'io'
|
|
63
64
|
Requires-Dist: tqdm; extra == 'io'
|
|
64
65
|
Provides-Extra: napari
|
|
65
|
-
Requires-Dist:
|
|
66
|
+
Requires-Dist: lxml-html-clean; extra == 'napari'
|
|
67
|
+
Requires-Dist: napari[all]>=0.5.5; extra == 'napari'
|
|
66
68
|
Provides-Extra: workflow
|
|
67
69
|
Requires-Dist: snakemake-executor-plugin-slurm; extra == 'workflow'
|
|
68
70
|
Requires-Dist: snakemake>=8; extra == 'workflow'
|
|
@@ -194,6 +194,146 @@ also re-runs a step when its **code, params or software environment** change —
|
|
|
194
194
|
so upgrading patchworks would re-do the conversion and overwrite an existing
|
|
195
195
|
result. Keep `mtime` and reruns happen only when an output is missing or stale.
|
|
196
196
|
|
|
197
|
+
## Custom segmentation function
|
|
198
|
+
|
|
199
|
+
Not using Cellpose? Run **your own** per-tile function — no need to edit the
|
|
200
|
+
package. You write one function; patchworks handles everything around it
|
|
201
|
+
(tiling, halos, skipping empty tiles, the zarr-native merge, global relabelling,
|
|
202
|
+
resume, logs).
|
|
203
|
+
|
|
204
|
+
### The contract
|
|
205
|
+
|
|
206
|
+
Your function is called **once per tile**:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
labels = segment(tile) # plus any kwargs you configure
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| | What you get / must return |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| **Input `tile`** | A NumPy array of **one** tile, with the overlap halo already included. The channel and pyramid level from the config are already selected, so it is purely spatial: `(z, y, x)` for a 3-D run, `(y, x)` for 2-D. Dtype is the image's (e.g. `uint16`). |
|
|
215
|
+
| **Return** | An integer **label** array (not a boolean mask), **same shape** as `tile`. `0` = background; each object a distinct positive integer. |
|
|
216
|
+
| **Labels** | Only need to be unique **within the tile**. Don't try to make them globally unique — the merge step stitches objects across tile borders and renumbers everything to a contiguous `1..N` (`sequential_labels: true`). |
|
|
217
|
+
| **Shape** | Must match the input exactly — patchworks trims the halo off your output, so a wrong shape is an error. Don't crop or resize inside the function. |
|
|
218
|
+
|
|
219
|
+
That is the whole interface. Anything that turns an image tile into a label
|
|
220
|
+
image works: classic image processing, StarDist, a trained model, an external
|
|
221
|
+
binary you shell out to, …
|
|
222
|
+
|
|
223
|
+
### Minimal example (no GPU, no deps beyond scikit-image)
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# my_seg.py
|
|
227
|
+
import numpy as np
|
|
228
|
+
from skimage.measure import label
|
|
229
|
+
|
|
230
|
+
def segment(tile: np.ndarray, sigma: float = 2.0) -> np.ndarray:
|
|
231
|
+
"""Threshold + connected components. Returns int32 labels (0 = bg)."""
|
|
232
|
+
from skimage.filters import gaussian, threshold_otsu
|
|
233
|
+
|
|
234
|
+
smooth = gaussian(tile, sigma=sigma, preserve_range=True)
|
|
235
|
+
thr = threshold_otsu(smooth) if smooth.max() > smooth.min() else np.inf
|
|
236
|
+
return label(smooth > thr).astype("int32")
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
method: "custom"
|
|
241
|
+
label_name: "my_labels"
|
|
242
|
+
custom:
|
|
243
|
+
module: "my_seg" # import name (see "Make it importable")
|
|
244
|
+
function: "segment" # default is "segment"
|
|
245
|
+
kwargs: # optional — forwarded as segment(tile, **kwargs)
|
|
246
|
+
sigma: 1.5
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Real example: StarDist 3-D, with model caching
|
|
250
|
+
|
|
251
|
+
Heavy models must be loaded **once**, not per tile. On SLURM each tile is its
|
|
252
|
+
own process so this matters less, but for local runs one process segments many
|
|
253
|
+
tiles — cache the model at module level (or with `functools.lru_cache`):
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
# stardist_seg.py
|
|
257
|
+
import numpy as np
|
|
258
|
+
|
|
259
|
+
_MODEL = None
|
|
260
|
+
|
|
261
|
+
def _model():
|
|
262
|
+
global _MODEL
|
|
263
|
+
if _MODEL is None: # loaded once per worker process
|
|
264
|
+
from stardist.models import StarDist3D
|
|
265
|
+
_MODEL = StarDist3D.from_pretrained("3D_demo")
|
|
266
|
+
return _MODEL
|
|
267
|
+
|
|
268
|
+
def segment(tile: np.ndarray, prob_thresh: float = 0.5) -> np.ndarray:
|
|
269
|
+
from csbdeep.utils import normalize
|
|
270
|
+
|
|
271
|
+
labels, _ = _model().predict_instances(
|
|
272
|
+
normalize(tile), prob_thresh=prob_thresh
|
|
273
|
+
)
|
|
274
|
+
return labels.astype("int32")
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```yaml
|
|
278
|
+
method: "custom"
|
|
279
|
+
label_name: "stardist"
|
|
280
|
+
custom:
|
|
281
|
+
module: "stardist_seg"
|
|
282
|
+
function: "segment"
|
|
283
|
+
kwargs:
|
|
284
|
+
prob_thresh: 0.5
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Using a GPU? The segment jobs already hold one (the `gres: "gpu:1"` request),
|
|
288
|
+
so just let your framework see it — nothing extra in the config.
|
|
289
|
+
|
|
290
|
+
### Test it before you submit
|
|
291
|
+
|
|
292
|
+
Run your function on one real tile first — it catches shape/dtype bugs in
|
|
293
|
+
seconds instead of after a queue wait. Output must be integer, same shape, `0`
|
|
294
|
+
for background:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from patchworks import load_ome_zarr
|
|
298
|
+
from my_seg import segment
|
|
299
|
+
|
|
300
|
+
img = load_ome_zarr("results/image.zarr", channel=0, level=0)
|
|
301
|
+
tile = img[:, :512, :512].compute() # a small spatial block
|
|
302
|
+
out = segment(tile)
|
|
303
|
+
|
|
304
|
+
assert out.shape == tile.shape, (out.shape, tile.shape)
|
|
305
|
+
assert out.dtype.kind in "iu" # integer labels, not a float mask
|
|
306
|
+
print("objects in tile:", int(out.max()))
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Make it importable on the cluster
|
|
310
|
+
|
|
311
|
+
The segment job runs `import <module>`, so the module must be on the path. Pick
|
|
312
|
+
one:
|
|
313
|
+
|
|
314
|
+
1. **Drop the file in `workflow/scripts/`** — Snakemake adds the script dir to
|
|
315
|
+
`sys.path`, so `module: "my_seg"` just works. Simplest for a single file.
|
|
316
|
+
2. **Install it** into the run env (`pip install -e .`, `pixi add --pypi …`),
|
|
317
|
+
then use its import name. Best for a real package with dependencies.
|
|
318
|
+
3. **Set `PYTHONPATH`** to the file's directory before launching Snakemake.
|
|
319
|
+
|
|
320
|
+
### Cluster checklist
|
|
321
|
+
|
|
322
|
+
- **Dependencies:** the env that runs the **segment** jobs must have everything
|
|
323
|
+
your function imports (`pip`/`pixi add` it). A missing import or any crash
|
|
324
|
+
shows up in `logs/segment/<index>.log`, not the (empty) SLURM log.
|
|
325
|
+
- **Offline GPU nodes:** the built-in `fetch_model` prefetch covers Cellpose
|
|
326
|
+
only. If your function downloads weights/data on first use, fetch them once on
|
|
327
|
+
the **login node** (network access) so they land in shared `$HOME`; otherwise
|
|
328
|
+
the segment jobs fail with `Network is unreachable`. See *Troubleshooting*.
|
|
329
|
+
- **Memory / walltime:** tune `segment:` in `profile/slurm/config.yaml` for your
|
|
330
|
+
model (`mem_mb`, `runtime`) just as for Cellpose.
|
|
331
|
+
- Everything else — tiling, halos, empty-tile skipping, the zarr-native merge,
|
|
332
|
+
resume, and per-tile logs — is identical to a Cellpose run.
|
|
333
|
+
|
|
334
|
+
For full control (your own tiling/merge loop instead of the bundled rules), call
|
|
335
|
+
the public API directly — see *How it works* below.
|
|
336
|
+
|
|
197
337
|
## pixi (instead of conda)
|
|
198
338
|
|
|
199
339
|
Conda is **not** required — Snakemake runs in whatever environment launches it.
|
|
@@ -57,7 +57,11 @@ bioio = [
|
|
|
57
57
|
# imaris reads .ims files natively (HDF5, no JVM) for OME-ZARR conversion.
|
|
58
58
|
imaris = ["imaris-ims-file-reader"]
|
|
59
59
|
# napari enables the interactive viewer plugin.
|
|
60
|
-
napari =
|
|
60
|
+
# - napari >= 0.5: NumPy 2.0 compatible (older 0.4.x uses np.array(copy=False),
|
|
61
|
+
# which raises under NumPy 2 -> ValidationError on Viewer()).
|
|
62
|
+
# - lxml-html-clean: napari's notebook_display imports lxml.html.clean, split
|
|
63
|
+
# into a separate package in lxml >= 5.2 (else ImportError on Viewer()).
|
|
64
|
+
napari = ["napari[all]>=0.5.5", "lxml-html-clean"]
|
|
61
65
|
# workflow runs the Snakemake pipeline (per-tile SLURM jobs across GPUs).
|
|
62
66
|
workflow = ["snakemake>=8", "snakemake-executor-plugin-slurm"]
|
|
63
67
|
dev = ["pytest", "pytest-cov", "scikit-image", "psutil", "tqdm"]
|
|
@@ -12,7 +12,7 @@ work_dir: "/path/to/results" # everything is written under here
|
|
|
12
12
|
# image.zarr/labels/<name>/ — the segmentation (multi-scale), in the image
|
|
13
13
|
|
|
14
14
|
# ---- conversion -------------------------------------------------------------
|
|
15
|
-
reuse_pyramid:
|
|
15
|
+
reuse_pyramid: false # for .ims: copy its own pyramid (fast); else rebuild
|
|
16
16
|
convert_chunks: null # null → patchworks' bounded auto chunks; or [c,z,y,x]
|
|
17
17
|
shard: false # true → pack chunks into shards (zarr v3; fewer files)
|
|
18
18
|
# Re-running reuses an existing image.zarr automatically (skips conversion).
|
|
@@ -29,10 +29,10 @@ skip_empty: true # skip background tiles
|
|
|
29
29
|
empty_threshold: null # null → Otsu; or a number
|
|
30
30
|
|
|
31
31
|
# ---- segmentation -----------------------------------------------------------
|
|
32
|
-
method: "cellpose" # "cellpose" (GPU)
|
|
33
|
-
label_name: "
|
|
32
|
+
method: "cellpose" # "cellpose" (GPU), "threshold" (no GPU; testing), "custom"
|
|
33
|
+
label_name: "cellpose_labels"
|
|
34
34
|
cellpose:
|
|
35
|
-
model: "
|
|
35
|
+
model: "nuclei"
|
|
36
36
|
diameter: 30
|
|
37
37
|
do_3D: true
|
|
38
38
|
gpu: true
|
|
@@ -40,6 +40,13 @@ cellpose:
|
|
|
40
40
|
# flow_threshold: 0.4
|
|
41
41
|
# cellprob_threshold: 0.0
|
|
42
42
|
|
|
43
|
+
# Your own per-tile function (method: "custom"). See the "custom function"
|
|
44
|
+
# section of docs/guide/snakemake.md.
|
|
45
|
+
# custom:
|
|
46
|
+
# module: "my_seg" # importable on the cluster (workflow/scripts/, PYTHONPATH, or pip-installed)
|
|
47
|
+
# function: "segment" # def segment(tile: np.ndarray) -> np.ndarray (int32 labels)
|
|
48
|
+
# kwargs: {} # optional extra keyword args
|
|
49
|
+
|
|
43
50
|
# ---- pyramid for the labels -------------------------------------------------
|
|
44
51
|
pyramid_levels: 5
|
|
45
52
|
pyramid_downscale: 2
|
|
@@ -33,11 +33,14 @@ set-resources:
|
|
|
33
33
|
mem_mb: 32000
|
|
34
34
|
runtime: 120
|
|
35
35
|
segment:
|
|
36
|
-
# one GPU per tile — this is what spreads Cellpose across GPUs
|
|
36
|
+
# one GPU per tile — this is what spreads Cellpose across GPUs.
|
|
37
|
+
# Use the plugin's native `gres` resource → emits --gres=gpu:1, which is
|
|
38
|
+
# how scicore binds a CUDA device. (The `gpu:` resource emits --gpus and
|
|
39
|
+
# leaves the job without a device.)
|
|
37
40
|
slurm_partition: "rtx4090"
|
|
38
|
-
gpu:
|
|
41
|
+
gres: "gpu:1"
|
|
39
42
|
qos: "rtx4090-6hours" # scicore: <partition>-<duration> QOS
|
|
40
|
-
mem_mb: 32000 # plenty — a tile used ~1G
|
|
43
|
+
mem_mb: 32000 # plenty — a tile used ~1G
|
|
41
44
|
cpus_per_task: 4
|
|
42
45
|
runtime: 360 # 6 hours — must match the QOS, NOT 120 (=2h → killed early)
|
|
43
46
|
merge:
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
12
|
import sys
|
|
13
|
+
from functools import partial
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
|
|
15
16
|
from patchworks import load_ome_zarr
|
|
@@ -124,8 +125,9 @@ def build_fn(cfg):
|
|
|
124
125
|
Parameters
|
|
125
126
|
----------
|
|
126
127
|
cfg : dict
|
|
127
|
-
Snakemake config. ``method`` selects ``"cellpose"`` (default)
|
|
128
|
-
|
|
128
|
+
Snakemake config. ``method`` selects ``"cellpose"`` (default), a simple
|
|
129
|
+
``"threshold"`` (testing / no-GPU), or ``"custom"`` to import your own
|
|
130
|
+
function (``cfg["custom"] = {module, function, kwargs}``).
|
|
129
131
|
|
|
130
132
|
Returns
|
|
131
133
|
-------
|
|
@@ -133,6 +135,21 @@ def build_fn(cfg):
|
|
|
133
135
|
``(ndarray) -> ndarray`` returning integer labels.
|
|
134
136
|
"""
|
|
135
137
|
method = cfg.get("method", "cellpose")
|
|
138
|
+
if method == "custom":
|
|
139
|
+
# Import a user-provided function, e.g.
|
|
140
|
+
# custom: {module: my_seg, function: segment, kwargs: {...}}
|
|
141
|
+
# The module must be importable on the cluster (a file in
|
|
142
|
+
# workflow/scripts/, on PYTHONPATH, or an installed package).
|
|
143
|
+
import importlib
|
|
144
|
+
|
|
145
|
+
spec = cfg["custom"]
|
|
146
|
+
fn = getattr(
|
|
147
|
+
importlib.import_module(spec["module"]),
|
|
148
|
+
spec.get("function", "segment"),
|
|
149
|
+
)
|
|
150
|
+
kwargs = spec.get("kwargs") or {}
|
|
151
|
+
return partial(fn, **kwargs) if kwargs else fn
|
|
152
|
+
|
|
136
153
|
if method == "threshold":
|
|
137
154
|
|
|
138
155
|
def fn(tile):
|
|
Binary file
|
|
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
|