Skip to content

Commit 2b93c28

Browse files
authored
Merge pull request #5 from rhornb/pred_error
channel input final fixes
2 parents 600c490 + 7147e16 commit 2b93c28

File tree

4 files changed

+138
-85
lines changed

4 files changed

+138
-85
lines changed

src/ilastik_tasks/__FRACTAL_MANIFEST__.json

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,90 +15,34 @@
1515
},
1616
"args_schema_parallel": {
1717
"$defs": {
18-
"CellposeChannel1InputModel": {
19-
"description": "Channel input for cellpose with normalization options.",
18+
"IlastikChannel1InputModel": {
19+
"description": "Channel input for ilastik.",
2020
"properties": {
2121
"wavelength_id": {
2222
"title": "Wavelength Id",
23-
"type": "string",
24-
"description": "Unique ID for the channel wavelength, e.g. `A01_C01`. Can only be specified if label is not set."
23+
"type": "string"
2524
},
2625
"label": {
2726
"title": "Label",
28-
"type": "string",
29-
"description": "Name of the channel. Can only be specified if wavelength_id is not set."
30-
},
31-
"normalize": {
32-
"$ref": "#/$defs/CellposeCustomNormalizer",
33-
"title": "Normalize",
34-
"description": "Validator to handle different normalization scenarios for Cellpose models"
27+
"type": "string"
3528
}
3629
},
37-
"title": "CellposeChannel1InputModel",
30+
"title": "IlastikChannel1InputModel",
3831
"type": "object"
3932
},
40-
"CellposeChannel2InputModel": {
41-
"description": "Channel input for secondary cellpose channel with normalization options.",
33+
"IlastikChannel2InputModel": {
34+
"description": "Channel input for secondary ilastik channel.",
4235
"properties": {
4336
"wavelength_id": {
4437
"title": "Wavelength Id",
45-
"type": "string",
46-
"description": "Unique ID for the channel wavelength, e.g. `A01_C01`. Can only be specified if label is not set."
38+
"type": "string"
4739
},
4840
"label": {
4941
"title": "Label",
50-
"type": "string",
51-
"description": "Name of the channel. Can only be specified if wavelength_id is not set."
52-
},
53-
"normalize": {
54-
"$ref": "#/$defs/CellposeCustomNormalizer",
55-
"title": "Normalize",
56-
"description": "Validator to handle different normalization scenarios for Cellpose models"
57-
}
58-
},
59-
"title": "CellposeChannel2InputModel",
60-
"type": "object"
61-
},
62-
"CellposeCustomNormalizer": {
63-
"description": "Validator to handle different normalization scenarios for Cellpose models",
64-
"properties": {
65-
"type": {
66-
"default": "default",
67-
"enum": [
68-
"default",
69-
"custom",
70-
"no_normalization"
71-
],
72-
"title": "Type",
73-
"type": "string",
74-
"description": "One of `default` (Cellpose default normalization), `custom` (using the other custom parameters) or `no_normalization`."
75-
},
76-
"lower_percentile": {
77-
"maximum": 100.0,
78-
"minimum": 0.0,
79-
"title": "Lower Percentile",
80-
"type": "number",
81-
"description": "Specify a custom lower-bound percentile for rescaling as a float value between 0 and 100. Set to 1 to run the same as default). You can only specify percentiles or bounds, not both."
82-
},
83-
"upper_percentile": {
84-
"maximum": 100.0,
85-
"minimum": 0.0,
86-
"title": "Upper Percentile",
87-
"type": "number",
88-
"description": "Specify a custom upper-bound percentile for rescaling as a float value between 0 and 100. Set to 99 to run the same as default, set to e.g. 99.99 if the default rescaling was too harsh. You can only specify percentiles or bounds, not both."
89-
},
90-
"lower_bound": {
91-
"title": "Lower Bound",
92-
"type": "integer",
93-
"description": "Explicit lower bound value to rescale the image at. Needs to be an integer, e.g. 100. You can only specify percentiles or bounds, not both."
94-
},
95-
"upper_bound": {
96-
"title": "Upper Bound",
97-
"type": "integer",
98-
"description": "Explicit upper bound value to rescale the image at. Needs to be an integer, e.g. 2000. You can only specify percentiles or bounds, not both."
42+
"type": "string"
9943
}
10044
},
101-
"title": "CellposeCustomNormalizer",
45+
"title": "IlastikChannel2InputModel",
10246
"type": "object"
10347
}
10448
},
@@ -115,12 +59,12 @@
11559
"description": "Pyramid level of the image to be segmented. Choose `0` to process at full resolution."
11660
},
11761
"channel": {
118-
"$ref": "#/$defs/CellposeChannel1InputModel",
62+
"$ref": "#/$defs/IlastikChannel1InputModel",
11963
"title": "Channel",
12064
"description": "Primary channel for pixel classification; requires either `wavelength_id` (e.g. `A01_C01`) or `label` (e.g. `DAPI`)."
12165
},
12266
"channel2": {
123-
"$ref": "#/$defs/CellposeChannel2InputModel",
67+
"$ref": "#/$defs/IlastikChannel2InputModel",
12468
"title": "Channel2",
12569
"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."
12670
},

src/ilastik_tasks/ilastik_pixel_classification_segmentation.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@
3131
import numpy as np
3232
import vigra
3333
import zarr
34-
from fractal_tasks_core.tasks.cellpose_utils import (
35-
CellposeChannel1InputModel,
36-
)
37-
from fractal_tasks_core.tasks.cellpose_utils import (
38-
CellposeChannel2InputModel,
34+
from ilastik_tasks.ilastik_utils import (
35+
IlastikChannel1InputModel,
36+
IlastikChannel2InputModel,
3937
)
4038
from fractal_tasks_core.labels import prepare_label_group
4139
from fractal_tasks_core.masked_loading import masked_loading_wrapper
@@ -158,9 +156,9 @@ def ilastik_pixel_classification_segmentation(
158156
zarr_url: str,
159157
# Task-specific arguments
160158
level: int,
161-
channel: CellposeChannel1InputModel,
162-
channel2: CellposeChannel2InputModel = Field(
163-
default_factory=CellposeChannel2InputModel
159+
channel: IlastikChannel1InputModel,
160+
channel2: IlastikChannel2InputModel = Field(
161+
default_factory=IlastikChannel2InputModel
164162
),
165163
input_ROI_table: str = "FOV_ROI_table",
166164
output_ROI_table: Optional[str] = None,

src/ilastik_tasks/ilastik_utils.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright 2023 (C) Friedrich Miescher Institute for Biomedical Research and
2+
# University of Zurich
3+
#
4+
# Original authors:
5+
# Joel Lüthi <[email protected]>
6+
#
7+
# This file is part of Fractal and was originally developed by eXact lab S.r.l.
8+
# <exact-lab.it> under contract with Liberali Lab from the Friedrich Miescher
9+
# Institute for Biomedical Research and Pelkmans Lab from the University of
10+
# Zurich.
11+
"""
12+
Helper functions for ilastik tasks.
13+
Modified from
14+
https://github.com/fractal-analytics-platform/fractal-tasks-core/blob/main/fractal_tasks_core/tasks/cellpose_utils.py
15+
"""
16+
import logging
17+
from typing import Optional
18+
19+
from pydantic import BaseModel
20+
from pydantic import model_validator
21+
from typing_extensions import Self
22+
23+
from fractal_tasks_core.channels import ChannelInputModel
24+
from fractal_tasks_core.channels import ChannelNotFoundError
25+
from fractal_tasks_core.channels import get_channel_from_image_zarr
26+
from fractal_tasks_core.channels import OmeroChannel
27+
28+
29+
logger = logging.getLogger(__name__)
30+
31+
32+
class IlastikChannel1InputModel(ChannelInputModel):
33+
"""
34+
Channel input for ilastik.
35+
36+
Attributes:
37+
wavelength_id: Unique ID for the channel wavelength, e.g. `A01_C01`.
38+
Can only be specified if label is not set.
39+
label: Name of the channel. Can only be specified if wavelength_id is
40+
not set.
41+
normalize: Validator to handle different normalization scenarios for
42+
Cellpose models
43+
"""
44+
45+
def get_omero_channel(self, zarr_url) -> OmeroChannel:
46+
try:
47+
return get_channel_from_image_zarr(
48+
image_zarr_path=zarr_url,
49+
wavelength_id=self.wavelength_id,
50+
label=self.label,
51+
)
52+
except ChannelNotFoundError as e:
53+
logger.warning(
54+
f"Channel with wavelength_id: {self.wavelength_id} "
55+
f"and label: {self.label} not found, exit from the task.\n"
56+
f"Original error: {str(e)}"
57+
)
58+
return None
59+
60+
61+
class IlastikChannel2InputModel(BaseModel):
62+
"""
63+
Channel input for secondary ilastik channel.
64+
65+
The secondary channel is Optional, thus both wavelength_id and label are
66+
optional to be set. The `is_set` function shows whether either value was
67+
set.
68+
69+
Attributes:
70+
wavelength_id: Unique ID for the channel wavelength, e.g. `A01_C01`.
71+
Can only be specified if label is not set.
72+
label: Name of the channel. Can only be specified if wavelength_id is
73+
not set.
74+
normalize: Validator to handle different normalization scenarios for
75+
Cellpose models
76+
"""
77+
78+
wavelength_id: Optional[str] = None
79+
label: Optional[str] = None
80+
81+
@model_validator(mode="after")
82+
def mutually_exclusive_channel_attributes(self: Self) -> Self:
83+
"""
84+
Check that only 1 of `label` or `wavelength_id` is set.
85+
"""
86+
wavelength_id = self.wavelength_id
87+
label = self.label
88+
if (wavelength_id is not None) and (label is not None):
89+
raise ValueError(
90+
"`wavelength_id` and `label` cannot be both set "
91+
f"(given {wavelength_id=} and {label=})."
92+
)
93+
return self
94+
95+
def is_set(self):
96+
if self.wavelength_id or self.label:
97+
return True
98+
return False
99+
100+
def get_omero_channel(self, zarr_url) -> OmeroChannel:
101+
try:
102+
return get_channel_from_image_zarr(
103+
image_zarr_path=zarr_url,
104+
wavelength_id=self.wavelength_id,
105+
label=self.label,
106+
)
107+
except ChannelNotFoundError as e:
108+
logger.warning(
109+
f"Second channel with wavelength_id: {self.wavelength_id} "
110+
f"and label: {self.label} not found, exit from the task.\n"
111+
f"Original error: {str(e)}"
112+
)
113+
return None

tests/test_ilastik_pixel_classification_segmentation.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33

44
import pytest
55
from devtools import debug
6-
from fractal_tasks_core.tasks.cellpose_utils import (
7-
CellposeChannel1InputModel,
6+
from ilastik_tasks.ilastik_utils import (
7+
IlastikChannel1InputModel,
8+
IlastikChannel2InputModel,
89
)
9-
from fractal_tasks_core.tasks.cellpose_utils import (
10-
CellposeChannel2InputModel,
11-
)
12-
from src.ilastik_tasks.ilastik_pixel_classification_segmentation import (
10+
from ilastik_tasks.ilastik_pixel_classification_segmentation import (
1311
ilastik_pixel_classification_segmentation,
1412
)
1513

@@ -40,8 +38,8 @@ def test_ilastik_pixel_classification_segmentation_task_3D(test_data_dir_3d):
4038
ilastik_pixel_classification_segmentation(
4139
zarr_url=zarr_url,
4240
level=4,
43-
channel=CellposeChannel1InputModel(label="DAPI_2"),
44-
channel2=CellposeChannel2InputModel(label="ECadherin_2"),
41+
channel=IlastikChannel1InputModel(label="DAPI_2"),
42+
channel2=IlastikChannel2InputModel(label="ECadherin_2"),
4543
ilastik_model=str(ilastik_model),
4644
output_label_name="test_label",
4745
)
@@ -52,7 +50,7 @@ def test_ilastik_pixel_classification_segmentation_task_3D(test_data_dir_3d):
5250
ilastik_pixel_classification_segmentation(
5351
zarr_url=zarr_url,
5452
level=0,
55-
channel=CellposeChannel1InputModel(label="DAPI_2"),
53+
channel=IlastikChannel1InputModel(label="DAPI_2"),
5654
channel2=None,
5755
ilastik_model=str(ilastik_model),
5856
output_label_name="test_label_single_channel",

0 commit comments

Comments
 (0)