Source code for numpyradiomics.mod_firstorder

import numpy as np
from scipy.stats import skew, kurtosis


[docs] def firstorder(image, mask, voxelVolume=1, binWidth=25, voxelArrayShift=0, extend=True): """ Compute first-order (intensity-based) statistics for a given image and mask, replicating Pyradiomics first-order features. Args: image (np.ndarray): 3D image array containing voxel intensities. mask (np.ndarray): 3D mask array (same shape as image), where non-zero values indicate the ROI. voxelVolume (float, optional): Volume of a single voxel (used to scale TotalEnergy). Default is 1. binWidth (float, optional): Width of bins for histogram-based features (entropy, uniformity). Default is 25. voxelArrayShift (float, optional): Value to add to intensities before computing Energy/RMS. Default is 0. extend (bool, optional): If True, returns extended features not strictly in the PyRadiomics standard set (05/95 Percentile, CoeffOfVar, Heterogeneity). Default is True. Returns: dict: Dictionary containing first-order feature names and their computed values. Base Features: - **Energy**: Sum of squared voxel intensities. - **TotalEnergy**: Energy scaled by voxelVolume. - **Entropy**: Histogram-based Shannon entropy. - **Minimum**: Minimum intensity. - **Maximum**: Maximum intensity. - **Mean**: Mean intensity. - **Median**: Median intensity. - **Range**: Maximum - Minimum. - **Variance**: Variance of intensities. - **StandardDeviation**: Standard deviation of intensities. - **Skewness**: Measure of asymmetry of the distribution. - **Kurtosis**: Measure of the "tailedness" of the distribution. - **MeanAbsoluteDeviation**: Mean distance of all intensity values from the Mean. - **RobustMeanAbsoluteDeviation**: Mean distance of intensities (10-90th percentile) from the Mean. - **RootMeanSquared**: Square root of the mean of all the squared intensity values. - **10Percentile**: 10th percentile of intensities. - **90Percentile**: 90th percentile of intensities. - **InterquartileRange**: 75th percentile - 25th percentile. - **Uniformity**: Sum of squared histogram probabilities. Extended Features (if ``extend=True``): - **05Percentile**: 5th percentile of intensities. - **95Percentile**: 95th percentile of intensities. - **CoefficientOfVariation**: Standard Deviation / Mean. - **Heterogeneity**: Interquartile Range / Median. Example: >>> import numpyradiomics as npr >>> # Generate a noisy ellipsoid (simulation of a tumor) >>> img, mask = npr.dro.noisy_ellipsoid(radii_mm=(20, 10, 5), intensity_range=(20, 80)) >>> >>> # Compute first order statistics >>> feats = npr.firstorder(img, mask) >>> >>> print(f"Mean: {feats['Mean']:.2f}") Mean: 50.02 >>> print(f"Range: {feats['Range']:.2f}") Range: 59.91 """ # Extract the voxels inside the mask roi = image[mask > 0].astype(np.float64) if roi.size == 0: raise ValueError("The mask does not contain any voxels.") # Basic statistics minimum = np.min(roi) maximum = np.max(roi) mean = np.mean(roi) median = np.median(roi) variance = np.var(roi) sdev = np.std(roi) rms = np.sqrt(np.mean((roi + voxelArrayShift)**2)) perc05 = np.percentile(roi, 5) perc10 = np.percentile(roi, 10) perc90 = np.percentile(roi, 90) perc95 = np.percentile(roi, 95) iqr = np.percentile(roi, 75) - np.percentile(roi, 25) mad = np.mean(np.abs(roi - mean)) # Mean absolute deviation range_val = maximum - minimum skewness = skew(roi) kurt = kurtosis(roi, fisher=False) energy = np.sum((roi + voxelArrayShift)**2) total_energy = voxelVolume * energy # same as energy in pyradiomics coefficient_of_variation = sdev / mean heterogeneity = iqr / median # Robust MAD: only voxels between 10th and 90th percentile roi_robust = roi[(roi >= perc10) & (roi <= perc90)] rmad = np.mean(np.abs(roi_robust - np.mean(roi_robust))) # Histogram-based features discretized = np.floor((roi - minimum) / binWidth).astype(np.int32) _, counts = np.unique(discretized, return_counts=True) probs = counts.astype(np.float64) / counts.sum() # Entropy: -sum(p * log2(p)) entropy = -np.sum(probs * np.log2(probs + 2e-16)) # Uniformity: sum(p^2) uniformity = np.sum(probs**2) features = { "Energy": energy, "TotalEnergy": total_energy, "Entropy": entropy, "Minimum": minimum, "Maximum": maximum, "10Percentile": perc10, "90Percentile": perc90, "Mean": mean, "Median": median, "InterquartileRange": iqr, "Range": range_val, "MeanAbsoluteDeviation": mad, "RobustMeanAbsoluteDeviation": rmad, "RootMeanSquared": rms, "StandardDeviation": sdev, "Skewness": skewness, "Kurtosis": kurt, "Variance": variance, "Uniformity": uniformity, } extended_features = { "05Percentile": perc05, "95Percentile": perc95, "CoefficientOfVariation": coefficient_of_variation, "Heterogeneity": heterogeneity, } if extend: features = features | extended_features return features
[docs] def firstorder_units(intensity_unit='HU', voxel_unit='mm'): """ Returns units of returned first-order metrics. Args: intensity_unit (str, optional): Units of signal intensity (e.g., 'HU', 'SUV'). Default is 'HU'. voxel_unit (str, optional): Unit of voxel length (e.g., 'mm'). Default is 'mm'. Returns: dict: Dictionary mapping feature names to their units. Example: >>> from numpyradiomics import firstorder_units >>> units = firstorder_units(intensity_unit='SUV') >>> print(units['Energy']) SUV^2 """ voxelVolume = f"{voxel_unit}^3" unit = intensity_unit unit_sq = '' if unit=='' else f"{unit}^2" if voxelVolume=='': unit_sq_vol = unit_sq elif unit=='': unit_sq_vol = voxelVolume else: unit_sq_vol = f"{unit_sq}*{voxelVolume}" return { "Energy": unit_sq, "TotalEnergy": unit_sq_vol, "Entropy": '', "Minimum": unit, "Maximum": unit, "10Percentile": unit, "90Percentile": unit, "Mean": unit, "Median": unit, "InterquartileRange": unit, "Range": unit, "MeanAbsoluteDeviation": unit, "RobustMeanAbsoluteDeviation": unit, "RootMeanSquared": unit, "StandardDeviation": unit, "Skewness": '', "Kurtosis": '', "Variance": unit_sq, "Uniformity": '', # Extended "05Percentile": unit, "95Percentile": unit, "CoefficientOfVariation": '', "Heterogeneity": '', }