@@ -199,20 +199,50 @@ def extract_svg(display_object, dpi=300, compress='auto'):
199199def cuts_from_bbox (mask_nii , cuts = 3 ):
200200 """Finds equi-spaced cuts for presenting images"""
201201 from nibabel .affines import apply_affine
202- mask_data = mask_nii .get_data ()
203- B = np .argwhere (mask_data > 0 )
204- start_coords = B .min (0 )
205- stop_coords = B .max (0 ) + 1
202+
203+ mask_data = mask_nii .get_data () > 0.0
204+
205+ # First, project the number of masked voxels on each axes
206+ ijk_counts = [
207+ mask_data .sum (2 ).sum (1 ), # project sagittal planes to transverse (i) axis
208+ mask_data .sum (2 ).sum (0 ), # project coronal planes to to longitudinal (j) axis
209+ mask_data .sum (1 ).sum (0 ), # project axial planes to vertical (k) axis
210+ ]
211+
212+ # If all voxels are masked in a slice (say that happens at k=10),
213+ # then the value for ijk_counts for the projection to k (ie. ijk_counts[2])
214+ # at that element of the orthogonal axes (ijk_counts[2][10]) is
215+ # the total number of voxels in that slice (ie. Ni x Nj).
216+ # Here we define some thresholds to consider the plane as "masked"
217+ # The thresholds vary because of the shape of the brain
218+ # I have manually found that for the axial view requiring 30%
219+ # of the slice elements to be masked drops almost empty boxes
220+ # in the mosaic of axial planes (and also addresses #281)
221+ ijk_th = [
222+ int ((mask_data .shape [1 ] * mask_data .shape [2 ]) * 0.2 ), # sagittal
223+ int ((mask_data .shape [0 ] * mask_data .shape [2 ]) * 0.0 ), # coronal
224+ int ((mask_data .shape [0 ] * mask_data .shape [1 ]) * 0.3 ), # axial
225+ ]
206226
207227 vox_coords = []
208- for start , stop in zip (start_coords , stop_coords ):
209- inc = abs (stop - start ) / (cuts + 1 )
210- vox_coords .append ([start + (i + 1 ) * inc for i in range (cuts )])
228+ for ax , (c , th ) in enumerate (zip (ijk_counts , ijk_th )):
229+ B = np .argwhere (c > th )
230+ if B .size :
231+ smin , smax = B .min (), B .max ()
232+
233+ # Avoid too narrow selections of cuts (very small masks)
234+ if not B .size or (th > 0 and (smin + cuts + 1 ) >= smax ):
235+ B = np .argwhere (c > 0 )
236+
237+ # Resort to full plane if mask is seemingly empty
238+ smin , smax = B .min (), B .max () if B .size else (0 , mask_data .shape [ax ])
239+ inc = (smax - smin ) / (cuts + 1 )
240+ vox_coords .append ([smin + (i + 1 ) * inc for i in range (cuts )])
211241
212242 ras_coords = []
213243 for cross in np .array (vox_coords ).T :
214- ras_coords .append (apply_affine (mask_nii . affine , cross ). tolist ())
215-
244+ ras_coords .append (apply_affine (
245+ mask_nii . affine , cross ). tolist ())
216246 ras_cuts = [list (coords ) for coords in np .transpose (ras_coords )]
217247 return {k : v for k , v in zip (['x' , 'y' , 'z' ], ras_cuts )}
218248
0 commit comments