Skip to content

Commit 7661e17

Browse files
chore: run pre-commit
1 parent 0b38c92 commit 7661e17

File tree

7 files changed

+72
-78
lines changed

7 files changed

+72
-78
lines changed

src/ilastik_tasks/__FRACTAL_MANIFEST__.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,17 @@
3737
"zarr_url": {
3838
"title": "Zarr Url",
3939
"type": "string",
40-
"description": "Missing description"
40+
"description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
4141
},
4242
"level": {
4343
"title": "Level",
4444
"type": "integer",
45-
"description": "Missing description"
45+
"description": "Pyramid level of the image to be segmented. Choose `0` to process at full resolution."
4646
},
4747
"channel": {
4848
"$ref": "#/$defs/ChannelInputModel",
4949
"title": "Channel",
50-
"description": "Missing description"
50+
"description": "Primary channel for pixel classification; requires either `wavelength_id` (e.g. `A01_C01`) or `label` (e.g. `DAPI`)."
5151
},
5252
"channel2": {
5353
"allOf": [
@@ -56,58 +56,58 @@
5656
}
5757
],
5858
"title": "Channel2",
59-
"description": "Missing description"
59+
"description": "Second channel for pixel classification (in the same format as `channel`). Use only if second channel has also been used during Ilastik model training."
6060
},
6161
"input_ROI_table": {
6262
"default": "FOV_ROI_table",
6363
"title": "Input Roi Table",
6464
"type": "string",
65-
"description": "Missing description"
65+
"description": "Name of the ROI table over which the task loops to apply Cellpose segmentation. Examples: `FOV_ROI_table` => loop over the field of views, `organoid_ROI_table` => loop over the organoid ROI table (generated by another task), `well_ROI_table` => process the whole well as one image."
6666
},
6767
"output_ROI_table": {
6868
"title": "Output Roi Table",
6969
"type": "string",
70-
"description": "Missing description"
70+
"description": "If provided, a ROI table with that name is created, which will contain the bounding boxes of the newly segmented labels. ROI tables should have `ROI` in their name."
7171
},
7272
"output_label_name": {
7373
"title": "Output Label Name",
7474
"type": "string",
75-
"description": "Missing description"
75+
"description": "Name of the output label image (e.g. `\"embryo\"`)."
7676
},
7777
"use_masks": {
7878
"default": true,
7979
"title": "Use Masks",
8080
"type": "boolean",
81-
"description": "Missing description"
81+
"description": "If `True`, try to use masked loading and fall back to `use_masks=False` if the ROI table is not suitable. Masked loading is relevant when only a subset of the bounding box should actually be processed (e.g. running within `emb_ROI_table`)."
8282
},
8383
"ilastik_model": {
8484
"title": "Ilastik Model",
8585
"type": "string",
86-
"description": "Missing description"
86+
"description": "Path to the Ilastik model (e.g. `\"somemodel.ilp\"`)."
8787
},
8888
"foreground_class": {
8989
"default": 0,
9090
"title": "Foreground Class",
9191
"type": "integer",
92-
"description": "Missing description"
92+
"description": "Class to be considered as foreground during prediction thresholding."
9393
},
9494
"threshold": {
9595
"default": 0.5,
9696
"title": "Threshold",
9797
"type": "number",
98-
"description": "Missing description"
98+
"description": "Probabiltiy threshold for the Ilastik model."
9999
},
100100
"min_size": {
101101
"default": 15,
102102
"title": "Min Size",
103103
"type": "integer",
104-
"description": "Missing description"
104+
"description": "Minimum size of the segmented objects (in pixels)."
105105
},
106106
"overwrite": {
107107
"default": true,
108108
"title": "Overwrite",
109109
"type": "boolean",
110-
"description": "Missing description"
110+
"description": "If `True`, overwrite the task output."
111111
}
112112
},
113113
"required": [
@@ -119,7 +119,7 @@
119119
"type": "object",
120120
"title": "IlastikPixelClassificationSegmentation"
121121
},
122-
"docs_info": "## ilastik_pixel_classification_segmentation\nRun Ilastik Pixel Classification on a Zarr image.\n\nArgs:\nzarr_url: Path or url to the individual OME-Zarr image to be processed.\n (standard argument for Fractal tasks, managed by Fractal server).\nlevel: Pyramid level of the image to be segmented. Choose `0` to\n process at full resolution.\nchannel: Primary channel for pixel classification; requires either\n `wavelength_id` (e.g. `A01_C01`) or `label` (e.g. `DAPI`).\nchannel2: Second channel for pixel classification (in the same format as\n `channel`). Use only if second channel has also been used during \n Ilastik model training.\ninput_ROI_table: Name of the ROI table over which the task loops to\n apply Cellpose segmentation. Examples: `FOV_ROI_table` => loop over\n the field of views, `organoid_ROI_table` => loop over the organoid\n ROI table (generated by another task), `well_ROI_table` => process\n the whole well as one image.\noutput_ROI_table: If provided, a ROI table with that name is created,\n which will contain the bounding boxes of the newly segmented\n labels. ROI tables should have `ROI` in their name.\noutput_label_name: Name of the output label image (e.g. `\"embryo\"`).\nuse_masks: If `True`, try to use masked loading and fall back to\n `use_masks=False` if the ROI table is not suitable. Masked\n loading is relevant when only a subset of the bounding box should\n actually be processed (e.g. running within `emb_ROI_table`).\nilastik_model: Path to the Ilastik model (e.g. `\"somemodel.ilp\"`).\nforeground_class: Class to be considered as foreground during\n prediction thresholding.\nthreshold: Probabiltiy threshold for the Ilastik model.\nmin_size: Minimum size of the segmented objects (in pixels).\noverwrite: If `True`, overwrite the task output.\n"
122+
"docs_info": "## ilastik_pixel_classification_segmentation\nRun Ilastik Pixel Classification on a Zarr image.\n"
123123
}
124124
],
125125
"has_args_schemas": true,

src/ilastik_tasks/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
"""
2-
Collection of Fractal task to run Headless ilastik workflows
3-
"""
1+
"""Collection of Fractal task to run Headless ilastik workflows"""
2+
43
from importlib.metadata import PackageNotFoundError, version
54

65
try:
76
__version__ = version("ilastik-tasks")
87
except PackageNotFoundError:
9-
__version__ = "uninstalled"
8+
__version__ = "uninstalled"

src/ilastik_tasks/dev/create_manifest.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
"""
2-
Generate JSON schemas for task arguments afresh, and write them
3-
to the package manifest.
4-
"""
1+
"""Generate JSON schemas for task arguments."""
2+
53
from fractal_tasks_core.dev.create_manifest import create_manifest
64

75
if __name__ == "__main__":

src/ilastik_tasks/ilastik_pixel_classification_segmentation.py

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,19 @@
1919
Alexa McIntyre <[email protected]>
2020
Ruth Hornbachner <[email protected]>
2121
"""
22-
import os
22+
2323
import json
2424
import logging
25+
import os
2526
from typing import Any, Optional
2627

2728
import anndata as ad
2829
import dask.array as da
2930
import fractal_tasks_core
3031
import numpy as np
31-
from skimage.measure import label, regionprops
32-
from skimage.morphology import remove_small_holes
3332
import vigra
3433
import zarr
35-
36-
from fractal_tasks_core.channels import (ChannelInputModel,
37-
get_channel_from_image_zarr
38-
)
34+
from fractal_tasks_core.channels import ChannelInputModel, get_channel_from_image_zarr
3935
from fractal_tasks_core.labels import prepare_label_group
4036
from fractal_tasks_core.masked_loading import masked_loading_wrapper
4137
from fractal_tasks_core.ngff import load_NgffImageMeta
@@ -57,6 +53,8 @@
5753
PreloadedArrayDatasetInfo,
5854
)
5955
from pydantic import validate_call
56+
from skimage.measure import label, regionprops
57+
from skimage.morphology import remove_small_holes
6058

6159
logger = logging.getLogger(__name__)
6260

@@ -86,11 +84,12 @@ def segment_ROI(
8684
Args:
8785
input_data: np.ndarray of shape (t, z, y, x, c).
8886
shell: Ilastik headless shell.
89-
foreground_class: Class to be considered as foreground
87+
foreground_class: Class to be considered as foreground
9088
during prediction thresholding.
9189
threshold: Threshold for the Ilastik model.
9290
min_size: Minimum size for the Ilastik model.
9391
label_dtype: Label images are cast into this `np.dtype`.
92+
9493
Returns:
9594
np.ndarray: Segmented image. Shape (z, y, x).
9695
"""
@@ -106,7 +105,7 @@ def segment_ROI(
106105
)
107106
}
108107
]
109-
108+
110109
ilastik_output = shell.workflow.batchProcessingApplet.run_export(
111110
data, export_to_array=True
112111
)[0]
@@ -122,9 +121,11 @@ def segment_ROI(
122121
# take mask of regions above threshold
123122
ilastik_output[ilastik_output < threshold] = 0
124123
ilastik_output[ilastik_output >= threshold] = 1
125-
124+
126125
# remove small holes
127-
ilastik_output = remove_small_holes(ilastik_output.astype(bool), area_threshold=min_size)
126+
ilastik_output = remove_small_holes(
127+
ilastik_output.astype(bool), area_threshold=min_size
128+
)
128129

129130
# label image
130131
ilastik_labels = label(ilastik_output)
@@ -145,7 +146,7 @@ def segment_ROI(
145146
ilastik_labels = label(ilastik_labels)
146147
print(f"number of labels after filtering for size = {ilastik_labels.max()}")
147148
label_props = regionprops(ilastik_labels)
148-
149+
149150
return ilastik_labels.astype(label_dtype)
150151

151152

@@ -179,7 +180,7 @@ def ilastik_pixel_classification_segmentation(
179180
channel: Primary channel for pixel classification; requires either
180181
`wavelength_id` (e.g. `A01_C01`) or `label` (e.g. `DAPI`).
181182
channel2: Second channel for pixel classification (in the same format as
182-
`channel`). Use only if second channel has also been used during
183+
`channel`). Use only if second channel has also been used during
183184
Ilastik model training.
184185
input_ROI_table: Name of the ROI table over which the task loops to
185186
apply Cellpose segmentation. Examples: `FOV_ROI_table` => loop over
@@ -202,7 +203,7 @@ def ilastik_pixel_classification_segmentation(
202203
overwrite: If `True`, overwrite the task output.
203204
"""
204205
logger.info(f"Processing {zarr_url=}")
205-
206+
206207
# Preliminary checks on Cellpose model
207208
if not os.path.exists(ilastik_model):
208209
raise ValueError(f"{ilastik_model=} path does not exist.")
@@ -226,11 +227,14 @@ def ilastik_pixel_classification_segmentation(
226227
# Check model channel requirements
227228
expected_channels = check_ilastik_model_channels(shell)
228229
if expected_channels == 2 and channel2 is None:
229-
raise ValueError(f"Ilastik model expects two channels as "
230-
"input but only one channel was provided")
230+
raise ValueError(
231+
"Ilastik model expects two channels as "
232+
"input but only one channel was provided"
233+
)
231234
elif expected_channels == 1 and channel2 is not None:
232-
raise ValueError(f"Ilastik model expects 1 channel as "
233-
"input but two channels were provided")
235+
raise ValueError(
236+
"Ilastik model expects 1 channel as " "input but two channels were provided"
237+
)
234238

235239
# Find channel index
236240
tmp_channel = get_channel_from_image_zarr(
@@ -242,7 +246,7 @@ def ilastik_pixel_classification_segmentation(
242246
ind_channel = tmp_channel.index
243247
else:
244248
return
245-
249+
246250
# Find channel index for second channel, if one is provided
247251
if channel2:
248252
tmp_channel_2 = get_channel_from_image_zarr(
@@ -275,9 +279,7 @@ def ilastik_pixel_classification_segmentation(
275279
ROI_table = ad.read_zarr(ROI_table_path)
276280

277281
# Perform some checks on the ROI table
278-
valid_ROI_table = is_ROI_table_valid(
279-
table_path=ROI_table_path, use_masks=use_masks
280-
)
282+
valid_ROI_table = is_ROI_table_valid(table_path=ROI_table_path, use_masks=use_masks)
281283
if use_masks and not valid_ROI_table:
282284
logger.info(
283285
f"ROI table at {ROI_table_path} cannot be used for masked "
@@ -306,7 +308,7 @@ def ilastik_pixel_classification_segmentation(
306308

307309
# Load zattrs file
308310
zattrs_file = f"{zarr_url}/.zattrs"
309-
with open(zattrs_file, "r") as jsonfile:
311+
with open(zattrs_file) as jsonfile:
310312
zattrs = json.load(jsonfile)
311313

312314
# Preliminary checks on multiscales
@@ -335,7 +337,7 @@ def ilastik_pixel_classification_segmentation(
335337
reference_level=level,
336338
remove_channel_axis=True,
337339
)
338-
340+
339341
label_attrs = {
340342
"image-label": {
341343
"version": __OME_NGFF_VERSION__,
@@ -346,9 +348,7 @@ def ilastik_pixel_classification_segmentation(
346348
"name": output_label_name,
347349
"version": __OME_NGFF_VERSION__,
348350
"axes": [
349-
ax
350-
for ax in multiscales[0]["axes"]
351-
if ax["type"] != "channel"
351+
ax for ax in multiscales[0]["axes"] if ax["type"] != "channel"
352352
],
353353
"datasets": new_datasets,
354354
}
@@ -364,9 +364,7 @@ def ilastik_pixel_classification_segmentation(
364364
logger=logger,
365365
)
366366

367-
logger.info(
368-
f"Helper function `prepare_label_group` returned {label_group=}"
369-
)
367+
logger.info(f"Helper function `prepare_label_group` returned {label_group=}")
370368
logger.info(f"Output label path: {zarr_url}/labels/{output_label_name}/0")
371369
store = zarr.storage.FSStore(f"{zarr_url}/labels/{output_label_name}/0")
372370
label_dtype = np.uint32
@@ -391,7 +389,7 @@ def ilastik_pixel_classification_segmentation(
391389
logger.info(
392390
f"mask will have shape {data_zyx.shape} " f"and chunks {data_zyx.chunks}"
393391
)
394-
392+
395393
# Initialize other things
396394
logger.info(f"Start ilastik pixel classification task for {zarr_url}")
397395
logger.info(f"{data_zyx.shape}")
@@ -413,7 +411,7 @@ def ilastik_pixel_classification_segmentation(
413411
slice(s_x, e_x),
414412
)
415413
logger.info(f"Now processing ROI {i_ROI+1}/{num_ROIs}")
416-
414+
417415
# Prepare single-channel or dual-channel input for Ilastik
418416
if channel2:
419417
# Dual channel mode
@@ -433,12 +431,10 @@ def ilastik_pixel_classification_segmentation(
433431
)
434432
logger.info(f"dual channel img shape {img_np.shape=}")
435433
else:
436-
img_np = load_region(data_zyx,
437-
region,
438-
compute=True,
439-
return_as_3D=True
440-
)
441-
img_np = img_np[np.newaxis, ...] # input for masked_wrapper has to be (czyx)
434+
img_np = load_region(data_zyx, region, compute=True, return_as_3D=True)
435+
img_np = img_np[
436+
np.newaxis, ...
437+
] # input for masked_wrapper has to be (czyx)
442438
logger.info(f"single channel img shape {img_np.shape=}")
443439

444440
# Prepare keyword arguments for segment_ROI function
@@ -494,7 +490,8 @@ def ilastik_pixel_classification_segmentation(
494490
)
495491

496492
logger.info(
497-
f"End Ilastik pixel-classification task for {zarr_url}, " "now building pyramids."
493+
f"End Ilastik pixel-classification task for {zarr_url}, "
494+
"now building pyramids."
498495
)
499496

500497
# Starting from on-disk highest-resolution data, build and write to disk a
@@ -535,19 +532,19 @@ def ilastik_pixel_classification_segmentation(
535532

536533
def check_ilastik_model_channels(shell) -> int:
537534
"""Check number of input channels expected by Ilastik model.
538-
535+
539536
Args:
540537
shell: Initialized Ilastik shell with loaded model
541-
538+
542539
Returns:
543540
int: Number of expected input channels
544541
"""
545542
# Get dataSelection applet from workflow
546543
data_selection = shell.workflow.dataSelectionApplet
547-
544+
548545
# Get slot info containing expected channels
549546
slot_info = data_selection.topLevelOperator.DatasetRoles.value
550-
547+
551548
# Return number of expected channels
552549
return len(slot_info)
553550

tests/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pooch
66
import pytest
7-
from devtools import debug
7+
88

99
@pytest.fixture(scope="session")
1010
def testdata_path() -> Path:
@@ -52,4 +52,3 @@ def zenodo_zarr_3d(testdata_path: Path) -> str:
5252
shutil.rmtree(str(folder))
5353
shutil.copytree(Path(zarr_full_path) / file_name, folder)
5454
return Path(folder)
55-

0 commit comments

Comments
 (0)