15
15
"""
16
16
import logging
17
17
from typing import Any
18
- from typing import Dict
19
18
from typing import List
20
- from typing import Sequence
19
+ from typing import Optional
21
20
22
21
import zarr
22
+ from pydantic import BaseModel
23
+
24
+
25
+ class ChannelWindow (BaseModel ):
26
+ min : str
27
+ max : str
28
+ start : Optional [str ]
29
+ end : Optional [str ]
30
+
31
+
32
+ class Channel (BaseModel ):
33
+ wavelength_id : str
34
+ label : Optional [str ]
35
+ index : Optional [int ]
36
+ active : bool = True
37
+ coefficient : int = 1
38
+ colormap : Optional [str ]
39
+ family : str = "linear"
40
+ inverted : bool = False
41
+ window : Optional [ChannelWindow ]
23
42
24
43
25
44
class ChannelNotFoundError (ValueError ):
@@ -31,19 +50,11 @@ class ChannelNotFoundError(ValueError):
31
50
pass
32
51
33
52
34
- def validate_allowed_channel_input (allowed_channels : Sequence [ Dict [ str , Any ] ]):
53
+ def validate_allowed_channel_input (allowed_channels : List [ Channel ]):
35
54
"""
36
- Check that (1) each channel has a wavelength_id key, and (2) the
37
- wavelength_id values are unique.
55
+ Check that the `wavelength_id` values are unique across channels
38
56
"""
39
- try :
40
- wavelength_ids = [c ["wavelength_id" ] for c in allowed_channels ]
41
- except KeyError as e :
42
- raise KeyError (
43
- "Missing wavelength_id key in some channel.\n "
44
- f"{ allowed_channels = } \n "
45
- f"Original error: { str (e )} "
46
- )
57
+ wavelength_ids = [c .wavelength_id for c in allowed_channels ]
47
58
if len (set (wavelength_ids )) < len (wavelength_ids ):
48
59
raise ValueError (
49
60
f"Non-unique wavelength_id's in { wavelength_ids } \n "
@@ -73,16 +84,16 @@ def check_well_channel_labels(*, well_zarr_path: str) -> None:
73
84
74
85
# For each pair of channel-labels lists, verify they do not overlap
75
86
for ind_1 , channels_1 in enumerate (list_of_channel_lists ):
76
- labels_1 = set ([c [ " label" ] for c in channels_1 ])
87
+ labels_1 = set ([c . label for c in channels_1 ])
77
88
for ind_2 in range (ind_1 ):
78
89
channels_2 = list_of_channel_lists [ind_2 ]
79
- labels_2 = set ([c [ " label" ] for c in channels_2 ])
90
+ labels_2 = set ([c . label for c in channels_2 ])
80
91
intersection = labels_1 & labels_2
81
92
if intersection :
82
93
hint = (
83
- "Are you parsing fields of view into separate OME-Zarr"
84
- " images? This could lead to non-unique channel labels"
85
- ", and then could be the reason of the error"
94
+ "Are you parsing fields of view into separate OME-Zarr "
95
+ "images? This could lead to non-unique channel labels, "
96
+ "and then could be the reason of the error"
86
97
)
87
98
raise ValueError (
88
99
"Non-unique channel labels\n "
@@ -92,7 +103,7 @@ def check_well_channel_labels(*, well_zarr_path: str) -> None:
92
103
93
104
def get_channel_from_image_zarr (
94
105
* , image_zarr_path : str , label : str = None , wavelength_id : str = None
95
- ) -> Dict [ str , Any ] :
106
+ ) -> Channel :
96
107
"""
97
108
Extract a channel from OME-NGFF zarr attributes
98
109
@@ -112,20 +123,23 @@ def get_channel_from_image_zarr(
112
123
return channel
113
124
114
125
115
- def get_omero_channel_list (* , image_zarr_path : str ) -> List [Dict [ str , Any ] ]:
126
+ def get_omero_channel_list (* , image_zarr_path : str ) -> List [Channel ]:
116
127
"""
117
128
Extract the list of channels from OME-NGFF zarr attributes
118
129
119
130
:param image_zarr_path: Path to an OME-NGFF image zarr group
120
131
:returns: A list of channel dictionaries
121
132
"""
122
133
group = zarr .open_group (image_zarr_path , mode = "r+" )
123
- return group .attrs ["omero" ]["channels" ]
134
+ channels_dicts = group .attrs ["omero" ]["channels" ]
135
+ # FIXME what is the type of channels_dicts??
136
+ channels = [Channel (** c ) for c in channels_dicts ]
137
+ return channels
124
138
125
139
126
140
def get_channel_from_list (
127
- * , channels : Sequence [ Dict ], label : str = None , wavelength_id : str = None
128
- ) -> Dict [ str , Any ] :
141
+ * , channels : List [ Channel ], label : str = None , wavelength_id : str = None
142
+ ) -> Channel :
129
143
"""
130
144
Find matching channel in a list
131
145
@@ -147,16 +161,14 @@ def get_channel_from_list(
147
161
matching_channels = [
148
162
c
149
163
for c in channels
150
- if (
151
- c ["label" ] == label and c ["wavelength_id" ] == wavelength_id
152
- )
164
+ if (c .label == label and c .wavelength_id == wavelength_id )
153
165
]
154
166
else :
155
- matching_channels = [c for c in channels if c [ " label" ] == label ]
167
+ matching_channels = [c for c in channels if c . label == label ]
156
168
else :
157
169
if wavelength_id :
158
170
matching_channels = [
159
- c for c in channels if c [ " wavelength_id" ] == wavelength_id
171
+ c for c in channels if c . wavelength_id == wavelength_id
160
172
]
161
173
else :
162
174
raise ValueError (
@@ -178,16 +190,16 @@ def get_channel_from_list(
178
190
raise ValueError (f"Inconsistent set of channels: { channels } " )
179
191
180
192
channel = matching_channels [0 ]
181
- channel [ " index" ] = channels .index (channel )
193
+ channel . index = channels .index (channel )
182
194
return channel
183
195
184
196
185
197
def define_omero_channels (
186
198
* ,
187
- channels : Sequence [ Dict [ str , Any ] ],
199
+ channels : List [ Channel ],
188
200
bit_depth : int ,
189
201
label_prefix : str = None ,
190
- ) -> List [Dict [str , Any ]]:
202
+ ) -> List [dict [str , Any ]]:
191
203
"""
192
204
Update a channel list to use it in the OMERO/channels metadata
193
205
@@ -211,11 +223,11 @@ def define_omero_channels(
211
223
default_colormaps = ["00FFFF" , "FF00FF" , "FFFF00" ]
212
224
213
225
for channel in channels :
214
- wavelength_id = channel [ " wavelength_id" ]
226
+ wavelength_id = channel . wavelength_id
215
227
216
228
# Always set a label
217
229
try :
218
- label = channel [ " label" ]
230
+ label = channel . label
219
231
except KeyError :
220
232
default_label = wavelength_id
221
233
if label_prefix :
@@ -227,7 +239,7 @@ def define_omero_channels(
227
239
228
240
# Set colormap attribute. If not specificed, use the default ones (for
229
241
# the first three channels) or gray
230
- colormap = channel .get ( " colormap" , None )
242
+ colormap = channel .colormap
231
243
if colormap is None :
232
244
try :
233
245
colormap = default_colormaps .pop ()
@@ -239,7 +251,7 @@ def define_omero_channels(
239
251
"min" : 0 ,
240
252
"max" : 2 ** bit_depth - 1 ,
241
253
}
242
- if "start" in channel .keys () and "end" in channel .keys ():
254
+ if "start" in channel .dict (). keys () and "end" in channel . dict () .keys ():
243
255
window ["start" ] = channel ["start" ]
244
256
window ["end" ] = channel ["end" ]
245
257
0 commit comments