Skip to content

Commit ec49fc8

Browse files
committed
Evaluate LaVision-rbOtof thresholds
1 parent a3bd929 commit ec49fc8

File tree

9 files changed

+78
-30
lines changed

9 files changed

+78
-30
lines changed

flamingo_tools/segmentation/chreef_utils.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ def get_roi(coord: tuple, roi_halo: tuple, resolution: float = 0.38) -> Tuple[in
7373
# reverse dimensions for correct extraction
7474
coords.reverse()
7575
coords = np.array(coords)
76+
if not isinstance(resolution, float):
77+
assert len(resolution) == 3
78+
resolution = np.array(resolution)[::-1]
7679
coords = coords / resolution
7780
coords = np.round(coords).astype(np.int32)
7881

@@ -144,12 +147,13 @@ def find_inbetween_ids(
144147
return inbetween_ids, allweak_positives, negexc_negatives
145148

146149

147-
def get_median_intensity(file_negexc, file_allweak, center, data_seg, table, column="median"):
150+
def get_median_intensity(file_negexc, file_allweak, center, data_seg, table, column="median",
151+
resolution=0.38):
148152
arr_negexc = tifffile.imread(file_negexc)
149153
arr_allweak = tifffile.imread(file_allweak)
150154

151155
roi_halo = tuple([r // 2 for r in arr_negexc.shape])
152-
roi = get_roi(center, roi_halo)
156+
roi = get_roi(center, roi_halo, resolution=resolution)
153157

154158
roi_seg = data_seg[roi]
155159
inbetween_ids, allweak_positives, negexc_negatives = find_inbetween_ids(arr_negexc, arr_allweak, roi_seg)
@@ -172,7 +176,8 @@ def get_median_intensity(file_negexc, file_allweak, center, data_seg, table, col
172176
return np.median(list(intensities))
173177

174178

175-
def localize_median_intensities(annotation_dir, cochlea, data_seg, table_measure, column="median", pattern=None):
179+
def localize_median_intensities(annotation_dir, cochlea, data_seg, table_measure, column="median", pattern=None,
180+
resolution=0.38):
176181
"""Find median intensities in blocks and assign them to center positions of cropped block.
177182
"""
178183
annotation_dic = find_annotations(annotation_dir, cochlea, pattern=pattern)
@@ -184,7 +189,7 @@ def localize_median_intensities(annotation_dir, cochlea, data_seg, table_measure
184189
file_pos = annotation_dic[center_str]["file_pos"]
185190
file_neg = annotation_dic[center_str]["file_neg"]
186191
median_intensity = get_median_intensity(file_neg, file_pos, center_coord, data_seg,
187-
table_measure, column=column)
192+
table_measure, column=column, resolution=resolution)
188193

189194
if median_intensity is None:
190195
print(f"No threshold identified for {center_str}.")

flamingo_tools/segmentation/cochlea_mapping.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ def measure_run_length_ihcs_multi_component(
314314
print(f"Graph consists of {len(components)} connected components.")
315315
if len(component_label) != len(components):
316316
raise ValueError(f"Length of graph components {len(components)} "
317-
f"does not match number of component labels {len(component_label)}. "
318-
"Check max_edge_distance and post-processing.")
317+
f"does not match number of component labels {len(component_label)}. "
318+
"Check max_edge_distance and post-processing.")
319319

320320
# Order connected components in order of component labels
321321
# e.g. component_labels = [7, 4, 1, 11] and len_c = [600, 400, 300, 55]
@@ -410,6 +410,7 @@ def measure_run_length_ihcs_multi_component(
410410

411411
return total_distance, path, path_dict
412412

413+
413414
def measure_run_length_ihcs(
414415
centroids: np.ndarray,
415416
max_edge_distance: float = 30,
@@ -583,7 +584,7 @@ def get_centers_from_path(
583584
try:
584585
f = interp1d(cum_len, path, axis=0) # fill_value="extrapolate"
585586
centers = f(target_s)
586-
except ValueError as ve:
587+
except ValueError:
587588
print("Using extrapolation to fill values.")
588589
f = interp1d(cum_len, path, axis=0, fill_value="extrapolate")
589590
centers = f(target_s)
@@ -724,6 +725,7 @@ def tonotopic_mapping(
724725
component_mapping: Optional[List[int]] = None,
725726
cell_type: str = "ihc",
726727
animal: str = "mouse",
728+
max_edge_distance: float = 30,
727729
apex_higher: bool = True,
728730
) -> pd.DataFrame:
729731
"""Tonotopic mapping of IHCs by supplying a table with component labels.
@@ -749,6 +751,7 @@ def tonotopic_mapping(
749751
if cell_type == "ihc":
750752
total_distance, _, path_dict = measure_run_length_ihcs(
751753
centroids, component_label=component_label, apex_higher=apex_higher,
754+
max_edge_distance=max_edge_distance
752755
)
753756

754757
else:

reproducibility/object_measures/repro_object_measures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ def repro_object_measures(
8282
)
8383

8484
compute_object_measures(
85-
image_path=img_path,
86-
segmentation_path=seg_path,
85+
image_path=img_s3,
86+
segmentation_path=seg_s3,
8787
segmentation_table_path=seg_table_s3,
8888
output_table_path=output_table_path,
8989
image_key=input_key,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"cochlea": "LaVision-OTOF23R",
4+
"segmentation_channel": "IHC_LOWRES-v3",
5+
"component_list":
6+
[
7+
4,
8+
18,
9+
7
10+
],
11+
"max_edge_distance": 50,
12+
"type": "ihc"
13+
}
14+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"cochlea": "LaVision-OTOF25R",
4+
"segmentation_channel": "IHC_LOWRES-v3",
5+
"component_list":
6+
[
7+
1
8+
],
9+
"max_edge_distance": 300,
10+
"type": "ihc"
11+
}
12+
]

reproducibility/tonotopic_mapping/MLR184L.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"component_list": [
55
1
66
],
7-
"segmentation_channel": "SGN_v2",
7+
"segmentation_channel": "SGN_v2b",
88
"type": "sgn"
99
}
1010
]

reproducibility/tonotopic_mapping/MLR184R.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"component_list": [
55
1
66
],
7-
"segmentation_channel": "SGN_v2",
7+
"segmentation_channel": "SGN_v2b",
88
"type": "sgn"
99
}
1010
]

reproducibility/tonotopic_mapping/repro_tonotopic_mapping.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def repro_tonotopic_mapping(
1818
):
1919
default_cell_type = "ihc"
2020
default_apex_position = "apex_higher"
21+
default_max_edge_distance = 30
2122
default_component_list = [1]
2223

2324
remove_columns = ["tonotopic_label",
@@ -39,7 +40,8 @@ def repro_tonotopic_mapping(
3940
elif cochlea[0] in ["G", "g"]:
4041
animal = "gerbil"
4142
else:
42-
raise ValueError("Cochlea does not have expected name format 'M_[...]' or 'G_[...]'.")
43+
animal = "mouse"
44+
# raise ValueError("Cochlea does not have expected name format 'M_[...]' or 'G_[...]'.")
4345

4446
cochlea_str = "-".join(cochlea.split("_"))
4547
seg_str = "-".join(seg_channel.split("_"))
@@ -58,6 +60,7 @@ def repro_tonotopic_mapping(
5860
component_list = dic["component_list"] if "component_list" in dic else default_component_list
5961
component_mapping = dic["component_mapping"] if "component_mapping" in dic else component_list
6062
apex_position = dic["apex_position"] if "apex_position" in dic else default_apex_position
63+
max_edge_distance = dic["max_edge_distance"] if "max_edge_distance" in dic else default_max_edge_distance
6164

6265
apex_higher = (apex_position == "apex_higher")
6366

@@ -68,7 +71,7 @@ def repro_tonotopic_mapping(
6871
if not os.path.isfile(output_table_path) or force_overwrite:
6972
table = tonotopic_mapping(table, component_label=component_list, animal=animal,
7073
cell_type=cell_type, component_mapping=component_mapping,
71-
apex_higher=apex_higher)
74+
apex_higher=apex_higher, max_edge_distance=max_edge_distance)
7275

7376
table.to_csv(output_table_path, sep="\t", index=False)
7477

scripts/measurements/evaluate_marker_annotations.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,33 @@
2929
]
3030

3131

32-
def get_length_fraction_from_center(table, center_str):
32+
def get_length_fraction_from_center(table, center_str, halo_size=20):
3333
"""Get 'length_fraction' parameter for center coordinate by averaging nearby segmentation instances.
3434
"""
3535
center_coord = tuple([int(c) for c in center_str.split("-")])
3636
(cx, cy, cz) = center_coord
37-
offset = 20
3837
subset = table[
39-
(cx - offset < table["anchor_x"]) &
40-
(table["anchor_x"] < cx + offset) &
41-
(cy - offset < table["anchor_y"]) &
42-
(table["anchor_y"] < cy + offset) &
43-
(cz - offset < table["anchor_z"]) &
44-
(table["anchor_z"] < cz + offset)
38+
(cx - halo_size < table["anchor_x"]) &
39+
(table["anchor_x"] < cx + halo_size) &
40+
(cy - halo_size < table["anchor_y"]) &
41+
(table["anchor_y"] < cy + halo_size) &
42+
(cz - halo_size < table["anchor_z"]) &
43+
(table["anchor_z"] < cz + halo_size)
4544
]
4645
length_fraction = list(subset["length_fraction"])
4746
length_fraction = float(sum(length_fraction) / len(length_fraction))
4847
return length_fraction
4948

5049

51-
def apply_nearest_threshold(intensity_dic, table_seg, table_measurement):
50+
def apply_nearest_threshold(intensity_dic, table_seg, table_measurement, halo_size=20):
5251
"""Apply threshold to nearest segmentation instances.
5352
Crop centers are transformed into the "length fraction" parameter of the segmentation table.
5453
This avoids issues with the spiral shape of the cochlea and maps the assignment onto the Rosenthal"s canal.
5554
"""
5655
# assign crop centers to length fraction of Rosenthal"s canal
5756
lf_intensity = {}
5857
for key in intensity_dic.keys():
59-
length_fraction = get_length_fraction_from_center(table_seg, key)
58+
length_fraction = get_length_fraction_from_center(table_seg, key, halo_size=halo_size)
6059
intensity_dic[key]["length_fraction"] = length_fraction
6160
lf_intensity[length_fraction] = {"threshold": intensity_dic[key]["median_intensity"]}
6261

@@ -94,13 +93,14 @@ def apply_nearest_threshold(intensity_dic, table_seg, table_measurement):
9493
return table_seg
9594

9695

97-
def find_thresholds(cochlea_annotations, cochlea, data_seg, table_measurement):
96+
def find_thresholds(cochlea_annotations, cochlea, data_seg, table_measurement, resolution=0.38):
9897
# Find the median intensities by averaging the individual annotations for specific crops
9998
annotation_dics = {}
10099
annotated_centers = []
101100
for annotation_dir in cochlea_annotations:
102101
print(f"Localizing threshold with median intensities for {os.path.basename(annotation_dir)}.")
103-
annotation_dic = localize_median_intensities(annotation_dir, cochlea, data_seg, table_measurement)
102+
annotation_dic = localize_median_intensities(annotation_dir, cochlea, data_seg, table_measurement,
103+
resolution=resolution)
104104
annotated_centers.extend(annotation_dic["center_strings"])
105105
annotation_dics[annotation_dir] = annotation_dic
106106

@@ -168,6 +168,13 @@ def evaluate_marker_annotation(
168168
"""
169169
input_key = "s0"
170170

171+
if marker_name == "rbOtof":
172+
halo_size = 150
173+
resolution = [1.887779, 1.887779, 3.0]
174+
else:
175+
halo_size = 20
176+
resolution = 0.38
177+
171178
if annotation_dirs is None:
172179
if "MARKER_DIR" in globals():
173180
marker_dir = MARKER_DIR
@@ -199,16 +206,17 @@ def evaluate_marker_annotation(
199206
with fs.open(table_path_s3, "r") as f:
200207
table_measurement = pd.read_csv(f, sep="\t")
201208

202-
# Find the threholds from the annotated blocks and save it if specified.
203-
intensity_dic = find_thresholds(cochlea_annotations, cochlea, data_seg, table_measurement)
209+
# Find the thresholds from the annotated blocks and save it if specified.
210+
intensity_dic = find_thresholds(cochlea_annotations, cochlea, data_seg, table_measurement,
211+
resolution=resolution)
204212
if threshold_save_dir is not None:
205213
os.makedirs(threshold_save_dir, exist_ok=True)
206214
threshold_out_path = os.path.join(threshold_save_dir, f"{cochlea_str}_{marker_name}_{seg_string}.json")
207215
with open(threshold_out_path, "w") as f:
208216
json.dump(intensity_dic, f, sort_keys=True, indent=4)
209217

210218
# Apply the threshold to all SGNs.
211-
table_seg = apply_nearest_threshold(intensity_dic, table_seg, table_measurement)
219+
table_seg = apply_nearest_threshold(intensity_dic, table_seg, table_measurement, halo_size=halo_size)
212220

213221
# Save the table with positives / negatives for all SGNs.
214222
os.makedirs(output_dir, exist_ok=True)
@@ -224,12 +232,15 @@ def main():
224232
parser.add_argument("-o", "--output", type=str, required=True, help="Output directory.")
225233
parser.add_argument("-a", "--annotation_dirs", type=str, nargs="+", default=None,
226234
help="Directories containing marker annotations.")
227-
parser.add_argument("--threshold_save_dir", "-t")
235+
parser.add_argument("-t", "--threshold_save_dir")
236+
parser.add_argument("-s", "--seg_name", type=str, default="SGN_v2")
237+
parser.add_argument("-m", "--marker_name", type=str, default="GFP")
228238
parser.add_argument("-f", "--force", action="store_true")
229239

230240
args = parser.parse_args()
231241
evaluate_marker_annotation(
232-
args.cochlea, args.output, args.annotation_dirs, threshold_save_dir=args.threshold_save_dir, force=args.force
242+
args.cochlea, args.output, args.annotation_dirs, threshold_save_dir=args.threshold_save_dir,
243+
seg_name=args.seg_name, marker_name=args.marker_name, force=args.force
233244
)
234245

235246

0 commit comments

Comments
 (0)