Paper · 2018-06-18 · cs.CV
Assessing robustness of radiomic features by image perturbation
Ranks 18 perturbation chains (rotation, noise, translation, volume change, supervoxel contour randomisation) for radiomic feature robustness on CT of 31 NSCLC and 19 HNSCC patients, against test-retest ICC. Chain NTVC gave the fewest false-positive robust features: 3.3% (NSCLC), 10.0% (HNSCC).
RadiomicsIntraclass correlation coefficientData augmentationmedical-imagingradiomicsfeature-robustnessevaluation
AI-compiled by claude-haiku-5-5 on 2026-11-08, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Radiomics turns a medical scan into a long list of numbers that describe a tumour: its size, its brightness, how grainy its texture looks. A model built on these numbers only works on new scans if the numbers stay stable when the scan is taken slightly differently, for example with the patient lying a little off-centre, with a bit more image noise, or with the tumour outline drawn a little differently. A stable number is called robust.
The usual way to find stable numbers is to scan the same patient twice and see which numbers agree. That doubles the scanning, and it is often not done in routine care. The authors ask whether one scan is enough. They take that scan, make many altered copies of it by applying small, realistic changes (rotate, add noise, shift, grow or shrink the outline, and randomly redraw parts of the outline), and count how many numbers stay stable across the copies. They then check the results against the real two-scan data. The best combination of changes flags very few numbers as stable when they are not.
TL;DR
- Compares 18 chains of image perturbations with test-retest CT scans of two cohorts (31 NSCLC, 19 HNSCC) §2 §4.1.
- Robustness is the intraclass correlation coefficient ICC(1,1); a feature with ICC ≥ 0.90 counts as robust §4.5.
- The chain noise + translation + volume change + contour randomisation (NTVC) gave the fewest false-positive robust features: 3.3% (NSCLC), 10.0% (HNSCC), taking test-retest as the reference §2.3.
- The results are author-reported on small cohorts; the method is CT-only.
Problem
Radiomic models that use features which change with patient positioning, acquisition or segmentation generalise poorly §1. Test-retest imaging is the usual way to identify non-robust features, but it is rarely available for a given phenotype, and robustness does not transfer between phenotypes, modalities or computational settings (voxel size, discretisation) §1. The authors want to know whether perturbations of a single image can replace test-retest data for this purpose §1.
Method
Overview
For each CT image and its gross tumour volume (GTV) mask, the pipeline is:
- Convert the RTSTRUCT contour to a voxel mask §4.2.
- Apply a chain of optional perturbations: rotation, noise, translation, volume adaptation, contour randomisation. Chains are built from the five basic perturbations and every combination of their settings is generated §2 [supp. note 5].
- Resample to isotropic spacings of 1, 2, 3 and 4 mm, after a Gaussian low-pass filter before the down-sampling §4.2 [supp. note 2].
- Re-segment the intensity mask, exclude voxels more than three standard deviations from the ROI mean, and compute 4032 features per perturbed image §4.2 §4.4 [supp. note 3].
- For each feature, compute ICC(1,1) of test-retest values across patients, and the perturbation ICC of each chain separately for the test and the retest image, averaged §4.5 fig. 2.
- Call a feature robust if its ICC is at least 0.90 §4.5.
- Compare each perturbation chain with the test-retest result feature by feature: robust under both is a true positive, non-robust under both a true negative, robust only under perturbation a false positive, robust only under test-retest a false negative §2.3 fig. 4.
# Robustness pipeline for one patient [§2] [§4.2] [§4.5]
# Input: test_ct, retest_ct (3-D, HU); gtv_test, gtv_retest (3-D bool)
# Output: per feature: test-retest ICC, perturbation ICC per chain
def robustness_pipeline(test_ct, retest_ct, gtv_test, gtv_retest, chains):
# Test-retest reference, on unperturbed images [§2.1]
features_test = extract_features(test_ct, gtv_test)
features_retest = extract_features(retest_ct, gtv_retest)
icc_by_chain = {}
# Perturbed copies of each image, one set per chain [§4.3] [tab. 1]
for chain in chains:
perturbed_test = perturb_all(test_ct, gtv_test, chain)
perturbed_retest = perturb_all(retest_ct, gtv_retest, chain)
# Feature values of every perturbed copy [§4.4]
values_test = extract_each(perturbed_test)
values_retest = extract_each(perturbed_retest)
# Perturbation ICC: average of the two images [§4.5]
icc_by_chain[chain.name] = perturbation_icc(values_test, values_retest)
return features_test, features_retest, icc_by_chainImage processing order
The steps for one perturbed image, in the order the paper describes them, are §4.2 [supp. note 4]:
- Rotate the image and mask in the axial plane by θ (optional). The mask is thresholded only after interpolation.
- Add Gaussian noise (optional), with σ estimated once on the unperturbed image; round the result to integer HU.
- Translate by a sub-voxel shift η (optional), performed together with the tri-linear interpolation onto the new grid.
- Apply the anti-aliasing Gaussian before interpolation (see pseudocode below) and interpolate image and mask to the isotropic target spacing. Round intensities to the nearest integer HU and set the mask with a partial-volume threshold of 0.5 tab. 2.
- Change the mask volume by a fraction τ (optional) [supp. note 4].
- Randomise the contour with supervoxels (optional) [supp. note 4].
- The intensity mask is copied from the morphological mask and re-segmented to the cohort range; the morphological mask is kept as it is §4.2.
Perturbation definitions
- Rotation (R): in-plane rotation of image and mask about the z-axis, angle θ in [−13°, 13°] §4.3 [supp. note 5]. Image sampling is bi-linear and the image is rounded to integer HU afterwards [supp. note 4].
- Noise (N): Gaussian noise with σ equal to the noise estimated from the original image (method below) §4.3 [supp. note 4].
- Translation (T): shift of the voxel grid by a fraction η of the target grid spacing, along x, y and z; the three axes are permuted over the listed fractions §4.3 [supp. note 5].
- Volume adaptation (V): grow or shrink the mask to a target volume V_a = floor(V_0 (1 + τ)), τ in [−0.28, 0.28] §4.3 [supp. note 4].
- Contour randomisation (C): supervoxels (simple linear iterative clustering, SLIC) are selected by their overlap with the expert contour §4.3 [supp. note 4].
Chains
Eighteen chains were built: the five basic perturbations, and 13 combinations of them. Every permutation of the settings of a chain was generated. Some combinations (for example rotation + translation + volume adaptation) were not tested because the number of permutations was too large. Noise alone and contour randomisation alone were repeated 30 times tab. 1 [supp. note 5].
Per CT image the sum of the 18 counts is 565 perturbed images (sum of the column in tab. 1). The paper applies perturbations to both the test and the retest image §4.5; this total is computed from the table and is not stated by the paper.
Features
- Base set: 182 features defined in IBSI §4.4 [supp. note 3]. 118 of them need discretisation, done with a fixed bin number (8, 16, 32, 64) or a fixed bin width (6, 12, 18, 24 HU), giving 8 settings tab. S2.
- Each feature is computed at 1, 2, 3 and 4 mm, so 1008 × 4 = 4032 features per image tab. S2 §4.4.
Robustness analysis
ICC(1,1) of Shrout and Fleiss is used §4.5. The paper does not write the
formula or the layout of the perturbation ICC (which factor is the subject
and which the rater) [pitfall below]. The R package psych was the basis of
the code §4.5.
Cohorts
- NSCLC: 31 patients from a public archive; two CT scans within 15 minutes, same scanner and protocol §4.1 [supp. note 1].
- HNSCC: 19 patients in-house; two CT scans within 4 days, using two different protocols for PET attenuation correction §4.1 [supp. note 1].
- GTV delineated by radio-oncologists in a treatment planning system §4.1.
Hyperparameters
| Name | Value | Where | Notes |
|---|---|---|---|
| feature voxel spacings | 1, 2, 3, 4 mm isotropic | §4.4 tab. 2 | |
| anti-aliasing β (filter) | 0.93 in the table; sweep 0.50 to 0.97, plus none | tab. 2 [supp. note 2] fig. S3 | text calls 0.93 to 0.90 a compromise |
| interpolation | tri-linear (image and ROI) | tab. 2 | rotation uses bi-linear [supp. note 4] |
| intensity rounding | nearest integer HU | tab. 2 | |
| ROI partial-volume threshold | 0.5 | tab. 2 | |
| re-segmentation range, NSCLC | [−300, 200] HU | tab. 2 | |
| re-segmentation range, HNSCC | [−150, 180] HU | tab. 2 | |
| re-segmentation outliers | exclude beyond ±3 σ of ROI voxels | tab. 2 | |
| discretisation, bin number | 8, 16, 32, 64 | tab. 2 [supp. note 3] | |
| discretisation, bin width | 6, 12, 18, 24 HU | tab. 2 [supp. note 3] | |
| fixed bin-width lower edge | −300 HU (NSCLC), −150 HU (HNSCC) | [supp. note 3] | |
| rotation θ | −13° to 13° in 1° steps (27 values) | [supp. note 5] | chains use −6, −2, 2, 6 or −10 to 10 |
| noise | σ from the noise estimate; 1 or 30 repetitions | [supp. note 4] [supp. note 5] | |
| translation η | {0.0, 0.333, 0.667} (T, TC); {0.25, 0.75} (others) | [supp. note 5] | fraction of spacing, per axis |
| volume τ | −0.28 to 0.28 in 0.02 steps (V); {−0.2, −0.1, 0, 0.1, 0.2} (chains) | [supp. note 5] | |
| contour randomisation repetitions | 1 (combined chains) or 6 (VC) | [supp. note 5] | |
| SLIC compactness | 0.05 | [supp. note 4] | the symbol β is reused here |
| supervoxel size | 0.25 to 1.5 cm³; target 0.5 cm³ per supervoxel | [supp. note 4] | |
| contour crop | 25 mm around the ROI bounding box | [supp. note 4] | |
| intensity window for SLIC | re-segmentation range extended by 10% on each side | [supp. note 4] | |
| supervoxel selection | always if overlap ≥ 0.90; never if < 0.20; random above | [supp. note 4] | |
| morphological element | Manhattan distance 1 (6-neighbour cross) | [supp. note 4] | |
| SLIC pre-smoothing σ | equal to the voxel spacing (mm) | [supp. note 4] | |
| noise constant | 0.6754 (median-to-σ) | [supp. note 4] | coiflet-1 high-pass |
| robustness threshold | ICC(1,1) ≥ 0.90 | §4.5 | |
| intensity-volume percentiles | 10, 25, 50, 75, 90 | [supp. note 3] | |
| Marching Cubes iso-level | 0.5 | [supp. note 3] | |
| GLCM / GLRLM directions | 13, averaged after per-matrix features | [supp. note 3] | GLRLM δ not stated |
| GLCM / NGTDM / NGLDM distance | Chebyshev δ = 1 | [supp. note 3] | |
| NGLDM coarseness α | 0 | [supp. note 3] | |
| Moran's I / Geary's C | 100 random voxels; repeat until SE < 0.002 | [supp. note 3] | stochastic |
| software | Python 3.6.1, NumPy 1.13.3, SciPy 0.19.1, scikit-image 0.13.1, PyWavelets 0.5.2, R 3.4.2 | [supp. note 4] §4.5 |
Results
Author-reported. Setup: CT, NSCLC (31) and HNSCC (19) test-retest cohorts, 4032 IBSI-based features, ICC(1,1) with a robustness threshold of 0.90 §2 §4. Baseline is test-retest robustness in the same cohort.
| Setting | Metric | Value | Source |
|---|---|---|---|
| test-retest, NSCLC | robust features | 73.5% (2963 of 4032) | §2.1 |
| test-retest, HNSCC | robust features | 34.0% (1369 of 4032) | §2.1 |
| features robust in both cohorts | count | 1116 | §2.1 |
| noise only (N), NSCLC / HNSCC | robust features | 96.6% / 99.3% | §2.2 |
| lowest robust, NSCLC | TVC 43.6%; NTVC 45.1%; RNVC 45.9%; RVC 46.0% | §2.2 | |
| lowest robust, HNSCC | NTVC 30.7%; TVC 31.2%; RNVC 31.6%; RVC and VC 32.3% | §2.2 | |
| false positives, NSCLC | RC 2.6%; RVC 3.0%; RNVC 3.1%; NTVC 3.3% | §2.3 | |
| false positives, HNSCC | NTVC 10.0%; RNVC 10.4%; TVC 10.6%; VC 10.7%; RVC 11.1%; RC 24.0% | §2.3 | |
| mean false positives, all chains | NSCLC 7.8%; HNSCC 30.3% | §2.3 | |
| CI width, test-retest ICC | NSCLC 0.12; HNSCC 0.35 | §3 | |
| CI width, NTVC perturbation ICC | NSCLC 0.11 (both CTs); HNSCC 0.18 (CT1), 0.17 (CT2) | §3 | |
| anti-aliasing β, NSCLC | β = 0.97 raises robust share from 59.0% to 75.9% | [supp. note 2] fig. S3 | |
| anti-aliasing β, HNSCC | β = 0.93: 34.0% robust, median ICC 0.85, median CI width 0.29; β = 0.90: 43.0%, 0.88, 0.23 | [supp. note 2] |
Other reported results: the perturbation chains' robust fraction, and false negatives, are in fig. 3 and fig. 4; ICC distributions and CI widths are in fig. S4 and fig. S5; the test of ICC bias between test and retest is in [supp. note 6] fig. S6. The paper concludes that chained perturbations of intensity and segmentation can stand in for test-retest imaging. NTVC gave a low false-positive count in both cohorts, though RC was lowest in NSCLC §2.3 §3.
Algorithms
Anti-aliasing σ [supp. note 2, the only numbered equation, eq. 1]:
# Gaussian pre-filter width before down-sampling [supp. note 2, eq. 1]
# Input: beta (0 < beta <= 1), spacing_orig and spacing_target (mm)
# Output: sigma (see Pitfalls for units)
def antialias_sigma(beta, spacing_orig, spacing_target):
# Nyquist frequency of the target grid [supp. note 2]
nyquist = spacing_orig / (2 * spacing_target)
# Solve exp(-nyquist^2 * sigma^2 / 2) = beta for sigma [supp. note 2]
sigma = sqrt(-2 * log(beta)) / nyquist
return sigmaNoise level and noise addition [supp. note 4, "Noise"]:
# Noise level of a CT image [supp. note 4, "Noise"]
# Input: image (3-D, HU), estimated once on the unperturbed image
# Output: sigma_noise (HU)
def estimate_noise(image):
# High-pass with coiflet-1 along x and y; how the two results are
# combined is not stated [supp. note 4]
diff = highpass_x_then_y(image)
# Median absolute value scaled to a standard deviation [supp. note 4]
return median(abs(diff)) / 0.6754
# Add noise after rotation, then round to integer HU [supp. note 4]
def add_noise(image, sigma_noise):
noisy = image + normal(0, sigma_noise, image.shape)
return round(noisy)Volume adaptation [supp. note 4, steps 1 to 6]:
# Grow or shrink a binary ROI to a target volume [supp. note 4]
# Input: roi (3-D bool), tau (float, fraction)
# Output: adapted roi (3-D bool)
def adapt_volume(roi, tau):
# tau == 0 is listed in the settings but its handling is not stated;
# returning the input is an assumption [supp. note 5]
if tau == 0:
return roi.copy()
# Target volume, rounded down [supp. note 4, step 2]
volume_0 = count(roi)
volume_target = floor(volume_0 * (1 + tau))
# Manhattan-distance-1 structuring element [supp. note 4, step 3]
element = cross_element_3d()
# Last mask that did not overshoot the target [supp. note 4, step 4]
previous = roi.copy()
volume_previous = volume_0
while True:
# Dilate for growth, erode for shrinkage [supp. note 4, step 5a-b]
if tau > 0:
current = dilate(previous, element)
else:
current = erode(previous, element)
volume_current = count(current)
# Stop when empty, or when the target is overshot [step 5d-f]
if volume_current == 0:
break
if tau > 0 and volume_current > volume_target:
break
if tau < 0 and volume_current < volume_target:
break
previous = current
volume_previous = volume_current
# Fix the remaining voxels from the rim [supp. note 4, step 6]
if volume_previous != volume_target:
# Rim between the overshoot mask and the last good mask [step 6b]
rim = logical_xor(current, previous)
# Number of voxels still to add or remove [step 6a]
number = abs(volume_target - volume_previous)
# Random voxels from the rim, without replacement [step 6c]
chosen = choose_without_replacement(rim, number)
# Add for growth, remove for shrinkage [steps 6d-e]
if tau > 0:
previous[chosen] = True
else:
previous[chosen] = False
return previousContour randomisation [supp. note 4, steps 1 to 7]:
# Supervoxel-based contour randomisation [supp. note 4]
# Input: image (3-D, HU), roi (3-D bool, expert contour),
# spacing_mm (1, 2, 3 or 4), g_low, g_high (re-segmentation range)
# Output: randomised roi (3-D bool)
def randomise_contour(image, roi, spacing_mm, g_low, g_high):
# Crop to 25 mm around the ROI bounding box [supp. note 4, step 1]
image_c, roi_c = crop_around(image, roi, margin_mm=25)
# Widen the range by 10% on each side and clip [supp. note 4, step 2a]
span = g_high - g_low
r_low = g_low - 0.1 * span
r_high = g_high + 0.1 * span
clipped = clip(image_c, r_low, r_high)
# Map to the unit interval [supp. note 4, step 2b]
scaled = (clipped - r_low) / (r_high - r_low)
# Estimated supervoxel count, about 0.5 cm^3 each [supp. note 4, step 3]
voxel_cm3 = prod(voxel_spacing_mm) / 1000
count_est = ceil(size(image_c) * voxel_cm3 / 0.5)
# Gaussian smoothing with sigma = voxel spacing [supp. note 4, step 4]
smoothed = gaussian_filter(scaled, sigma=spacing_mm)
# SLIC: compactness 0.05, supervoxel size 0.25 to 1.5 cm^3 [step 5]
labels = slic(smoothed, count_est, compactness=0.05)
# Overlap of each supervoxel with the expert ROI [supp. note 4, step 6]
overlap = {}
for k in unique(labels):
# Intersection over supervoxel size; see Pitfalls [step 6c]
overlap[k] = count((labels == k) & roi_c) / count(labels == k)
# Highest-overlap supervoxel is always kept [supp. note 4, step 7a]
best = argmax(overlap)
selected = []
for k in overlap:
if k == best or overlap[k] >= 0.90:
selected.append(k)
elif overlap[k] < 0.20:
continue
# Keep with probability equal to the overlap [step 7d]
elif random_uniform(0, 1) <= overlap[k]:
selected.append(k)
# Union of selected supervoxels, then morphological closing [step 7e]
new_roi = binary_closing(union_of(labels, selected), cross_element_3d())
return new_roiRobustness of one feature §4.5:
# Robust flag and perturbation ICC [§4.5] [fig. 2]
# Input: feature values (patients x repeats) for test or retest images
# Output: ICC(1,1) and robust flag
def is_robust(icc):
# Robust when ICC(1,1) reaches the threshold [§4.5]
return icc >= 0.90
def perturbation_icc(values_test, values_retest):
# ICC computed separately on the perturbations of each image [§4.5]
icc_test = icc_1_1(values_test)
icc_retest = icc_1_1(values_retest)
# Average of the two [§4.5] [fig. 2]
return (icc_test + icc_retest) / 2Paper vs code
No official code.
Searches for this card: the paper contains no code link (its only URLs are
for PyWavelets and Python) §4.2 [supp. note 4]; no project page exists. A
GitHub repository search for "perturbation radiomic" returned only
third-party repositories, which are not used. Searches of the authors'
GitHub user account and of the OncoRay organisation found no repository for
this method. The OncoRay organisation's oncoray/mirp package (EUPL-1.2) is
the lab's IBSI-compliant radiomics package; its README does not mention
perturbations or robustness, and the paper does not link it, so it is not
used here.
Pitfalls
- Overlap formula is printed inverted. The paper writes the supervoxel overlap as voxels of the supervoxel divided by voxels of the intersection [supp. note 4, step 6c], which is ≥ 1, but states 0 ≤ η ≤ 1. The pseudocode uses intersection divided by supervoxel size. Confirm this.
- Anti-aliasing σ is printed in a form that is not real for β < 1. The paper's last line, σ = −2 (d2/d1) √(2 ln β) [supp. note 2], is negative under the root. Solving its own equation gives σ = √(−2 ln β) / ω_N with ω_N = d1 / (2 d2) [supp. note 2, eq. 1]. The units are not stated; the derivation uses the original voxel grid, so σ is taken in original-voxel units (inferred from the derivation).
- β is used twice. The anti-aliasing filter parameter (0.93 in tab. 2) and the SLIC compactness (0.05) [supp. note 4] are different quantities.
- τ = 0 is not handled. Several chains list τ = 0.0 [supp. note 5]. The described loop neither dilates nor erodes at τ = 0 and has no break condition for it. The pseudocode returns the input mask; the paper does not say this.
- Volume adaptation edge cases. The paper does not say what happens when the rim holds fewer voxels than the number still to change, or whether the final step uses the overshoot mask or the last valid mask (the pseudocode follows the text in using both).
- Noise estimate. The paper gives the coiflet-1 high-pass and the 0.6754 constant, but not how the x and y responses are combined (sum, product, or maximum). The noise σ is taken from the original image before any step [supp. note 4].
- Translation units. η is a fraction of the target grid spacing, applied per axis, so each axis is permuted independently [supp. note 5] §4.3. The paper uses "isotropic voxel spacing" in the same sentence; the target grid is the reading the paper's interpolation description supports.
- Mask thresholding order. Rotation of the mask is not thresholded until after interpolation [supp. note 4]. A naive implementation that thresholds earlier will give different masks.
- Random seeds. Noise, supervoxel selection, rim selection and the Moran's I / Geary's C approximation all use random draws; no seed is stated. Exact reproduction needs the seeds.
- Moran's I / Geary's C are approximated from 100 random voxels, repeated until the standard error falls below 0.002 [supp. note 3]. The stopping rule is stochastic and not otherwise specified.
- ICC design is not stated. The paper cites Shrout and Fleiss and says the psych package code was adapted §4.5, but does not say which factor is the target and which is the rater for the perturbation ICC, or whether the single-measure form is used. Read the code if you need this; the code is not public (see Paper vs code).
- Test-retest is itself confounded. HNSCC retests used two different PET protocols, with exposure about four times higher on average and two reconstruction kernels; NSCLC used one protocol §3 [supp. note 1]. The paper suggests this may explain the higher false-negative rate in NSCLC.
- Small cohorts. 31 and 19 patients. The CI widths are large (test-retest HNSCC average 0.35 §3). The headline percentages can move a lot with small changes in the reference.
- Robust fraction depends on the spacing and β choices. The main results use β = 0.93 tab. 2. The β sweep [supp. note 2] shows that HNSCC robustness changes strongly with β; choose and report β explicitly.
- Chain counts. Table 1 counts agree with the setting lists in [supp. note 5] for all 18 chains (for example, RV: 6 rotation angles × 5 volume fractions = 30). The text's "NTCV" in §3 is a typo for NTVC.
- Function names. The paper names
skimage.segmentation.slic_superpixelsfor SLIC, with scikit-image 0.13.1 [supp. note 4]. Check the function name and call signature (supervoxel count, size bounds) in the scikit-image version you use; the paper's name is not confirmed here. - Feature averaging. For GLCM and GLRLM, features are computed per direction and averaged, not computed on an averaged matrix [supp. note 3].
- Perturbation counts are not uniform. Chain sizes range from 27 to 40 images tab. 1; the paper keeps this to limit sample-size effects on ICC. Compare chains only within the same cohort.
Lineage
- Builds on: IBSI, the Image Biomarker Standardisation Initiative, for feature definitions and image processing §4.2 §4.4 (not a card here).
- Builds on: Chang's noise estimate and SLIC supervoxels [supp. note 4] (not cards here).
- Compared against: test-retest ICC from the same two cohorts §2.
- Superseded by: not stated in this paper.
Source and license
Adapted from “Assessing robustness of radiomic features by image perturbation” by Alex Zwanenburg, Stefan Leger, Linda Agolli, Karoline Pilz, Esther G. C. Troost, Christian Richter, Steffen Löck (arXiv:1806.06719), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors.