11import argparse
2+ import json
23import os
34from typing import List , Optional
45
910from flamingo_tools .segmentation .chreef_utils import localize_median_intensities , find_annotations
1011
1112MARKER_DIR = "/mnt/vast-nhr/projects/nim00007/data/moser/cochlea-lightsheet/ChReef_PV-GFP/2025-07_PV_GFP_SGN"
13+ # The cochlea for the CHReef analysis.
14+ COCHLEAE = [
15+ "M_LR_000143_L" ,
16+ "M_LR_000144_L" ,
17+ "M_LR_000145_L" ,
18+ "M_LR_000153_L" ,
19+ "M_LR_000155_L" ,
20+ "M_LR_000189_L" ,
21+ "M_LR_000143_R" ,
22+ "M_LR_000144_R" ,
23+ "M_LR_000145_R" ,
24+ "M_LR_000153_R" ,
25+ "M_LR_000155_R" ,
26+ "M_LR_000189_L" ,
27+ "M_LR_000189_R" ,
28+ ]
1229
1330
1431def get_length_fraction_from_center (table , center_str ):
@@ -76,13 +93,40 @@ def apply_nearest_threshold(intensity_dic, table_seg, table_measurement):
7693 return table_seg
7794
7895
96+ def find_thresholds (cochlea_annotations , cochlea , data_seg , table_measurement ):
97+ # Find the median intensities by averaging th individual annotations for specific crops
98+ annotation_dics = {}
99+ annotated_centers = []
100+ for annotation_dir in cochlea_annotations :
101+ annotation_dic = localize_median_intensities (annotation_dir , cochlea , data_seg , table_measurement )
102+ annotated_centers .extend (annotation_dic ["center_strings" ])
103+ annotation_dics [annotation_dir ] = annotation_dic
104+
105+ annotated_centers = list (set (annotated_centers ))
106+ intensity_dic = {}
107+ # loop over all annotated blocks
108+ for annotated_center in annotated_centers :
109+ intensities = []
110+ # loop over annotated block from single user
111+ for annotator_key in annotation_dics .keys ():
112+ if annotated_center not in annotation_dics [annotator_key ]["center_strings" ]:
113+ continue
114+ else :
115+ intensities .append (annotation_dics [annotator_key ][annotated_center ]["median_intensity" ])
116+ intensity_dic [annotated_center ] = {"median_intensity" : float (sum (intensities ) / len (intensities ))}
117+
118+ return intensity_dic
119+
120+
79121def evaluate_marker_annotation (
80122 cochleae ,
81123 output_dir : str ,
82124 annotation_dirs : Optional [List [str ]] = None ,
83125 seg_name : str = "SGN_v2" ,
84126 marker_name : str = "GFP" ,
85- ):
127+ threshold_save_dir : Optional [str ] = None ,
128+ force : bool = False ,
129+ ) -> None :
86130 """Evaluate marker annotations of a single or multiple annotators.
87131 Segmentation instances are assigned a positive (1) or negative label (2)
88132 in form of the "marker_label" component of the output segmentation table.
@@ -95,6 +139,8 @@ def evaluate_marker_annotation(
95139 annotation_dirs: List of directories containing marker annotations by annotator(s).
96140 seg_name: Identifier for segmentation.
97141 marker_name: Identifier for marker stain.
142+ threshold_save_dir: Optional directory for saving the thresholds.
143+ force: Whether to overwrite already existing results.
98144 """
99145 input_key = "s0"
100146
@@ -104,11 +150,17 @@ def evaluate_marker_annotation(
104150 annotation_dirs = [entry .path for entry in os .scandir (marker_dir )
105151 if os .path .isdir (entry ) and "Results" in entry .name ]
106152
153+ seg_string = "-" .join (seg_name .split ("_" ))
107154 for cochlea in cochleae :
155+ cochlea_str = "-" .join (cochlea .split ("_" ))
156+ out_path = os .path .join (output_dir , f"{ cochlea_str } _{ marker_name } _{ seg_string } .tsv" )
157+ if os .path .exists (out_path ) and not force :
158+ continue
159+
108160 cochlea_annotations = [a for a in annotation_dirs if len (find_annotations (a , cochlea )["center_strings" ]) != 0 ]
109161 print (f"Evaluating data for cochlea { cochlea } in { cochlea_annotations } ." )
110162
111- # get segmentation data
163+ # Get the segmentation data and table.
112164 input_path = f"{ cochlea } /images/ome-zarr/{ seg_name } .ome.zarr"
113165 input_path , fs = get_s3_path (input_path )
114166 data_seg = read_image_data (input_path , input_key )
@@ -118,37 +170,24 @@ def evaluate_marker_annotation(
118170 with fs .open (table_path_s3 , "r" ) as f :
119171 table_seg = pd .read_csv (f , sep = "\t " )
120172
121- seg_string = "-" .join (seg_name .split ("_" ))
122173 table_measurement_path = f"{ cochlea } /tables/{ seg_name } /{ marker_name } _{ seg_string } _object-measures.tsv"
123174 table_path_s3 , fs = get_s3_path (table_measurement_path )
124175 with fs .open (table_path_s3 , "r" ) as f :
125176 table_measurement = pd .read_csv (f , sep = "\t " )
126177
127- # find median intensities by averaging all individual annotations for specific crops
128- annotation_dics = {}
129- annotated_centers = []
130- for annotation_dir in cochlea_annotations :
131-
132- annotation_dic = localize_median_intensities (annotation_dir , cochlea , data_seg , table_measurement )
133- annotated_centers .extend (annotation_dic ["center_strings" ])
134- annotation_dics [annotation_dir ] = annotation_dic
135-
136- annotated_centers = list (set (annotated_centers ))
137- intensity_dic = {}
138- # loop over all annotated blocks
139- for annotated_center in annotated_centers :
140- intensities = []
141- # loop over annotated block from single user
142- for annotator_key in annotation_dics .keys ():
143- if annotated_center not in annotation_dics [annotator_key ]["center_strings" ]:
144- continue
145- else :
146- intensities .append (annotation_dics [annotator_key ][annotated_center ]["median_intensity" ])
147- intensity_dic [annotated_center ] = {"median_intensity" : float (sum (intensities ) / len (intensities ))}
178+ # Find the threholds from the annotated blocks and save it if specified.
179+ intensity_dic = find_thresholds (cochlea_annotations , cochlea , data_seg , table_measurement )
180+ if threshold_save_dir is not None :
181+ os .makedirs (threshold_save_dir , exist_ok = True )
182+ threshold_out_path = os .path .join (threshold_save_dir , f"{ cochlea_str } _{ marker_name } _{ seg_string } .json" )
183+ with open (threshold_out_path , "w" ) as f :
184+ json .dump (intensity_dic , f , sort_keys = True , indent = 4 )
148185
186+ # Apply the threshold to all SGNs.
149187 table_seg = apply_nearest_threshold (intensity_dic , table_seg , table_measurement )
150- cochlea_str = "-" .join (cochlea .split ("_" ))
151- out_path = os .path .join (output_dir , f"{ cochlea_str } _{ marker_name } _{ seg_string } .tsv" )
188+
189+ # Save the table with positives / negatives for all SGNs.
190+ os .makedirs (output_dir , exist_ok = True )
152191 table_seg .to_csv (out_path , sep = "\t " , index = False )
153192
154193
@@ -157,18 +196,18 @@ def main():
157196 description = "Assign each segmentation instance a marker based on annotation thresholds."
158197 )
159198
160- parser .add_argument ("-c" , "--cochlea" , type = str , nargs = "+" , required = True ,
161- help = "Cochlea(e) to process." )
199+ parser .add_argument ("-c" , "--cochlea" , type = str , nargs = "+" , default = COCHLEAE , help = "Cochlea(e) to process." )
162200 parser .add_argument ("-o" , "--output" , type = str , required = True , help = "Output directory." )
163-
164201 parser .add_argument ("-a" , "--annotation_dirs" , type = str , nargs = "+" , default = None ,
165202 help = "Directories containing marker annotations." )
203+ parser .add_argument ("--threshold_save_dir" , "-t" )
204+ parser .add_argument ("-f" , "--force" , action = "store_true" )
166205
167206 args = parser .parse_args ()
168-
169- evaluate_marker_annotation (args .cochlea , args .output , args .annotation_dirs ,)
207+ evaluate_marker_annotation (
208+ args .cochlea , args .output , args .annotation_dirs , threshold_save_dir = args .threshold_save_dir , force = args .force
209+ )
170210
171211
172212if __name__ == "__main__" :
173-
174213 main ()
0 commit comments