@@ -3358,3 +3358,50 @@ def delete_volumes_from_4D_array(
33583358 out_array = in_array [:, :, :, vols_to_keep ]
33593359
33603360 return out_array , list (vols_to_remove .tolist ())
3361+
3362+
3363+ ######################################################################################################
3364+ def dilate_mm (
3365+ array : np .ndarray , affine : np .ndarray , shape : str = "cube" , dilation_mm : float = 1
3366+ ) -> np .ndarray :
3367+ """
3368+ Binarize a 3D array and dilate by a given distance in millimeters.
3369+
3370+ Parameters
3371+ ----------
3372+ array : 3D numpy array with values and zeros
3373+ affine : 4x4 affine matrix (voxel → mm)
3374+ shape : 'cube' for a box or 'sphere' for an ellipsoid structuring element
3375+ dilation_mm : dilation radius in millimeters
3376+
3377+ Returns
3378+ -------
3379+ dilated : binary 3D numpy array (bool)
3380+ """
3381+
3382+ binary_array = array != 0
3383+ voxel_sizes = get_voxel_size (affine )
3384+
3385+ # Per-axis radii in voxels
3386+ radii_vox = np .array ([dilation_mm / vs for vs in voxel_sizes ])
3387+ r = np .ceil (radii_vox ).astype (int )
3388+
3389+ if shape is None or shape == "cube" :
3390+ # Box: include every voxel within ±r[i] along each axis
3391+ structure = np .ones ([2 * ri + 1 for ri in r ], dtype = bool )
3392+
3393+ else :
3394+ # Ellipsoid: anisotropic sphere in mm space
3395+ xi = np .arange (- r [0 ], r [0 ] + 1 )
3396+ yi = np .arange (- r [1 ], r [1 ] + 1 )
3397+ zi = np .arange (- r [2 ], r [2 ] + 1 )
3398+ xx , yy , zz = np .meshgrid (xi , yi , zi , indexing = "ij" )
3399+ structure = (
3400+ (xx / radii_vox [0 ]) ** 2
3401+ + (yy / radii_vox [1 ]) ** 2
3402+ + (zz / radii_vox [2 ]) ** 2
3403+ ) <= 1.0
3404+
3405+ dilated = binary_dilation (binary_array , structure = structure )
3406+
3407+ return dilated
0 commit comments