11import argparse
22import os
3+ from typing import List , Optional
34
45import numpy as np
56import pandas as pd
67import tifffile
78import zarr
89
910from flamingo_tools .s3_utils import get_s3_path , BUCKET_NAME , SERVICE_ENDPOINT
11+ from flamingo_tools .segmentation .postprocessing import filter_cochlea_volume , filter_cochlea_volume_single
1012# from skimage.segmentation import relabel_sequential
1113
1214
@@ -26,37 +28,157 @@ def filter_component(fs, segmentation, cochlea, seg_name, components):
2628 return segmentation
2729
2830
31+ def filter_cochlea (
32+ cochlea : str ,
33+ filter_cochlea_channels : str ,
34+ sgn_components : Optional [List [int ]] = None ,
35+ ihc_components : Optional [List [int ]] = None ,
36+ ds_factor : int = 24 ,
37+ dilation_iterations : int = 8 ,
38+ ) -> np .ndarray :
39+ """Pre-process information for filtering cochlea volume based on segmentation table.
40+ Differentiates between the input of a single channel of either IHC or SGN or if both are supplied.
41+ If a single channel is given, the filtered volume contains
42+ a down-sampled segmentation area, which has been dilated.
43+ If both IHC and SGN segmentation are supplied, a more specialized dilation
44+ is applied to ensure that the connecting volume is not filtered.
45+
46+ Args:
47+ cochlea: Name of cochlea.
48+ filter_cochlea_channels: Segmentation table(s) used for filtering.
49+ sgn_components: Component labels for filtering SGN segmentation table.
50+ ihc_components: Component labels for filtering IHC segmentation table.
51+ ds_factor: Down-sampling factor for filtering.
52+ dilation_iterations: Iterations for dilating binary segmentation mask.
53+
54+ Returns:
55+ Binary 3D array of filtered cochlea
56+ """
57+ # we check if the supplied channels contain an SGN and IHC channel
58+ sgn_channels = [ch for ch in filter_cochlea_channels if "SGN" in ch ]
59+ sgn_channel = None if len (sgn_channels ) == 0 else sgn_channels [0 ]
60+
61+ ihc_channels = [ch for ch in filter_cochlea_channels if "IHC" in ch ]
62+ ihc_channel = None if len (ihc_channels ) == 0 else ihc_channels [0 ]
63+
64+ if ihc_channel is None and sgn_channel is None :
65+ raise ValueError ("Channels supplied for filtering cochlea volume do not contain an IHC or SGN segmentation." )
66+
67+ if sgn_channel is not None :
68+ internal_path = os .path .join (cochlea , "tables" , sgn_channel , "default.tsv" )
69+ tsv_path , fs = get_s3_path (internal_path , bucket_name = BUCKET_NAME , service_endpoint = SERVICE_ENDPOINT )
70+ with fs .open (tsv_path , "r" ) as f :
71+ table_sgn = pd .read_csv (f , sep = "\t " )
72+
73+ if ihc_channel is not None :
74+ internal_path = os .path .join (cochlea , "tables" , ihc_channel , "default.tsv" )
75+ tsv_path , fs = get_s3_path (internal_path , bucket_name = BUCKET_NAME , service_endpoint = SERVICE_ENDPOINT )
76+ with fs .open (tsv_path , "r" ) as f :
77+ table_ihc = pd .read_csv (f , sep = "\t " )
78+
79+ if sgn_channel is None :
80+ # filter based in IHC segmentation
81+ return filter_cochlea_volume_single (table_ihc , components = ihc_components ,
82+ scale_factor = ds_factor , dilation_iterations = dilation_iterations )
83+ elif ihc_channel is None :
84+ # filter based on SGN segmentation
85+ return filter_cochlea_volume_single (table_sgn , components = sgn_components ,
86+ scale_factor = ds_factor , dilation_iterations = dilation_iterations )
87+ else :
88+ # filter based on SGN and IHC segmentation with a specialized function
89+ return filter_cochlea_volume (table_sgn , table_ihc ,
90+ sgn_components = sgn_components ,
91+ ihc_components = ihc_components ,
92+ scale_factor = ds_factor ,
93+ dilation_iterations = dilation_iterations )
94+
95+
96+ def upscale_volume (
97+ target_data : np .ndarray ,
98+ downscaled_volume : np .ndarray ,
99+ upscale_factor : int ,
100+ ) -> np .ndarray :
101+ """Up-scale binary 3D mask to dimensions of target data.
102+ After an initial up-scaling, the dimensions are cropped or zero-padded to fit the target shape.
103+
104+ Args:
105+ target_data: Reference data for up-scaling.
106+ downscaled_volume: Down-scaled binary 3D array.
107+ upscale_factor: Initial factor for up-scaling binary array.
108+
109+ Returns:
110+ Resized binary array.
111+ """
112+ target_shape = target_data .shape
113+ upscaled_filter = np .repeat (
114+ np .repeat (
115+ np .repeat (downscaled_volume , upscale_factor , axis = 0 ),
116+ upscale_factor , axis = 1 ),
117+ upscale_factor , axis = 2 )
118+ resized = np .zeros (target_shape , dtype = target_data .dtype )
119+ min_x , min_y , min_z = tuple (min (upscaled_filter .shape [i ], target_shape [i ]) for i in range (3 ))
120+ resized [:min_x , :min_y , :min_z ] = upscaled_filter [:min_x , :min_y , :min_z ]
121+ return resized
122+
123+
29124def export_lower_resolution (args ):
30- output_folder = os .path .join (args .output_folder , args .cochlea , f"scale{ args .scale } " )
31- os .makedirs (output_folder , exist_ok = True )
32-
33- input_key = f"s{ args .scale } "
34- for channel in args .channels :
35- out_path = os .path .join (output_folder , f"{ channel } .tif" )
36- if os .path .exists (out_path ):
37- continue
38-
39- print ("Exporting channel" , channel )
40- internal_path = os .path .join (args .cochlea , "images" , "ome-zarr" , f"{ channel } .ome.zarr" )
41- s3_store , fs = get_s3_path (internal_path , bucket_name = BUCKET_NAME , service_endpoint = SERVICE_ENDPOINT )
42- with zarr .open (s3_store , mode = "r" ) as f :
43- data = f [input_key ][:]
44- print (data .shape )
45- if args .filter_by_components is not None :
46- data = filter_component (fs , data , args .cochlea , channel , args .filter_by_components )
47- if args .binarize :
48- data = (data > 0 ).astype ("uint16" )
49- tifffile .imwrite (out_path , data , bigtiff = True , compression = "zlib" )
125+ # calculate single filter mask for all lower resolutions
126+ if args .filter_cochlea_channels is not None :
127+ ds_factor = 48
128+ filter_volume = filter_cochlea (args .cochlea , args .filter_cochlea_channels ,
129+ sgn_components = args .filter_sgn_components ,
130+ ihc_components = args .filter_ihc_components ,
131+ dilation_iterations = args .filter_dilation_iterations , ds_factor = ds_factor )
132+ filter_volume = np .transpose (filter_volume , (2 ,1 ,0 ))
133+
134+ # iterate through exporting lower resolutions
135+ for scale in args .scale :
136+ if args .filter_cochlea_channels is not None :
137+ output_folder = os .path .join (args .output_folder , args .cochlea ,
138+ f"scale{ scale } _dilation{ args .filter_dilation_iterations } " )
139+ else :
140+ output_folder = os .path .join (args .output_folder , args .cochlea , f"scale{ scale } " )
141+ os .makedirs (output_folder , exist_ok = True )
142+
143+ input_key = f"s{ scale } "
144+ for channel in args .channels :
145+ out_path = os .path .join (output_folder , f"{ channel } .tif" )
146+ if os .path .exists (out_path ):
147+ continue
148+
149+ print ("Exporting channel" , channel )
150+ internal_path = os .path .join (args .cochlea , "images" , "ome-zarr" , f"{ channel } .ome.zarr" )
151+ s3_store , fs = get_s3_path (internal_path , bucket_name = BUCKET_NAME , service_endpoint = SERVICE_ENDPOINT )
152+ with zarr .open (s3_store , mode = "r" ) as f :
153+ data = f [input_key ][:]
154+ print ("Data shape" , data .shape )
155+ if args .filter_by_components is not None :
156+ data = filter_component (fs , data , args .cochlea , channel , args .filter_by_components )
157+ if args .filter_cochlea_channels is not None :
158+ us_factor = ds_factor // (2 ** scale )
159+ upscaled_filter = upscale_volume (data , filter_volume , upscale_factor = us_factor )
160+ data [upscaled_filter == 0 ] = 0
161+ if "PV" in channel :
162+ max_intensity = 1400
163+ data [data > max_intensity ] = 0
164+
165+ if args .binarize :
166+ data = (data > 0 ).astype ("uint16" )
167+ tifffile .imwrite (out_path , data , bigtiff = True , compression = "zlib" )
50168
51169
52170def main ():
53171 parser = argparse .ArgumentParser ()
54172 parser .add_argument ("--cochlea" , "-c" , required = True )
55- parser .add_argument ("--scale" , "-s" , type = int , required = True )
173+ parser .add_argument ("--scale" , "-s" , nargs = "+" , type = int , required = True )
56174 parser .add_argument ("--output_folder" , "-o" , required = True )
57- parser .add_argument ("--channels" , nargs = "+" , default = ["PV" , "VGlut3" , "CTBP2" ])
175+ parser .add_argument ("--channels" , nargs = "+" , type = str , default = ["PV" , "VGlut3" , "CTBP2" ])
58176 parser .add_argument ("--filter_by_components" , nargs = "+" , type = int , default = None )
177+ parser .add_argument ("--filter_sgn_components" , nargs = "+" , type = int , default = [1 ])
178+ parser .add_argument ("--filter_ihc_components" , nargs = "+" , type = int , default = [1 ])
59179 parser .add_argument ("--binarize" , action = "store_true" )
180+ parser .add_argument ("--filter_cochlea_channels" , nargs = "+" , type = str , default = None )
181+ parser .add_argument ("--filter_dilation_iterations" , type = int , default = 8 )
60182 args = parser .parse_args ()
61183
62184 export_lower_resolution (args )
0 commit comments