2323from scipy .ndimage import zoom
2424
2525
26- def _percentile_norm (arr , pct_low = 1 , pct_high = 99 ):
27- """Percentile-normalise *arr* to the range [0, 1] using global statistics."""
26+ def _estimate_global_percentiles (
27+ arr ,
28+ pct_low = 1 ,
29+ pct_high = 99 ,
30+ ):
31+ """
32+ Estimate percentile normalization bounds from an image
33+ """
2834 lo = np .percentile (arr , pct_low )
2935 hi = np .percentile (arr , pct_high )
30- if hi > lo :
31- return np .clip ((arr .astype (float ) - lo ) / (hi - lo ), 0.0 , 1.0 )
32- return np .zeros_like (arr , dtype = float )
33-
34-
35- def _match_shape (source , target_shape , order = 1 ):
36- """Zoom *source* array to *target_shape* if shapes differ."""
37- if source .shape == target_shape :
38- return source
39- factors = [t / s for t , s in zip (target_shape , source .shape )]
40- return zoom (source , factors , order = order )
41-
36+ return float (lo ), float (hi )
4237
43- def _select_best_z_slice (ff_crop ):
44- """Return the Z-index with the most field-fraction signal.
4538
46- Falls back to the central slice when no signal is present.
47- """
48- ff_per_z = ff_crop .sum (axis = (0 , 1 ))
49- if ff_per_z .max () > 0 :
50- return int (ff_per_z .argmax ())
51- return ff_crop .shape [2 ] // 2
52-
53-
54- def _get_bounding_box (mask , pad = 5 ):
55- """Return index slices for the bounding box of a boolean *mask* with padding.
56-
57- Returns ``None`` when the mask is empty.
58- """
59- indices = np .where (mask )
60- if not indices [0 ].size :
61- return None
62- shape = mask .shape
63- return tuple (
64- slice (max (0 , int (idx .min ()) - pad ), min (sz , int (idx .max ()) + pad + 1 ))
65- for idx , sz in zip (indices , shape )
66- )
39+ def _apply_fixed_percentile_norm (arr , lo , hi ):
40+ if hi > lo :
41+ return np .clip ((arr .astype (np .float32 ) - lo ) / (hi - lo ), 0.0 , 1.0 )
42+ return np .zeros_like (arr , dtype = np .float32 )
6743
6844
6945def main ():
@@ -73,20 +49,33 @@ def main():
7349 max_rois = snakemake .params .max_rois
7450 n_cols = snakemake .params .n_cols
7551
76- spim_img = ZarrNii .from_ome_zarr (snakemake .input .spim ,level = snakemake .params .level , downsample_near_isotropic = True ,channel_labels = [snakemake .wildcards .stain ])
77- mask_img = ZarrNii .from_ome_zarr (snakemake .input .mask ,level = 0 )
78-
79- atlas = ZarrNiiAtlas .from_files (snakemake .input .dseg_nii ,snakemake .input .label_tsv )
52+ spim_img = ZarrNii .from_ome_zarr (
53+ snakemake .input .spim ,
54+ level = snakemake .params .level ,
55+ downsample_near_isotropic = True ,
56+ channel_labels = [snakemake .wildcards .stain ],
57+ )
58+ mask_img = ZarrNii .from_ome_zarr (snakemake .input .mask , level = 0 )
8059
81- dseg_data = atlas . dseg . data . compute ( )
60+ atlas = ZarrNiiAtlas . from_files ( snakemake . input . dseg_nii , snakemake . input . label_tsv )
8261
62+ dseg_data = atlas .dseg .data .compute ()
8363
8464 # Voxel dimensions (mm) for physical aspect-ratio correction - not implemented yet
85- # but should be easy with ZarrNii image .scale
65+ # but should be easy with ZarrNii image .scale
8666 aspect_axial = 1
8767
68+ spim_img_ds = ZarrNii .from_ome_zarr (
69+ snakemake .input .spim ,
70+ level = (int (snakemake .params .level ) + 5 ),
71+ downsample_near_isotropic = True ,
72+ channel_labels = [snakemake .wildcards .stain ],
73+ )
8874
89- # Global normalisations - not implemented yet
75+ # estimate once globally, from a coarse version of the full image
76+ glob_lo , glob_hi = _estimate_global_percentiles (
77+ spim_img_ds .data .compute (), pct_low = 1 , pct_high = 99
78+ )
9079
9180 # Load atlas label table
9281 label_df = atlas .labels_df
@@ -145,20 +134,27 @@ def main():
145134 label_id = int (row ["index" ])
146135 label_name = str (row .get ("name" , label_id ))
147136
148-
149- #get cropped images for this label
137+ # get cropped images for this label
150138 bbox_min , bbox_max = atlas .get_region_bounding_box (region_ids = label_id )
151139 center_coord = tuple ((x + y ) / 2 for x , y in zip (bbox_min , bbox_max ))
152- spim_crop = spim_img .crop_centered (center_coord , patch_size = (2000 ,2000 ,1 ))
153- mask_crop = mask_img .crop_centered (center_coord , patch_size = (2000 ,2000 ,1 ))
154-
140+ spim_crop = spim_img .crop_centered (
141+ center_coord ,
142+ patch_size = (snakemake .params .patch_size , snakemake .params .patch_size , 1 ),
143+ )
144+ mask_crop = mask_img .crop_centered (
145+ center_coord ,
146+ patch_size = (snakemake .params .patch_size , snakemake .params .patch_size , 1 ),
147+ )
155148
156- spim_sl = np .rot90 (spim_crop .data [0 , :, :].squeeze ().compute ())
157- mask_sl = np .rot90 (mask_crop .data [0 , :, :].squeeze ().compute ())
149+ spim_sl = spim_crop .data [0 , :, :].squeeze ().compute ()
150+ spim_sl = _apply_fixed_percentile_norm (spim_sl , glob_lo , glob_hi )
151+ mask_sl = mask_crop .data [0 , :, :].squeeze ().compute ()
158152
159153 ax .imshow (spim_sl , cmap = "gray" )
160154 mask_masked = np .ma .masked_where (mask_sl < 100 , mask_sl )
161- ax .imshow (mask_masked , cmap = "spring" , alpha = 0.6 , vmin = 0 , vmax = 100 , aspect = aspect_axial )
155+ ax .imshow (
156+ mask_masked , cmap = "spring" , alpha = 0.6 , vmin = 0 , vmax = 100 , aspect = aspect_axial
157+ )
162158 ax .set_title (label_name , fontsize = 7 , pad = 2 )
163159 ax .set_xticks ([])
164160 ax .set_yticks ([])
0 commit comments