Skip to content

Commit b1536be

Browse files
committed
Merge branch 'master' into export_filtered_cochlea
2 parents 65ccdd7 + e615d60 commit b1536be

File tree

10 files changed

+238
-143
lines changed

10 files changed

+238
-143
lines changed

flamingo_tools/segmentation/cochlea_mapping.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import networkx as nx
55
import numpy as np
66
import pandas as pd
7-
from networkx.algorithms.approximation import steiner_tree
87
from scipy.ndimage import distance_transform_edt, binary_dilation, binary_closing
98
from scipy.interpolate import interp1d
109

@@ -146,28 +145,48 @@ def measure_run_length_sgns(centroids: np.ndarray, scale_factor=10):
146145
return total_distance, path, path_dict
147146

148147

149-
def measure_run_length_ihcs(centroids):
148+
def measure_run_length_ihcs(centroids, max_edge_distance=50):
150149
"""Measure the run lengths of the IHC segmentation
151-
by finding the shortest path between the most distant nodes in a Steiner Tree.
150+
by determining the shortest path between the most distant nodes of a graph.
151+
The graph is created based on a maximal edge distance between nodes.
152+
However, this value may not correspond to the max_edge_distance parameter used to process the IHC segmentation,
153+
which may consist of more than one component.
154+
That's why a large max_edge_distance is used to connect different components and identify
155+
the two most distant nodes and the shortest path between them.
156+
The path is then edited using a moving average filter.
152157
153158
Args:
154159
centroids: Centroids of SGN segmentation.
160+
max_edge_distance: Maximal edge distance between graph nodes to create an edge between nodes.
155161
156162
Returns:
157163
Total distance of the path.
158164
Path as an nd.array of positions.
159165
A dictionary containing the position and the length fraction of each point in the path.
160166
"""
161167
graph = nx.Graph()
162-
for num, pos in enumerate(centroids):
168+
coords = {}
169+
labels = [int(i) for i in range(len(centroids))]
170+
for index, element in zip(labels, centroids):
171+
coords[index] = element
172+
173+
for num, pos in coords.items():
163174
graph.add_node(num, pos=pos)
164-
# approximate Steiner tree and find shortest path between the two most distant nodes
165-
terminals = set(graph.nodes()) # All nodes are required
166-
# Approximate Steiner Tree over all nodes
167-
T = steiner_tree(graph, terminals)
168-
u, v = find_most_distant_nodes(T)
169-
path = nx.shortest_path(T, source=u, target=v)
170-
total_distance = nx.path_weight(T, path, weight="weight")
175+
176+
# TODO: Find cleaner option than the creation of a new graph with edges
177+
# to identify start / end point and shortest path.
178+
179+
# create edges between points whose distance is less than threshold max_edge_distance
180+
for num_i, pos_i in coords.items():
181+
for num_j, pos_j in coords.items():
182+
if num_i < num_j:
183+
dist = math.dist(pos_i, pos_j)
184+
if dist <= max_edge_distance:
185+
graph.add_edge(num_i, num_j, weight=dist)
186+
187+
u, v = find_most_distant_nodes(graph)
188+
path = nx.shortest_path(graph, source=u, target=v)
189+
total_distance = nx.path_weight(graph, path, weight="weight")
171190

172191
# assign relative distance to points on path
173192
path_dict = {}
@@ -180,6 +199,9 @@ def measure_run_length_ihcs(centroids):
180199
path_dict[num + 1] = {"pos": graph.nodes[p]["pos"], "length_fraction": rel_dist}
181200
path_dict[len(path)] = {"pos": graph.nodes[path[-1]]["pos"], "length_fraction": 1}
182201

202+
path_pos = np.array([graph.nodes[p]["pos"] for p in path])
203+
path = moving_average_3d(path_pos, window=5)
204+
183205
return total_distance, path, path_dict
184206

185207

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
[
2+
{
3+
"cochlea": "M_AMD_OTOF1_L",
4+
"image_channel": [
5+
"Apha",
6+
"Vglut3",
7+
"IHC_v4b"
8+
],
9+
"segmentation_channel": "IHC_v4b",
10+
"type": "ihc",
11+
"n_blocks": 6,
12+
"component_list": [
13+
3,
14+
11
15+
],
16+
"halo_size": [
17+
256,
18+
256,
19+
128
20+
],
21+
"crop_centers": [
22+
[
23+
981,
24+
1326,
25+
947
26+
],
27+
[
28+
1281,
29+
731,
30+
699
31+
],
32+
[
33+
645,
34+
872,
35+
377
36+
],
37+
[
38+
614,
39+
1126,
40+
1048
41+
],
42+
[
43+
1017,
44+
453,
45+
1106
46+
],
47+
[
48+
715,
49+
37,
50+
527
51+
]
52+
]
53+
}
54+
]

reproducibility/block_extraction/repro_equidistant_centers.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ def repro_equidistant_centers(
3232

3333
for dic in param_dicts:
3434
cochlea = dic["cochlea"]
35-
img_channel = dic["image_channel"]
3635
seg_channel = dic["segmentation_channel"]
3736

3837
s3_path = os.path.join(f"{cochlea}", "tables", f"{seg_channel}", "default.tsv")
@@ -50,8 +49,7 @@ def repro_equidistant_centers(
5049

5150
centers = equidistant_centers(table, component_label=component_list, cell_type=cell_type, n_blocks=n_blocks)
5251
centers = [[int(c) for c in center] for center in centers]
53-
ddict = {"cochlea": cochlea}
54-
ddict["image_channel"] = img_channel
52+
ddict = dic.copy()
5553
ddict["crop_centers"] = centers
5654
ddict["halo_size"] = halo_size
5755
out_dict.append(ddict)
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
[
22
{
33
"cochlea": "M_LR_000226_L",
4-
"segmentation_channel": "IHC_v3",
4+
"segmentation_channel": "IHC_v4b",
55
"type": "ihc"
66
},
77
{
88
"cochlea": "M_LR_000226_R",
9-
"segmentation_channel": "IHC_v3",
9+
"segmentation_channel": "IHC_v4b",
1010
"type": "ihc"
1111
},
1212
{
1313
"cochlea": "M_LR_000227_L",
14-
"segmentation_channel": "IHC_v3",
14+
"segmentation_channel": "IHC_v4b",
1515
"type": "ihc"
1616
},
1717
{
1818
"cochlea": "M_LR_000227_R",
19-
"segmentation_channel": "IHC_v3",
19+
"segmentation_channel": "IHC_v4b",
2020
"type": "ihc"
2121
}
2222
]

reproducibility/tonotopic_mapping/2025-07-SGN.json

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[
2+
{
3+
"cochlea": "M_LR_000143_L",
4+
"segmentation_channel": "SGN_v2",
5+
"type": "sgn"
6+
},
7+
{
8+
"cochlea": "M_LR_000144_L",
9+
"segmentation_channel": "SGN_v2",
10+
"type": "sgn"
11+
},
12+
{
13+
"cochlea": "M_LR_000145_L",
14+
"segmentation_channel": "SGN_v2",
15+
"type": "sgn"
16+
},
17+
{
18+
"cochlea": "M_LR_000189_L",
19+
"segmentation_channel": "SGN_v2",
20+
"type": "sgn"
21+
},
22+
{
23+
"cochlea": "M_LR_000144_R",
24+
"segmentation_channel": "SGN_v2",
25+
"type": "sgn"
26+
},
27+
{
28+
"cochlea": "M_LR_000145_R",
29+
"segmentation_channel": "SGN_v2",
30+
"type": "sgn"
31+
},
32+
{
33+
"cochlea": "M_LR_000153_R",
34+
"segmentation_channel": "SGN_v2",
35+
"type": "sgn"
36+
},
37+
{
38+
"cochlea": "M_LR_000155_R",
39+
"segmentation_channel": "SGN_v2",
40+
"type": "sgn"
41+
},
42+
{
43+
"cochlea": "M_LR_000189_R",
44+
"segmentation_channel": "SGN_v2",
45+
"type": "sgn"
46+
}
47+
]

reproducibility/tonotopic_mapping/2025-07-SGN_fig2.json

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,21 @@
22
{
33
"cochlea": "M_LR_000226_L",
44
"segmentation_channel": "SGN_v2",
5-
"type": "sgn",
6-
"filter_factor": 0.75
5+
"type": "sgn"
76
},
87
{
98
"cochlea": "M_LR_000226_R",
109
"segmentation_channel": "SGN_v2",
11-
"type": "sgn",
12-
"filter_factor": 0.75
10+
"type": "sgn"
1311
},
1412
{
1513
"cochlea": "M_LR_000227_L",
1614
"segmentation_channel": "SGN_v2",
17-
"type": "sgn",
18-
"max_edge_distance": 70,
19-
"filter_factor": 0.75
15+
"type": "sgn"
2016
},
2117
{
2218
"cochlea": "M_LR_000227_R",
2319
"segmentation_channel": "SGN_v2",
24-
"type": "sgn",
25-
"filter_factor": 0.75
20+
"type": "sgn"
2621
}
2722
]

scripts/export_lower_resolution.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import argparse
22
import os
33
from typing import List, Optional
4+
import warnings
45

56
import numpy as np
67
import pandas as pd
@@ -21,10 +22,12 @@ def filter_component(fs, segmentation, cochlea, seg_name, components):
2122
# Then we get the ids for the components and us them to filter the segmentation.
2223
component_mask = np.isin(table.component_labels.values, components)
2324
keep_label_ids = table.label_id.values[component_mask].astype("int64")
25+
if max(keep_label_ids) > np.iinfo("uint16").max:
26+
warnings.warn(f"Label ID exceeds maximum of data type 'uint16': {np.iinfo('uint16').max}.")
27+
2428
filter_mask = ~np.isin(segmentation, keep_label_ids)
2529
segmentation[filter_mask] = 0
26-
27-
# segmentation, _, _ = relabel_sequential(segmentation)
30+
segmentation = segmentation.astype("uint16")
2831
return segmentation
2932

3033

0 commit comments

Comments
 (0)