Skip to content

Commit b90687b

Browse files
authored
Merge pull request #49 from computational-cell-analytics/export_filtered_cochlea
Filter cochlea with IHC and SGN segmentation tables and export lower resolution.
2 parents e615d60 + 7706f5a commit b90687b

File tree

9 files changed

+653
-30
lines changed

9 files changed

+653
-30
lines changed

flamingo_tools/segmentation/postprocessing.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pandas as pd
1111

1212
from elf.io import open_file
13+
from scipy.ndimage import distance_transform_edt, binary_dilation, binary_closing
1314
from scipy.sparse import csr_matrix
1415
from scipy.spatial import distance
1516
from scipy.spatial import cKDTree, ConvexHull
@@ -615,3 +616,177 @@ def postprocess_ihc_seg(
615616
table.loc[:, "component_labels"] = comp_labels
616617

617618
return table
619+
620+
621+
def dilate_and_trim(
622+
arr_orig: np.ndarray,
623+
edt: np.ndarray,
624+
iterations: int = 15,
625+
offset: float = 0.4,
626+
) -> np.ndarray:
627+
"""Dilate and trim original binary array according to a
628+
Euclidean Distance Trasform computed for a separate target array.
629+
630+
Args:
631+
arr_orig: Original 3D binary array
632+
edt: 3D array containing Euclidean Distance transform for guiding dilation
633+
iterations: Number of iterations for dilations
634+
offset: Offset for regulating dilation. value should be in range(0, 0.45)
635+
636+
Returns:
637+
Dilated binary array
638+
"""
639+
border_coords = [(1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)]
640+
for _ in range(iterations):
641+
arr_dilated = binary_dilation(arr_orig)
642+
for x in range(arr_dilated.shape[0]):
643+
for y in range(arr_dilated.shape[1]):
644+
for z in range(arr_dilated.shape[2]):
645+
if arr_dilated[x, y, z] != 0:
646+
if arr_orig[x, y, z] == 0:
647+
min_dist = float('inf')
648+
for dx, dy, dz in border_coords:
649+
nx, ny, nz = x+dx, y+dy, z+dz
650+
if arr_orig[nx, ny, nz] == 1:
651+
min_dist = min([min_dist, edt[nx, ny, nz]])
652+
if edt[x, y, z] >= min_dist - offset:
653+
arr_dilated[x, y, z] = 0
654+
arr_orig = arr_dilated
655+
return arr_dilated
656+
657+
658+
def filter_cochlea_volume_single(
659+
table: pd.DataFrame,
660+
components: Optional[List[int]] = [1],
661+
scale_factor: int = 48,
662+
resolution: float = 0.38,
663+
dilation_iterations: int = 12,
664+
padding: int = 1200,
665+
) -> np.ndarray:
666+
"""Filter cochlea volume based on a segmentation table.
667+
Centroids contained in the segmentation table are used to create a down-scaled binary array.
668+
The array can be dilated.
669+
670+
Args:
671+
table: Segmentation table.
672+
components: Component labels for filtering segmentation table.
673+
scale_factor: Down-sampling factor for filtering.
674+
resolution: Resolution of pixel in µm.
675+
dilation_iterations: Iterations for dilating binary segmentation mask. A negative value omits binary closing.
676+
padding: Padding in pixel to apply to guessed dimensions based on centroid coordinates.
677+
678+
Returns:
679+
Binary 3D array of filtered cochlea.
680+
"""
681+
# filter components
682+
if components is not None:
683+
table = table[table["component_labels"].isin(components)]
684+
685+
# identify approximate input dimensions for down-scaling
686+
centroids = list(zip(table["anchor_x"] / resolution,
687+
table["anchor_y"] / resolution,
688+
table["anchor_z"] / resolution))
689+
690+
# padding the array allows for dilation without worrying about array borders
691+
max_x = table["anchor_x"].max() / resolution + padding
692+
max_y = table["anchor_y"].max() / resolution + padding
693+
max_z = table["anchor_z"].max() / resolution + padding
694+
ref_dimensions = (max_x, max_y, max_z)
695+
696+
# down-scale arrays
697+
array_downscaled = downscaled_centroids(centroids, ref_dimensions=ref_dimensions,
698+
scale_factor=scale_factor, downsample_mode="capped")
699+
700+
if dilation_iterations > 0:
701+
array_dilated = binary_dilation(array_downscaled, np.ones((3, 3, 3)), iterations=dilation_iterations)
702+
return binary_closing(array_dilated, np.ones((3, 3, 3)), iterations=1)
703+
704+
elif dilation_iterations == 0:
705+
return binary_closing(array_downscaled, np.ones((3, 3, 3)), iterations=1)
706+
707+
else:
708+
return array_downscaled
709+
710+
711+
def filter_cochlea_volume(
712+
sgn_table: pd.DataFrame,
713+
ihc_table: pd.DataFrame,
714+
sgn_components: Optional[List[int]] = [1],
715+
ihc_components: Optional[List[int]] = [1],
716+
scale_factor: int = 48,
717+
resolution: float = 0.38,
718+
dilation_iterations: int = 12,
719+
padding: int = 1200,
720+
dilation_method = "individual",
721+
) -> np.ndarray:
722+
"""Filter cochlea volume with SGN and IHC segmentation.
723+
Centroids contained in the segmentation tables are used to create down-scaled binary arrays.
724+
The arrays are then dilated using guided dilation to fill the section inbetween SGNs and IHCs.
725+
726+
Args:
727+
sgn_table: SGN segmentation table.
728+
ihc_table: IHC segmentation table.
729+
sgn_components: Component labels for filtering SGN segmentation table.
730+
ihc_components: Component labels for filtering IHC segmentation table.
731+
scale_factor: Down-sampling factor for filtering.
732+
resolution: Resolution of pixel in µm.
733+
dilation_iterations: Iterations for dilating binary segmentation mask.
734+
padding: Padding in pixel to apply to guessed dimensions based on centroid coordinates.
735+
dilation_method: Dilation style for SGN and IHC segmentation, either 'individual', 'combined' or no dilation.
736+
737+
Returns:
738+
Binary 3D array of filtered cochlea.
739+
"""
740+
# filter components
741+
if sgn_components is not None:
742+
sgn_table = sgn_table[sgn_table["component_labels"].isin(sgn_components)]
743+
if ihc_components is not None:
744+
ihc_table = ihc_table[ihc_table["component_labels"].isin(ihc_components)]
745+
746+
# identify approximate input dimensions for down-scaling
747+
centroids_sgn = list(zip(sgn_table["anchor_x"] / resolution,
748+
sgn_table["anchor_y"] / resolution,
749+
sgn_table["anchor_z"] / resolution))
750+
centroids_ihc = list(zip(ihc_table["anchor_x"] / resolution,
751+
ihc_table["anchor_y"] / resolution,
752+
ihc_table["anchor_z"] / resolution))
753+
754+
# padding the array allows for dilation without worrying about array borders
755+
max_x = max([sgn_table["anchor_x"].max(), ihc_table["anchor_x"].max()]) / resolution + padding
756+
max_y = max([sgn_table["anchor_y"].max(), ihc_table["anchor_y"].max()]) / resolution + padding
757+
max_z = max([sgn_table["anchor_z"].max(), ihc_table["anchor_z"].max()]) / resolution + padding
758+
ref_dimensions = (max_x, max_y, max_z)
759+
760+
# down-scale arrays
761+
array_downscaled_sgn = downscaled_centroids(centroids_sgn, ref_dimensions=ref_dimensions,
762+
scale_factor=scale_factor, downsample_mode="capped")
763+
764+
array_downscaled_ihc = downscaled_centroids(centroids_ihc, ref_dimensions=ref_dimensions,
765+
scale_factor=scale_factor, downsample_mode="capped")
766+
767+
# dilate down-scaled SGN array in direction of IHC segmentation
768+
distance_from_sgn = distance_transform_edt(~array_downscaled_sgn.astype(bool))
769+
iterations = 20
770+
arr_dilated = dilate_and_trim(array_downscaled_ihc.copy(), distance_from_sgn, iterations=iterations, offset=0.4)
771+
772+
# dilate single structures first
773+
if dilation_method == "individual":
774+
ihc_dilated = binary_dilation(array_downscaled_ihc, np.ones((3, 3, 3)), iterations=dilation_iterations)
775+
sgn_dilated = binary_dilation(array_downscaled_sgn, np.ones((3, 3, 3)), iterations=dilation_iterations)
776+
combined_dilated = arr_dilated + ihc_dilated + sgn_dilated
777+
combined_dilated[combined_dilated > 0] = 1
778+
combined_dilated = binary_dilation(combined_dilated, np.ones((3, 3, 3)), iterations=1)
779+
780+
# dilate combined structure
781+
elif dilation_method == "combined":
782+
# combine SGN, IHC, and region between both to form output mask
783+
combined_structure = arr_dilated + array_downscaled_ihc + array_downscaled_sgn
784+
combined_structure[combined_structure > 0] = 1
785+
combined_dilated = binary_dilation(combined_structure, np.ones((3, 3, 3)), iterations=dilation_iterations)
786+
787+
# no dilation of combined structure
788+
else:
789+
combined_dilated = arr_dilated + ihc_dilated + sgn_dilated
790+
combined_dilated[combined_dilated > 0] = 1
791+
792+
return combined_dilated
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[
2+
{
3+
"cochlea": "M_LR_000143_L",
4+
"image_channel": [
5+
"PV",
6+
"GFP",
7+
"SGN_v2"
8+
],
9+
"segmentation_channel": "SGN_v2",
10+
"type": "sgn",
11+
"n_blocks": 6,
12+
"halo_size": [
13+
256,
14+
256,
15+
50
16+
],
17+
"component_list": [
18+
1
19+
],
20+
"crop_centers": [
21+
[
22+
1761,
23+
716,
24+
810
25+
],
26+
[
27+
1637,
28+
795,
29+
854
30+
],
31+
[
32+
1563,
33+
872,
34+
940
35+
],
36+
[
37+
1432,
38+
939,
39+
940
40+
],
41+
[
42+
1284,
43+
950,
44+
912
45+
],
46+
[
47+
1180,
48+
900,
49+
815
50+
]
51+
]
52+
}
53+
]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[
2+
{
3+
"cochlea": "M_LR_000144_L",
4+
"image_channel": [
5+
"PV",
6+
"GFP",
7+
"SGN_v2"
8+
],
9+
"segmentation_channel": "SGN_v2",
10+
"type": "sgn",
11+
"n_blocks": 6,
12+
"halo_size": [
13+
256,
14+
256,
15+
50
16+
],
17+
"component_list": [
18+
1
19+
],
20+
"crop_centers": [
21+
[
22+
1762,
23+
598,
24+
666
25+
],
26+
[
27+
1441,
28+
526,
29+
738
30+
],
31+
[
32+
1352,
33+
848,
34+
696
35+
],
36+
[
37+
1500,
38+
924,
39+
972
40+
],
41+
[
42+
1357,
43+
716,
44+
1185
45+
],
46+
[
47+
1050,
48+
629,
49+
1309
50+
]
51+
]
52+
}
53+
]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[
2+
{
3+
"cochlea": "M_LR_000145_L",
4+
"image_channel": [
5+
"PV",
6+
"GFP",
7+
"SGN_v2"
8+
],
9+
"segmentation_channel": "SGN_v2",
10+
"type": "sgn",
11+
"n_blocks": 6,
12+
"halo_size": [
13+
256,
14+
256,
15+
50
16+
],
17+
"component_list": [
18+
1
19+
],
20+
"crop_centers": [
21+
[
22+
1080,
23+
849,
24+
700
25+
],
26+
[
27+
1034,
28+
557,
29+
603
30+
],
31+
[
32+
815,
33+
523,
34+
857
35+
],
36+
[
37+
625,
38+
769,
39+
775
40+
],
41+
[
42+
560,
43+
662,
44+
457
45+
],
46+
[
47+
442,
48+
339,
49+
420
50+
]
51+
]
52+
}
53+
]

0 commit comments

Comments
 (0)