RTpipeline Output Format - Quick Reference¶
Quick reference for the most common tasks.
π Where to Start¶
All aggregated results are in: {output_dir}/_RESULTS/
_RESULTS/
βββ dvh_metrics.xlsx β All dose-volume metrics
βββ radiomics_ct.xlsx β All CT radiomic features
βββ radiomics_mr.xlsx β All MR radiomic features
βββ radiomics_robustness_summary.xlsx β Cohort stability summary (if enabled)
βββ radiomics_robustness_summary_raw_values.parquet β Raw perturbation rows
βββ case_metadata.xlsx β Patient/treatment info
βββ qc_reports.xlsx β Quality control summary
π₯ Most Common Tasks¶
Load DVH Metrics¶
import pandas as pd
dvh = pd.read_excel("_RESULTS/dvh_metrics.xlsx")
# Filter by structure
bladder = dvh[dvh['Structure'].str.contains('bladder', case=False)]
# Get specific metrics
v50_values = bladder['V50Gy'] # Volume receiving β₯50 Gy
dmean_values = bladder['Dmean_Gy'] # Mean dose
Load Radiomics Features¶
radiomics = pd.read_excel("_RESULTS/radiomics_ct.xlsx")
# Get only original (most stable) features
original_features = radiomics.filter(regex='^original_')
# Get shape features
shape_features = radiomics.filter(regex='shape')
# Filter by structure
ptv = radiomics[radiomics['Structure'].str.contains('ptv', case=False)]
Load Robustness Summary¶
robustness = pd.read_excel(
"_RESULTS/radiomics_robustness_summary.xlsx",
sheet_name="robust_features"
)
# Select the exact ROI/source used by the downstream model; classifications are
# intentionally not pooled across heterogeneous structures or sources.
selected = robustness[
(robustness["structure"] == "GTV_primary")
& (robustness["segmentation_source"] == "Custom")
]
robust_feature_names = selected["feature_name"].tolist()
Compare Auto vs Manual Segmentation¶
# Load DVH
dvh = pd.read_excel("_RESULTS/dvh_metrics.xlsx")
# Separate by source
manual = dvh[dvh['ROI_Type'] == 'MANUAL']
auto = dvh[dvh['ROI_Type'] == 'TOTALSEGMENTATOR']
# Merge and compare
comparison = manual.merge(
auto,
on=['PatientID', 'CourseID', 'Structure'],
suffixes=('_manual', '_auto')
)
# Volume difference
comparison['volume_diff_%'] = 100 * (
comparison['ROI_Volume_cc_auto'] - comparison['ROI_Volume_cc_manual']
) / comparison['ROI_Volume_cc_manual']
π Key Column Reference¶
DVH Metrics (dvh_metrics.xlsx)¶
| Column | Description | Example Use |
|---|---|---|
Dmean_Gy |
Mean dose | Parallel organ toxicity (lung, kidney) |
Dmax_Gy |
Maximum dose | Serial organ toxicity (spinal cord) |
D2ccGy |
Dose received by the hottest 2 cmΒ³, in Gy, bounded by Dmin and Dmax | Bladder, rectum and bowel dose reporting |
D2cc_status |
available, roi_below_2cc, or the dose-grid coverage status when coverage is incomplete |
Identify computable whole-ROI values |
D0.03ccGy, D0.03cc_status |
Hottest 0.03 cmΒ³ dose and availability status | Small-volume dose reporting |
D50% |
Median dose | Homogeneity |
D95% |
Dose to 95% of volume | Target coverage |
V95% |
Volume receiving β₯95% Rx | PTV coverage |
V50Gy |
Volume receiving β₯50 Gy | OAR sparing |
ROI_Volume_cc |
Structure volume | Normalization |
dose_summary_basis identifies RTSTRUCT mean and maximum as binned or unbinned_subcentigray. The latter uses supported unbinned samples from the same raster mask and working dose grid when the histogram has at most one bin or its maximum is zero. Positive doses below 0.01 Gy remain positive. Dose-volume quantiles and DVH curves remain histogram-derived. NIfTI-mask summaries already use unbinned samples and leave this field empty.
The aggregate identifies this disposition contract with
aggregate_schema_version = rtpipeline-dvh-aggregate-v3. Outside-grid rows remain
as nonmeasurements. All producer-defined dose-derived metrics, including
D90Gy, D2ccGy and D0.03ccGy, are null;
dose_metric_usable_for_dose_response is false. Volume (cmΒ³) is retained.
Their status depends on the evidence published by the producer:
| Published evidence | Aggregate dose_metric_status and reason |
|---|---|
Both dose_grid_coverage_status and dose_metric_status are outside_grid |
Preserves outside_grid and the producer's reason, normally βWhole-ROI metrics require complete independent dose-grid support.β |
zero_dose_status is zero_dose_outside_dose_grid without that paired declaration |
Uses not_measurable_outside_dose_grid: βDose-derived metrics are null because the ROI does not intersect the selected RTDOSE grid.β |
An automatic ROI containing only closed items with fewer than three distinct points is nonmeasurable_auto_sliver, with row_status = not_computed and no DVH calculation. AutoRTS requires an origin sidecar matching the RTSTRUCT file hash; selected custom model outputs record their producer source and file hash. dvh_nonmeasurement_evidence stores contour count, distinct-point counts, source and origin evidence. This class is expected nonmeasurement. Manual or unverified area-less geometry is degenerate_contours. Valid polygons retain their coverage result when stray area-less items coexist.
At exactly 2 cmΒ³, D2ccGy equals Dmin. Below 2 cmΒ³, it is empty with D2cc_status = "roi_below_2cc". Incomplete coverage leaves the whole-ROI dose empty and propagates the coverage status. Axial RTSTRUCT, rotated-grid and NIfTI-mask measurements use linear cumulative-DVH interpolation.
Radiomics Features (radiomics_ct.xlsx)¶
| Feature Prefix | Type | Description |
|---|---|---|
original_shape_* |
Geometry | Volume, sphericity, elongation |
original_firstorder_* |
Intensity | Mean, std, entropy, skewness |
original_glcm_* |
Texture | Contrast, homogeneity, correlation |
original_glrlm_* |
Texture | Run length patterns |
log-sigma-* |
Filtered | Multi-scale edge detection |
wavelet-* |
Filtered | Frequency decomposition |
Recommendation: Start with original_* features (most stable)
β οΈ Quality Control Quick Check¶
qc = pd.read_excel("_RESULTS/qc_reports.xlsx")
# Check for failures
failures = qc[qc['Overall_Status'] == 'FAIL']
print(f"{len(failures)} cases failed QC")
# Check for cropping issues
cropped = qc[qc['Structures_Fully_Cropped'].notna()]
print(f"{len(cropped)} cases have fully cropped structures")
# Check frame-of-reference issues
for_issues = qc[qc['Frame_Of_Reference_Consistent'] == False]
print(f"{len(for_issues)} cases have FOR mismatches")
π― Feature Selection Tips¶
Remove Correlated Features (r > 0.95)¶
import numpy as np
def remove_correlated(df, threshold=0.95):
corr_matrix = df.corr().abs()
upper = corr_matrix.where(
np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)
)
to_drop = [col for col in upper.columns if any(upper[col] > threshold)]
return df.drop(columns=to_drop)
# Apply
features_clean = remove_correlated(original_features)
Filter by Stability (Use Tier 1 Features)¶
# Tier 1 features (ICC >0.9, most reproducible)
tier1_features = [
col for col in radiomics.columns
if any(x in col for x in [
'original_shape_',
'original_firstorder_Mean',
'original_firstorder_Median',
'original_glcm_Energy',
'original_glcm_Homogeneity'
])
]
stable_features = radiomics[tier1_features]
π¦ File Locations¶
For a specific patient/course:¶
{output_dir}/{PatientID}/{CourseID}/
βββ DICOM/CT/ # Original DICOM
βββ NIFTI/ct.nii.gz # CT volume
βββ Segmentation_TotalSegmentator/ # Auto-segmented organs
β βββ bladder--ct.nii.gz
β βββ rectum--ct.nii.gz
β βββ [100+ more structures]
βββ RS.dcm / RS_auto(.cropped).dcm / RS_custom.dcm
βββ radiomics_ct.xlsx # Per-course radiomics
βββ radiomics_robustness_ct.parquet # Per-course perturbation-level robustness data
βββ dvh_metrics.xlsx # Per-course DVH
βββ qc_reports/ # Quality control
RTSTRUCT quick guide
- RS.dcm: untouched copy of the clinical RTSTRUCT imported from the TPS.
- RS_auto.dcm: TotalSegmentator output on the uncropped CT grid.
- RS_auto_cropped.dcm: cropped variant used when ct_cropping.use_cropped_for_* is enabled.
- RS_custom.dcm: merged manual + auto + custom_structures*.yaml definitions (boolean/margin ROIs).
π§ Common Issues & Fixes¶
Issue: Missing radiomics features (NaN values)¶
Cause: ROI too small (<64 voxels) or in skip list Fix:
# Check ROI size
radiomics[['Structure', 'Volume_voxels']].sort_values('Volume_voxels')
# Check QC for cropping
qc[['PatientID', 'Structures_Fully_Cropped']]
Issue: DVH metrics don't match clinical system¶
Cause: Different interpolation or frame-of-reference mismatch Fix:
# Check FOR consistency
qc[['PatientID', 'Frame_Of_Reference_Consistent']]
# Compare volumes
dvh[['Structure', 'ROI_Type', 'ROI_Volume_cc']]
π Quick Analysis Templates¶
Toxicity Prediction Model¶
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
# Load data
dvh = pd.read_excel("_RESULTS/dvh_metrics.xlsx")
# Filter organ
rectum = dvh[dvh['Structure'].str.contains('rectum', case=False)]
# Features
X = rectum[['Dmean_Gy', 'D2ccGy', 'V50Gy', 'V60Gy']]
# Train (assuming you have outcome labels)
# y = outcomes['Grade2+_Toxicity']
# clf = RandomForestClassifier()
# clf.fit(X, y)
Radiomics Outcome Prediction¶
# Load radiomics
rad = pd.read_excel("_RESULTS/radiomics_ct.xlsx")
# Filter to target structures
ptv = rad[rad['Structure'].str.contains('ptv', case=False)]
# Original features only (most stable)
features = ptv.filter(regex='^original_')
# Remove high correlation
features_clean = remove_correlated(features, threshold=0.95)
# Standardize and train model
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X = scaler.fit_transform(features_clean)
Cohort Description (Table 1)¶
metadata = pd.read_excel("_RESULTS/case_metadata.xlsx")
print("Cohort Summary:")
print(f"Patients: {metadata['PatientID'].nunique()}")
print(f"Courses: {len(metadata)}")
print(f"\nSex distribution:\n{metadata['PatientSex'].value_counts()}")
print(f"\nMean prescription: {metadata['PrescriptionDose_Gy'].mean():.1f} Gy")
print(f"Mean fractions: {metadata['NumberOfFractions'].mean():.1f}")
π Resources¶
- Setup Script:
./setup_docker_project.sh - PyRadiomics Docs: https://pyradiomics.readthedocs.io/
- IBSI Standards: https://theibsi.github.io/
- Pipeline Overview: Home
π Need Help?¶
- Check QC reports first:
_RESULTS/qc_reports.xlsx - Check logs:
{logs_dir}/ - Review technical documentation: Architecture
Document Version: 1.0 Compatible with: rtpipeline v2.1.1+ Last Updated: 2025-11-13