Source code for numpyradiomics.dro

import numpy as np

def _create_grid(radii_mm, spacing, padding_mm):
    """
    Helper to create centered physical coordinate grids.
    Ensures 0.0 is exactly at the center of the array.
    """
    radii_mm = np.array(radii_mm, dtype=np.float64)
    spacing = np.array(spacing, dtype=np.float64)
    
    # Field of View limits (radii + padding)
    limits = radii_mm + padding_mm
    
    # Create coordinate arrays (centered at 0)
    # Using arange ensures steps are exactly 'spacing'
    z_mm = np.arange(-limits[0], limits[0], spacing[0])
    y_mm = np.arange(-limits[1], limits[1], spacing[1])
    x_mm = np.arange(-limits[2], limits[2], spacing[2])
    
    # Create 3D Meshgrid
    # indexing='ij' ensures Matrix/Image ordering (Z, Y, X)
    Z, Y, X = np.meshgrid(z_mm, y_mm, x_mm, indexing='ij')
    
    return Z, Y, X

[docs] def cuboid(radii_mm=(30.0, 10.0, 2.5), spacing=(1.0, 1.0, 1.0), padding_mm=5.0): """ Creates a binary mask of a solid cuboid. Args: radii_mm (tuple): Physical half-lengths (Rx, Ry, Rz) in mm. Total size will be 2*Rx, 2*Ry, 2*Rz. spacing (tuple): Voxel spacing (Sz, Sy, Sx) in mm. padding_mm (float): Padding around the object in mm. Returns: image (np.ndarray): Dummy intensity image (value 100 inside). mask (np.ndarray): Binary mask (uint8). Example: >>> from numpyradiomics.dro import cuboid >>> # Create a 10x10x10mm cube (5mm radii) with 1mm spacing >>> image, mask = cuboid(radii_mm=(5, 5, 5), spacing=(1.0, 1.0, 1.0)) >>> print(mask.shape) (20, 20, 20) """ Z, Y, X = _create_grid(radii_mm, spacing, padding_mm) # Cuboid Logic: Intersection of linear bounds # Using half-open intervals [-R, R) prevents "fencepost" errors where # centered grids include one too many pixels for even integer dimensions. mask = ( (Z >= -radii_mm[0]) & (Z < radii_mm[0]) & (Y >= -radii_mm[1]) & (Y < radii_mm[1]) & (X >= -radii_mm[2]) & (X < radii_mm[2]) ).astype(np.uint8) image = mask.astype(np.float32) * 100.0 return image, mask
[docs] def ellipsoid(radii_mm=(30.0, 10.0, 2.5), spacing=(1.0, 1.0, 1.0), padding_mm=5.0): """ Creates a binary mask of a solid ellipsoid. Args: radii_mm (tuple): Physical radii (Rx, Ry, Rz) in mm. spacing (tuple): Voxel spacing (Sz, Sy, Sx) in mm. padding_mm (float): Padding around the object in mm. Returns: image (np.ndarray): Dummy intensity image (value 100 inside). mask (np.ndarray): Binary mask (uint8). Example: >>> from numpyradiomics.dro import ellipsoid >>> # Create an isotropic sphere (radius 10mm) with 0.5mm spacing >>> image, mask = ellipsoid(radii_mm=(10, 10, 10), spacing=(0.5, 0.5, 0.5)) >>> print(mask.sum()) # Number of voxels in the sphere 33489 """ Z, Y, X = _create_grid(radii_mm, spacing, padding_mm) # Ellipsoid Logic: Sum of squared normalized distances <= 1 normalized_dist_sq = ( (Z / radii_mm[0])**2 + (Y / radii_mm[1])**2 + (X / radii_mm[2])**2 ) mask = (normalized_dist_sq <= 1.0).astype(np.uint8) image = mask.astype(np.float32) * 100.0 return image, mask
[docs] def noisy_ellipsoid(radii_mm=(30.0, 10.0, 2.5), spacing=(1.0, 1.0, 1.0), padding_mm=5.0, intensity_range=(0, 100)): """ Creates a binary mask of an ellipsoid filled with random noise. Useful for testing texture features. Args: radii_mm (tuple): Physical radii (Rx, Ry, Rz) in mm. spacing (tuple): Voxel spacing (Sz, Sy, Sx) in mm. padding_mm (float): Padding around the object in mm. intensity_range (tuple): (min, max) intensity values for noise. Returns: image (np.ndarray): Noisy image (float32). mask (np.ndarray): Binary mask (uint8). Example: >>> from numpyradiomics.dro import noisy_ellipsoid >>> import numpy as np >>> # Create a noisy ellipsoid for texture analysis >>> img, mask = noisy_ellipsoid(radii_mm=(20, 10, 5)) >>> print(f"Mean Intensity in ROI: {np.mean(img[mask==1]):.2f}") Mean Intensity in ROI: 49.87 """ Z, Y, X = _create_grid(radii_mm, spacing, padding_mm) # Ellipsoid Logic normalized_dist_sq = ( (Z / radii_mm[0])**2 + (Y / radii_mm[1])**2 + (X / radii_mm[2])**2 ) mask = (normalized_dist_sq <= 1.0).astype(np.uint8) # Generate Noise np.random.seed(42) image = np.random.uniform(intensity_range[0], intensity_range[1], mask.shape).astype(np.float32) # Apply Mask (Background = 0) image[mask == 0] = 0 return image, mask