@@ -58,7 +58,7 @@ def update_hist(self):
5858 # Seaborn version (nicer aesthetics)
5959 sns .histplot (data , bins = self .bins , ax = self .ax , kde = False )
6060
61- self .ax .set_xlabel (f"{ stat_name } GFP Intensity" )
61+ self .ax .set_xlabel (f"{ stat_name } Marker Intensity" )
6262 self .ax .set_ylabel ("Count" )
6363 self .canvas .draw_idle ()
6464
@@ -69,118 +69,134 @@ def _create_stat_widget(statistics, default_stat):
6969
7070
7171# Extend via watershed, this could work for a better alignment.
72- def _extend_sgns_complex ( gfp , sgns ):
72+ def _extend_seg_complex ( stain , seg ):
7373 # 1.) compute distance to the SGNs to create the background seed.
7474 print ("Compute edt" )
7575
7676 # Could use parallel impl
7777 distance_threshol = 7
78- distances = distance_transform (sgns == 0 )
78+ distances = distance_transform (seg == 0 )
7979
8080 # Erode seeds?
81- seeds = sgns .copy ()
81+ seeds = seg .copy ()
8282 bg_seed_id = int (seeds .max ()) + 1
8383 seeds [distances > distance_threshol ] = bg_seed_id
8484
8585 # Dilate to cover everything on the boundary?
8686 print ("Run watershed" )
87- sgns_extended = seeded_watershed (gfp , markers = seeds )
88- sgns_extended [ sgns_extended == bg_seed_id ] = 0
87+ seg_extended = seeded_watershed (stain , markers = seeds )
88+ seg_extended [ seg_extended == bg_seed_id ] = 0
8989
9090 v = napari .Viewer ()
91- v .add_image (gfp )
91+ v .add_image (stain )
9292 v .add_image (distances )
9393 v .add_labels (seeds )
94- v .add_labels (sgns_extended )
94+ v .add_labels (seg_extended )
9595 napari .run ()
9696
9797
9898# Just dilate by 3 pixels.
99- def _extend_sgns_simple ( gfp , sgns , dilation ):
99+ def _extend_seg_simple ( stain , seg , dilation ):
100100 block_shape = (128 ,) * 3
101101 halo = (dilation + 2 ,) * 3
102102
103- distances = distance_transform (sgns == 0 , block_shape = block_shape , halo = halo , n_threads = 8 )
103+ distances = distance_transform (seg == 0 , block_shape = block_shape , halo = halo , n_threads = 8 )
104104 mask = distances < dilation
105105
106- sgns_extended = np .zeros_like (sgns )
107- sgns_extended = seeded_watershed (
108- distances , sgns , sgns_extended , block_shape = block_shape , halo = halo , n_threads = 8 , mask = mask
106+ seg_extended = np .zeros_like (seg )
107+ seg_extended = seeded_watershed (
108+ distances , seg , seg_extended , block_shape = block_shape , halo = halo , n_threads = 8 , mask = mask
109109 )
110110
111- return sgns_extended
111+ return seg_extended
112112
113113
114- def _create_mask (sgns_extended , gfp ):
114+ def _create_mask (seg_extended , stain ):
115115 from skimage .transform import downscale_local_mean , resize
116116
117- gfp_averaged = downscale_local_mean (gfp , factors = (16 , 16 , 16 ))
117+ stain_averaged = downscale_local_mean (stain , factors = (16 , 16 , 16 ))
118118 # The 35th percentile seems to be a decent approximation for the background subtraction.
119- threshold = np .percentile (gfp_averaged , 35 )
120- mask = gfp_averaged > threshold
121- mask = resize (mask , sgns_extended .shape , order = 0 , anti_aliasing = False , preserve_range = True ).astype (bool )
122- mask [sgns_extended != 0 ] = 0
119+ threshold = np .percentile (stain_averaged , 35 )
120+ mask = stain_averaged > threshold
121+ mask = resize (mask , seg_extended .shape , order = 0 , anti_aliasing = False , preserve_range = True ).astype (bool )
122+ mask [seg_extended != 0 ] = 0
123123
124124 # v = napari.Viewer()
125- # v.add_image(gfp )
126- # v.add_image(gfp_averaged , scale=(16, 16, 16))
125+ # v.add_image(stain )
126+ # v.add_image(stain_averaged , scale=(16, 16, 16))
127127 # v.add_labels(mask)
128128 # # v.add_labels(mask, scale=(16, 16, 16))
129- # v.add_labels(sgns_extended )
129+ # v.add_labels(seg_extended )
130130 # napari.run()
131131
132132 return mask
133133
134134
135- def gfp_annotation (prefix , default_stat = "median" , background_norm = None ):
135+ def gfp_annotation (prefix , default_stat = "median" , background_norm = None , is_otof = False ):
136136 assert background_norm in (None , "division" , "subtraction" )
137137
138138 direc = os .path .dirname (os .path .abspath (prefix ))
139139 basename = os .path .basename (prefix )
140140 file_names = [entry .name for entry in os .scandir (direc )]
141- gfp_file = [name for name in file_names if basename in name and "GFP" in name ][0 ]
142- sgn_file = [name for name in file_names if basename in name and "SGN" in name ][0 ]
143- pv_file = [name for name in file_names if basename in name and "PV" in name ][0 ]
141+ if is_otof : # OTOF cochlea with VGlut3, Alphatag and IHC segmentation.
142+ stain1_file = [name for name in file_names if basename in name and "pha" in name ][0 ]
143+ stain2_file = [name for name in file_names if basename in name and "lut3" in name ][0 ]
144+ seg_file = [name for name in file_names if basename in name and "IHC" in name ][0 ]
144145
145- gfp = imageio .imread (os .path .join (direc , gfp_file ))
146- sgns = imageio .imread (os .path .join (direc , sgn_file ))
147- pv = imageio .imread (os .path .join (direc , pv_file ))
146+ stain1_name = "Alphatag"
147+ stain2_name = "Vglut3"
148+ seg_name = "IHC"
149+
150+ else : # ChReef cochlea with PV, GFPand SGN segmentation
151+ stain1_file = [name for name in file_names if basename in name and "GFP" in name ][0 ]
152+ stain2_file = [name for name in file_names if basename in name and "PV" in name ][0 ]
153+ seg_file = [name for name in file_names if basename in name and "SGN" in name ][0 ]
154+
155+ stain1_name = "GFP"
156+ stain2_name = "PV"
157+ seg_name = "SGN"
158+
159+ stain1 = imageio .imread (os .path .join (direc , stain1_file ))
160+ stain2 = imageio .imread (os .path .join (direc , stain2_file ))
161+ seg = imageio .imread (os .path .join (direc , seg_file ))
148162
149163 # bb = np.s_[128:-128, 128:-128, 128:-128]
150164 # gfp, sgns, pv = gfp[bb], sgns[bb], pv[bb]
151165 # print(gfp.shape)
152166
153167 # Extend the sgns so that they cover the SGN boundaries.
154- # sgns_extended = _extend_sgns (gfp, sgns)
168+ # sgns_extended = _extend_seg (gfp, sgns)
155169 # TODO we need to integrate this directly in the object measurement to efficiently do it at scale.
156- sgns_extended = _extend_sgns_simple (gfp , sgns , dilation = 4 )
170+ seg_extended = _extend_seg_simple (stain1 , seg , dilation = 4 )
171+ if is_otof :
172+ seg_extended = seg .copy ()
157173
158174 # Compute the intensity statistics.
159175 if background_norm is None :
160176 mask = None
161177 feature_set = "default"
162178 else :
163- mask = _create_mask (sgns_extended , gfp )
164- assert mask .shape == sgns_extended .shape
179+ mask = _create_mask (seg_extended , stain1 )
180+ assert mask .shape == seg_extended .shape
165181 feature_set = "default_background_norm" if background_norm == "division" else "default_background_subtract"
166182 statistics = compute_object_measures_impl (
167- gfp , sgns_extended , feature_set = feature_set , foreground_mask = mask , median_only = True
183+ stain1 , seg_extended , feature_set = feature_set , foreground_mask = mask , median_only = True
168184 )
169185
170186 # Open the napari viewer.
171187 v = napari .Viewer ()
172188
173189 # Add the base layers.
174- v .add_image (gfp , name = "GFP" )
175- v .add_image (pv , visible = False , name = "PV" )
176- v .add_labels (sgns , visible = False , name = "SGNs " )
177- v .add_labels (sgns_extended , name = "SGNs -extended" )
190+ v .add_image (stain1 , name = stain1_name )
191+ v .add_image (stain2 , visible = False , name = stain2_name )
192+ v .add_labels (seg , visible = False , name = f" { seg_name } s " )
193+ v .add_labels (seg_extended , name = f" { seg_name } s -extended" )
178194 if mask is not None :
179195 v .add_labels (mask , name = "mask-for-background" , visible = False )
180196
181197 # Add additional layers for intensity coloring and classification
182198 # data_numerical = np.zeros(gfp.shape, dtype="float32")
183- data_labels = np .zeros (gfp .shape , dtype = "uint8" )
199+ data_labels = np .zeros (stain1 .shape , dtype = "uint8" )
184200
185201 # v.add_image(data_numerical, name="gfp-intensity")
186202 v .add_labels (data_labels , name = "positive-negative" )
@@ -204,7 +220,7 @@ def gfp_annotation(prefix, default_stat="median", background_norm=None):
204220 call_button = "Pick Value"
205221 )
206222 def pick_widget (viewer : napari .Viewer , value : float = 0.0 ):
207- layer = viewer .layers ["SGNs -extended" ]
223+ layer = viewer .layers [f" { seg_name } s -extended" ]
208224 selected_id = layer .selected_label
209225
210226 stat_name = stat_widget .param_box .currentText ()
@@ -227,15 +243,15 @@ def pick_widget(viewer: napari.Viewer, value: float = 0.0):
227243 },
228244 call_button = "Apply" ,
229245 )
230- def threshold_widget (viewer : napari .Viewer , threshold : float = (max_val - min_val ) / 2 ):
246+ def threshold_widget (viewer : napari .Viewer , threshold : float = (max_val + min_val ) / 2 ):
231247 label_ids = statistics .label_id .values
232248 stat_name = stat_widget .param_box .currentText ()
233249 vals = statistics [stat_name ].values
234250 pos_ids = label_ids [vals >= threshold ]
235251 neg_ids = label_ids [vals <= threshold ]
236- data_labels = np .zeros (gfp .shape , dtype = "uint8" )
237- data_labels [np .isin (sgns_extended , pos_ids )] = 2
238- data_labels [np .isin (sgns_extended , neg_ids )] = 1
252+ data_labels = np .zeros (stain1 .shape , dtype = "uint8" )
253+ data_labels [np .isin (seg_extended , pos_ids )] = 2
254+ data_labels [np .isin (seg_extended , neg_ids )] = 1
239255 viewer .layers ["positive-negative" ].data = data_labels
240256
241257 threshold_widget .viewer .value = v
@@ -244,7 +260,7 @@ def threshold_widget(viewer: napari.Viewer, threshold: float = (max_val - min_va
244260 v .window .add_dock_widget (stat_widget , area = "right" )
245261 v .window .add_dock_widget (pick_widget , area = "right" )
246262 v .window .add_dock_widget (threshold_widget , area = "right" )
247- stat_widget .setWindowTitle ("GFP Histogram" )
263+ stat_widget .setWindowTitle (f" { stain1_name } Histogram" )
248264
249265 napari .run ()
250266
@@ -254,12 +270,20 @@ def threshold_widget(viewer: napari.Viewer, threshold: float = (max_val - min_va
254270# - M_LR_000145_L: rough alignment is ok, detailed alignment also ok.
255271# - M_LR_000151_R: rough alignment is ok, detailed alignment also ok.
256272def main ():
257- parser = argparse .ArgumentParser ()
258- parser .add_argument ("prefix" )
259- parser .add_argument ("-b" , "--background_norm" )
273+ parser = argparse .ArgumentParser (
274+ description = "Start a GUI for determining an intensity threshold for positive "
275+ "/ negative transduction in segmented cells." )
276+ parser .add_argument ("prefix" , help = "The prefix of the files to open with the annotation tool." )
277+ parser .add_argument ("-b" , "--background_norm" ,
278+ help = "How to normalize the intensity values for background intensity."
279+ "Valid options are 'division' and 'subtraction'."
280+ "If nothing is passed then the intensity values are not normalized." )
281+ parser .add_argument ("--otof" , action = "store_true" ,
282+ help = "Whether to run the annotation tool for otof samples with VGlut3, "
283+ "Alphatag and IHC segmentation." ) # noqa
260284 args = parser .parse_args ()
261285
262- gfp_annotation (args .prefix , background_norm = args .background_norm )
286+ gfp_annotation (args .prefix , background_norm = args .background_norm , is_otof = args . otof )
263287
264288
265289if __name__ == "__main__" :
0 commit comments