Skip to content

Commit e3c0055

Browse files
Sgn density and volume analysis (#78)
* Implement SGN density analysis * Add volume analysis * Update density analysis * Update rankovic analysis * Add IHC density analysis * Update SGN density plots * Update alias in density and volume scripts
1 parent 7d767ee commit e3c0055

File tree

7 files changed

+609
-7
lines changed

7 files changed

+609
-7
lines changed

flamingo_tools/measurements.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def _get_bounding_box_and_center(table, seg_id, resolution, shape, dilation):
4343
if dilation is not None and dilation > 0:
4444
bb_extension = dilation + 1
4545
else:
46-
bb_extension = 1
46+
bb_extension = 2
4747

4848
bb_min = np.array([
4949
row.bb_min_z.item(), row.bb_min_y.item(), row.bb_min_x.item()
@@ -169,6 +169,21 @@ def _default_object_features(
169169
return measures
170170

171171

172+
def _morphology_features(seg_id, table, image, segmentation, resolution, **kwargs):
173+
measures = {"label_id": seg_id}
174+
175+
bb, center = _get_bounding_box_and_center(table, seg_id, resolution, image.shape, dilation=0)
176+
mask = segmentation[bb] == seg_id
177+
178+
# Hard-coded value for LaVision cochleae. This is a hack for the wrong voxel size in MoBIE.
179+
# resolution = (3.0, 0.76, 0.76)
180+
181+
volume, surface = _measure_volume_and_surface(mask, resolution)
182+
measures["volume"] = volume
183+
measures["surface"] = surface
184+
return measures
185+
186+
172187
def _regionprops_features(seg_id, table, image, segmentation, resolution, background_mask=None, dilation=None):
173188
bb, _ = _get_bounding_box_and_center(table, seg_id, resolution, image.shape, dilation)
174189

@@ -222,6 +237,7 @@ def get_object_measures_from_table(arr_seg, table):
222237
"skimage": _regionprops_features,
223238
"default_background_norm": partial(_default_object_features, background_radius=75, norm=np.divide),
224239
"default_background_subtract": partial(_default_object_features, background_radius=75, norm=np.subtract),
240+
"morphology": _morphology_features,
225241
}
226242
"""The different feature functions that are supported in `compute_object_measures` and
227243
that can be selected via the feature_set argument. Currently this supports:

scripts/figures/plot_fig6.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def fig_06e_octave(otof_data, save_path, plot=False, use_alias=True, trendline_m
168168
marker_neg = len([1 for i in marker_labels if i == 2])
169169
expression_eff = marker_pos / (marker_pos + marker_neg)
170170
print(f"Cochlea {name}, average expression efficiency {expression_eff}")
171+
print(f"Cochlea {name}, number of IHCs: {len(freq)}")
171172
octave_binned = frequency_mapping(
172173
freq, marker_labels, animal="mouse", transduction_efficiency=True,
173174
bin_edges=bin_edges, bin_labels=bin_labels

scripts/figures/util.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ def _get_mapping(animal):
168168

169169

170170
def frequency_mapping(
171-
frequencies, values, animal="mouse", transduction_efficiency=False, bin_edges=None, bin_labels=None
171+
frequencies, values, animal="mouse", transduction_efficiency=False,
172+
bin_edges=None, bin_labels=None, aggregation="mean",
172173
):
173174
# Get the mapping of frequencies to octave bands for the given species.
174175
if bin_edges is None:
@@ -186,11 +187,8 @@ def frequency_mapping(
186187
num_tot = df[df["value"].isin([1, 2])].groupby("octave_band", observed=False).size()
187188
value_by_band = (num_pos / num_tot).reindex(bin_labels)
188189
else: # Otherwise, aggregate the values over the octave band using the mean.
189-
value_by_band = (
190-
df.groupby("octave_band", observed=True)["value"]
191-
.mean()
192-
.reindex(bin_labels) # keep octave order even if a bin is empty
193-
)
190+
aggregator = getattr(df.groupby("octave_band", observed=True)["value"], aggregation)
191+
value_by_band = aggregator().reindex(bin_labels) # keep octave order even if a bin is empty
194192
return value_by_band
195193

196194

scripts/la-vision/otof/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Analysis Reuslts OTOF La-Vision Data
2+
3+
## From Rankovic et al.
4+
5+
- OTOF-23R:
6+
- N-IHCS: 340
7+
- Expression Efficiency: 0.3058823529411765
8+
- OTOF-25R:
9+
- N-IHCS: 346
10+
- Expression Efficiency: 0.21676300578034682
11+
12+
## Our Analysis
13+
14+
- OTOF-23R:
15+
- N-IHCS: 643
16+
- Expression Efficiency: 0.38202247191011235
17+
- OTOF-25R:
18+
- N-IHCS: 520
19+
- Expression Efficiency: 0.37884615384615383
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import sys
2+
3+
import matplotlib.pyplot as plt
4+
5+
sys.path.append("../../figures")
6+
7+
8+
def plot_tonotopy(bin_labels, values, names, expression_eff_list):
9+
from util import prism_style, prism_cleanup_axes
10+
11+
color_dict = {"O1": "#9C5027", "O2": "#67279C"}
12+
13+
prism_style()
14+
label_size = 20
15+
tick_label_size = 14
16+
17+
# result = pd.DataFrame(result)
18+
# bin_labels = pd.unique(result["octave_band"])
19+
# band_to_x = {band: i for i, band in enumerate(bin_labels)}
20+
# result["x_pos"] = result["octave_band"].map(band_to_x)
21+
22+
fig, ax = plt.subplots(figsize=(8, 4))
23+
24+
offset = 0.08
25+
for num in range(len(names)):
26+
27+
name = names[num]
28+
expr_vals = values[num]
29+
30+
x_positions = range(len(bin_labels))
31+
x_positions = [x - len(bin_labels) // 2 * offset + offset * num for x in x_positions]
32+
33+
x_positions = [pos for i, pos in enumerate(x_positions) if expr_vals[i] is not None]
34+
expr_vals = [val for val in expr_vals if val is not None]
35+
assert len(x_positions) == len(expr_vals)
36+
37+
ax.scatter(x_positions, expr_vals, marker="o", label=name, s=80, alpha=1, color=color_dict[name])
38+
39+
xlim_left, xlim_right = ax.get_xlim()
40+
y_offset = [0.01, -0.04]
41+
x_offset = 0.5
42+
plt.xlim(xlim_left, xlim_right)
43+
for num, key in enumerate(color_dict.keys()):
44+
color = color_dict[key]
45+
expression_eff = expression_eff_list[num]
46+
47+
ax.text(xlim_left + x_offset, expression_eff + y_offset[num], "mean",
48+
color=color, fontsize=tick_label_size, ha="center")
49+
trend_r, = ax.plot(
50+
[xlim_left, xlim_right],
51+
[expression_eff, expression_eff],
52+
linestyle="dashed",
53+
color=color,
54+
alpha=0.7,
55+
zorder=0
56+
)
57+
58+
ax.set_xticks(range(len(bin_labels)))
59+
ax.set_xticklabels(bin_labels)
60+
ax.set_xlabel("Octave band [kHz]", fontsize=label_size)
61+
62+
ax.set_ylabel("Expression efficiency")
63+
plt.tight_layout()
64+
prism_cleanup_axes(ax)
65+
66+
plt.show()
67+
68+
69+
def main():
70+
frequencies = ["4-8", "8-12", "12-16", "16-24", "24-32"]
71+
72+
n_ihcs_23R = [51, 107, 77, 105, None]
73+
n_pos_23R = [15, 36, 24, 29, None]
74+
assert len(n_ihcs_23R) == len(n_pos_23R)
75+
assert len(n_ihcs_23R) == len(frequencies)
76+
77+
n_ihcs_25R = [None, 103, 71, 102, 70]
78+
n_pos_25R = [None, 24, 21, 28, 2]
79+
assert len(n_ihcs_25R) == len(n_pos_25R)
80+
assert len(n_ihcs_25R) == len(frequencies)
81+
82+
total_23R = sum(n for n in n_ihcs_23R if n is not None)
83+
pos_23R = sum(n for n in n_pos_23R if n is not None)
84+
eff_23R = float(pos_23R) / total_23R
85+
print("N-IHCs 23R:", total_23R)
86+
print("Expr. Eff.:", eff_23R)
87+
print()
88+
89+
total_25R = sum(n for n in n_ihcs_25R if n is not None)
90+
pos_25R = sum(n for n in n_pos_25R if n is not None)
91+
eff_25R = float(pos_25R) / total_25R
92+
print("N-IHCs 25R:", total_25R)
93+
print("Expr. Eff.:", eff_25R)
94+
95+
expr_23R = [None if n is None else float(pos) / n
96+
for pos, n in zip(n_pos_23R, n_ihcs_23R)]
97+
expr_25R = [None if n is None else float(pos) / n
98+
for pos, n in zip(n_pos_25R, n_ihcs_25R)]
99+
plot_tonotopy(
100+
frequencies,
101+
values=[expr_23R, expr_25R],
102+
names=["O1", "O2"],
103+
expression_eff_list=[eff_23R, eff_25R]
104+
)
105+
106+
107+
if __name__ == "__main__":
108+
main()

0 commit comments

Comments
 (0)