Metrics API¶
This module contains core functions for dose analysis, DVH computation, quality metrics, and plan comparison.
DVH Module¶
dvh ¶
Dose-Volume Histogram (DVH) computation and analysis.
This module provides functions for computing DVHs and extracting DVH-based metrics such as volume at dose (VX) and dose at volume (DX).
Classes¶
Functions:¶
compute_dvh ¶
compute_dvh(dose: Dose, structure: Structure, max_dose: Optional[float] = None, step_size: float = 0.1) -> Tuple[np.ndarray, np.ndarray]
Compute dose-volume histogram for a structure.
A DVH shows the percentage of structure volume that receives at least a given dose level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to compute DVH for |
required |
max_dose
|
Optional[float]
|
Maximum dose for histogram bins (auto-detect if None) |
None
|
step_size
|
float
|
Bin width in Gy |
0.1
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Tuple of (dose_bins, volume_percentages) |
ndarray
|
|
Tuple[ndarray, ndarray]
|
|
Examples:
>>> from dosemetrics.dose import Dose
>>> from dosemetrics.metrics import dvh
>>>
>>> dose = Dose.from_dicom("rtdose.dcm")
>>> ptv = structures.get_structure("PTV")
>>>
>>> dose_bins, volumes = dvh.compute_dvh(dose, ptv)
>>>
>>> # Plot DVH
>>> import matplotlib.pyplot as plt
>>> plt.plot(dose_bins, volumes)
>>> plt.xlabel("Dose (Gy)")
>>> plt.ylabel("Volume (%)")
Source code in src/dosemetrics/metrics/dvh.py
compute_volume_at_dose ¶
Compute percentage of structure receiving at least the dose threshold.
This computes VX where X is the dose threshold (e.g., V20 = % volume >= 20 Gy).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
dose_threshold
|
float
|
Dose threshold in Gy |
required |
Returns:
| Type | Description |
|---|---|
float
|
Percentage of volume (0-100) receiving >= dose_threshold |
Examples:
>>> # V20: percentage of lung receiving >= 20 Gy
>>> v20 = compute_volume_at_dose(dose, lung, 20.0)
>>> print(f"V20: {v20:.1f}%")
>>>
>>> # V5: percentage of heart receiving >= 5 Gy
>>> v5 = compute_volume_at_dose(dose, heart, 5.0)
Source code in src/dosemetrics/metrics/dvh.py
compute_dose_at_volume ¶
Compute dose received by a given percentage of structure volume.
This computes DX where X is the volume percentage (e.g., D95 = dose to 95% of volume).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
volume_percent
|
float
|
Volume percentage (0-100) |
required |
Returns:
| Type | Description |
|---|---|
float
|
Dose in Gy that the specified volume percentage receives |
Raises:
| Type | Description |
|---|---|
ValueError
|
If volume_percent is not in range 0-100 |
Examples:
>>> # D95: dose covering 95% of PTV
>>> d95 = compute_dose_at_volume(dose, ptv, 95)
>>> print(f"D95: {d95:.2f} Gy")
>>>
>>> # D_0.1cc for OAR (requires volume in cc conversion)
>>> # For now, use percentile approximation
>>> d_max = compute_dose_at_volume(dose, brainstem, 0.1)
Source code in src/dosemetrics/metrics/dvh.py
compute_dose_at_volume_cc ¶
Compute dose received by a given absolute volume in cc.
This computes D_Xcc (e.g., D_0.1cc = dose to hottest 0.1 cc).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
volume_cc
|
float
|
Absolute volume in cubic centimeters |
required |
Returns:
| Type | Description |
|---|---|
float
|
Dose in Gy received by the specified volume |
Examples:
>>> # D_0.1cc: dose to hottest 0.1 cc (common OAR metric)
>>> d_0_1cc = compute_dose_at_volume_cc(dose, brainstem, 0.1)
>>> print(f"D_0.1cc: {d_0_1cc:.2f} Gy")
Source code in src/dosemetrics/metrics/dvh.py
compute_equivalent_uniform_dose ¶
Compute Equivalent Uniform Dose (EUD).
EUD = (mean(D_i^a))^(1/a)
The a-parameter depends on tissue type: - a < 0 for tumors (emphasizes cold spots) - a > 0 for normal tissues (emphasizes hot spots)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
a_parameter
|
float
|
Tissue-specific parameter |
required |
Returns:
| Type | Description |
|---|---|
float
|
Equivalent uniform dose in Gy |
References
Niemierko, Med Phys 1997
Examples:
>>> # For tumor (emphasize underdosage)
>>> eud_tumor = compute_equivalent_uniform_dose(dose, ptv, a_parameter=-10)
>>>
>>> # For OAR (emphasize overdosage)
>>> eud_oar = compute_equivalent_uniform_dose(dose, brainstem, a_parameter=5)
Source code in src/dosemetrics/metrics/dvh.py
create_dvh_table ¶
create_dvh_table(dose: Dose, structure_set: StructureSet, structure_names: Optional[list] = None, max_dose: Optional[float] = None, step_size: float = 0.1) -> pd.DataFrame
Create DVH table for multiple structures in long format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure_set
|
StructureSet
|
StructureSet containing structures |
required |
structure_names
|
Optional[list]
|
List of structure names to include (optional) |
None
|
max_dose
|
Optional[float]
|
Maximum dose for bins |
None
|
step_size
|
float
|
Dose bin width in Gy |
0.1
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with columns [Dose, Structure, Volume] |
Examples:
>>> dvh_df = create_dvh_table(dose, structures,
... structure_names=["PTV", "Brainstem", "SpinalCord"])
>>> dvh_df.to_csv("dvh_data.csv")
Source code in src/dosemetrics/metrics/dvh.py
extract_dvh_metrics ¶
extract_dvh_metrics(dose: Dose, structure: Structure, dose_thresholds: Optional[list] = None, volume_percentages: Optional[list] = None) -> Dict[str, float]
Extract common DVH metrics for a structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
dose_thresholds
|
Optional[list]
|
List of dose levels for VX metrics (Gy) |
None
|
volume_percentages
|
Optional[list]
|
List of volume percentages for DX metrics |
None
|
Returns:
| Type | Description |
|---|---|
Dict[str, float]
|
Dictionary with DVH metrics |
Examples:
>>> metrics = extract_dvh_metrics(
... dose, ptv,
... dose_thresholds=[20, 40, 60],
... volume_percentages=[2, 50, 95, 98]
... )
>>> print(metrics)
{'V20': 98.5, 'V40': 97.2, 'V60': 95.8, 'D2': 63.5, 'D50': 60.2, ...}
Source code in src/dosemetrics/metrics/dvh.py
compute_dose_statistics ¶
Compute comprehensive dose statistics for a structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, float]
|
Dictionary with statistics including: |
Dict[str, float]
|
|
Dict[str, float]
|
|
Examples:
>>> from dosemetrics.dose import Dose
>>> from dosemetrics.structure_set import StructureSet
>>> from dosemetrics.metrics import dvh
>>>
>>> dose = Dose.from_dicom("rtdose.dcm")
>>> structures = StructureSet(...)
>>> ptv = structures.get_structure("PTV")
>>>
>>> stats = dvh.compute_dose_statistics(dose, ptv)
>>> print(f"Mean dose: {stats['mean_dose']:.2f} Gy")
>>> print(f"D95: {stats['D95']:.2f} Gy")
Source code in src/dosemetrics/metrics/dvh.py
compute_mean_dose ¶
Compute mean dose in structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
Returns:
| Type | Description |
|---|---|
float
|
Mean dose in Gy |
Source code in src/dosemetrics/metrics/dvh.py
compute_max_dose ¶
Compute maximum dose in structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
Returns:
| Type | Description |
|---|---|
float
|
Maximum dose in Gy |
Source code in src/dosemetrics/metrics/dvh.py
compute_min_dose ¶
Compute minimum dose in structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
Returns:
| Type | Description |
|---|---|
float
|
Minimum dose in Gy |
Source code in src/dosemetrics/metrics/dvh.py
compute_median_dose ¶
Compute median dose in structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
Returns:
| Type | Description |
|---|---|
float
|
Median dose in Gy |
Source code in src/dosemetrics/metrics/dvh.py
compute_dvh_score ¶
Compute DVH Score: average absolute difference in D1, D95, D99 between two dose distributions.
DVH Score = (|D1_ref - D1_eval| + |D95_ref - D95_eval| + |D99_ref - D99_eval|) / 3
Where DX means X% of the volume receives at least this dose. This metric captures clinically relevant dose differences at near-maximum (D1), near-minimum (D99), and dose coverage (D95) levels.
Lower values indicate better agreement between the two distributions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose_reference
|
Dose
|
Reference dose distribution |
required |
dose_evaluated
|
Dose
|
Evaluated dose distribution to compare |
required |
structure
|
Structure
|
Structure to restrict comparison to |
required |
Returns:
| Type | Description |
|---|---|
float
|
Average absolute DVH difference in Gy |
References
Adapted from GDP-HMM AAPM Challenge evaluation methodology.
Examples:
>>> score = compute_dvh_score(reference_dose, predicted_dose, ptv)
>>> print(f"DVH Score: {score:.3f} Gy")
Source code in src/dosemetrics/metrics/dvh.py
compute_dvh_auc ¶
compute_dvh_auc(dose: Dose, structure: Structure, num_bins: int = 100, normalize: bool = True, dose_range: Optional[Tuple[float, float]] = None) -> float
Compute the Area Under the DVH Curve (DVH-AUC) using the trapezoidal rule.
The DVH-AUC is the integral of volume percentage over the dose range. A higher AUC indicates that more of the structure volume receives higher doses. This is a single-distribution metric (unlike area-between-curves which compares two).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to compute DVH AUC for |
required |
num_bins
|
int
|
Number of dose bins for DVH computation (default: 100) |
100
|
normalize
|
bool
|
If True, normalize AUC to [0, 1] by dividing by the maximum possible area (100% volume × dose range). Default: True. |
True
|
dose_range
|
Optional[Tuple[float, float]]
|
Fixed (min_dose, max_dose) in Gy for binning. Uses per-structure min/max if None. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
DVH AUC value. If normalize=True, returns value in [0, 1]. |
float
|
If normalize=False, returns value in Gy (dose × volume units). |
References
Adapted from DVHAUC metric in GDP-HMM AAPM Challenge.
Examples:
>>> auc = compute_dvh_auc(dose, ptv, normalize=True)
>>> print(f"DVH AUC (normalized): {auc:.3f}")
>>>
>>> # Compare two structures
>>> ptv_auc = compute_dvh_auc(dose, ptv)
>>> oar_auc = compute_dvh_auc(dose, brainstem)
Source code in src/dosemetrics/metrics/dvh.py
compute_dose_percentile ¶
Compute dose percentile (DX).
D95 means 95% of the volume receives at least this dose. This corresponds to the 5th percentile of the dose distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Structure
|
Structure to analyze |
required |
percentile
|
float
|
Volume percentage (0-100). For D95, use percentile=95 |
required |
Returns:
| Type | Description |
|---|---|
float
|
Dose in Gy that the specified percentage of volume receives |
Raises:
| Type | Description |
|---|---|
ValueError
|
If percentile is not in range 0-100 |
Examples:
>>> # D95: dose received by 95% of volume
>>> d95 = compute_dose_percentile(dose, ptv, 95)
>>>
>>> # D50: median dose
>>> d50 = compute_dose_percentile(dose, ptv, 50)
>>>
>>> # D05: near-maximum dose (hot spot)
>>> d05 = compute_dose_percentile(dose, ptv, 5)
Source code in src/dosemetrics/metrics/dvh.py
Conformity Module¶
conformity ¶
Conformity indices for target coverage evaluation.
This module provides various conformity indices used to evaluate how well the prescription isodose conforms to the target volume. These metrics are critical for assessing treatment plan quality.
Classes¶
Functions:¶
compute_conformity_index ¶
Compute Conformity Index (CI).
CI = V_target_rx / V_rx
Where: - V_target_rx = volume of target receiving >= prescription dose - V_rx = total volume receiving >= prescription dose
Measures how well the prescription isodose conforms to the target. Ideal value is 1.0. Values < 1.0 indicate dose spillage outside target.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure (PTV, CTV, etc.) |
required |
prescription_dose
|
float
|
Prescription dose in Gy |
required |
Returns:
| Type | Description |
|---|---|
float
|
Conformity index (dimensionless, typically 0-1) |
References
ICRU Report 62 (1999)
Examples:
>>> ci = compute_conformity_index(dose, ptv, prescription_dose=60.0)
>>> print(f"Conformity Index: {ci:.3f}")
Source code in src/dosemetrics/metrics/conformity.py
compute_conformity_number ¶
Compute Conformity Number (CN) or Conformation Number.
CN = (V_target_rx / V_target) * (V_target_rx / V_rx)
Combines target coverage and dose spillage into a single metric. Ideal value is 1.0.
The first factor (V_target_rx / V_target) represents target coverage. The second factor (V_target_rx / V_rx) represents conformity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure |
required |
prescription_dose
|
float
|
Prescription dose in Gy |
required |
Returns:
| Type | Description |
|---|---|
float
|
Conformity number (0-1) |
References
van't Riet et al., Int J Radiat Oncol Biol Phys 1997
Examples:
>>> cn = compute_conformity_number(dose, ptv, prescription_dose=60.0)
>>> print(f"Conformity Number: {cn:.3f}")
Source code in src/dosemetrics/metrics/conformity.py
compute_paddick_conformity_index ¶
Compute Paddick Conformity Index (CI_Paddick).
CI_Paddick = (V_target_rx)^2 / (V_target * V_rx)
This index is commonly used for radiosurgery and SBRT plans. Ideal value is 1.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure |
required |
prescription_dose
|
float
|
Prescription dose in Gy |
required |
Returns:
| Type | Description |
|---|---|
float
|
Paddick conformity index (0-1) |
References
Paddick, J Neurosurg 2000
Examples:
>>> # Often used for stereotactic radiosurgery
>>> ci_paddick = compute_paddick_conformity_index(dose, gtv, prescription_dose=18.0)
>>> print(f"Paddick CI: {ci_paddick:.3f}")
Source code in src/dosemetrics/metrics/conformity.py
compute_coverage ¶
Compute target coverage.
Coverage = V_target_rx / V_target
Percentage of target volume receiving at least the prescription dose.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure |
required |
prescription_dose
|
float
|
Prescription dose in Gy |
required |
Returns:
| Type | Description |
|---|---|
float
|
Coverage as fraction (0-1) or percentage if multiplied by 100 |
Examples:
>>> coverage = compute_coverage(dose, ptv, prescription_dose=60.0)
>>> print(f"Target coverage: {coverage*100:.1f}%")
Source code in src/dosemetrics/metrics/conformity.py
compute_spillage ¶
Compute dose spillage outside target.
Spillage = (V_rx - V_target_rx) / V_rx
Fraction of prescription isodose volume that is outside the target. Lower values indicate better conformity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure |
required |
prescription_dose
|
float
|
Prescription dose in Gy |
required |
Returns:
| Type | Description |
|---|---|
float
|
Spillage as fraction (0-1) |
Examples:
>>> spillage = compute_spillage(dose, ptv, prescription_dose=60.0)
>>> print(f"Dose spillage: {spillage*100:.1f}%")
Source code in src/dosemetrics/metrics/conformity.py
compute_rtog_conformity_index ¶
Compute the RTOG Conformity Index (RTOG CI).
RTOG CI = V_Rx / V_target
Where: - V_Rx = total volume receiving >= prescription dose (prescription isodose volume) - V_target = target structure volume
The RTOG CI measures how well the prescription isodose conforms to the target. Values close to 1.0 are ideal. Values > 1.0 indicate over-coverage (dose spillage); values < 1.0 indicate under-coverage.
This differs from the ICRU-based CI in this library (V_target_rx / V_rx), which measures how much of the prescription isodose overlaps the target.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure (PTV, CTV, etc.) |
required |
prescription_dose
|
float
|
Prescription dose in Gy |
required |
Returns:
| Type | Description |
|---|---|
float
|
RTOG Conformity Index (dimensionless). Ideal value: 1.0. |
References
Shaw E, et al. Int J Radiat Oncol Biol Phys. 1993;27(5):1231-9. RTOG 90-05 stereotactic radiosurgery protocol.
Examples:
>>> rtog_ci = compute_rtog_conformity_index(dose, ptv, prescription_dose=60.0)
>>> if 0.9 <= rtog_ci <= 1.1:
... print("Excellent conformity (RTOG criteria)")
>>> elif 0.7 <= rtog_ci <= 1.5:
... print("Acceptable conformity (RTOG criteria)")
Source code in src/dosemetrics/metrics/conformity.py
compute_prescription_mae ¶
Compute the Mean Absolute Error (MAE) between actual dose and prescription dose within target.
Prescription MAE = mean(|dose_in_target - prescription_dose|)
This metric measures how well the dose within the target matches the prescription. A value of 0.0 means every voxel in the target received exactly the prescription dose. Useful for quantifying underdosing and overdosing within the target volume.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure (PTV, CTV, etc.) |
required |
prescription_dose
|
float
|
Prescription dose in Gy |
required |
Returns:
| Type | Description |
|---|---|
float
|
Mean absolute error from prescription dose in Gy |
References
Adapted from PTVPrescriptionMAE in GDP-HMM AAPM Challenge evaluation.
Examples:
>>> mae = compute_prescription_mae(dose, ptv, prescription_dose=60.0)
>>> print(f"Prescription MAE: {mae:.2f} Gy ({mae/60.0*100:.1f}% of prescription)")
Source code in src/dosemetrics/metrics/conformity.py
Homogeneity Module¶
homogeneity ¶
Homogeneity indices for target dose uniformity.
This module provides metrics to assess the uniformity of dose distribution within target volumes. More homogeneous dose distributions are generally preferred for tumor control.
Classes¶
Functions:¶
compute_homogeneity_index ¶
compute_homogeneity_index(dose: Dose, target: Structure, d2_percentile: float = 2.0, d98_percentile: float = 98.0) -> float
Compute Homogeneity Index (HI).
HI = (D2 - D98) / D50
Where: - D2 = dose received by 2% of volume (near-maximum) - D98 = dose received by 98% of volume (near-minimum) - D50 = median dose
Measures dose uniformity within target. Lower values indicate more homogeneous dose distribution.
Typical acceptable range: 0.05 - 0.20
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure (PTV, CTV, etc.) |
required |
d2_percentile
|
float
|
Upper percentile for near-max (typically 2%) |
2.0
|
d98_percentile
|
float
|
Lower percentile for near-min (typically 98%) |
98.0
|
Returns:
| Type | Description |
|---|---|
float
|
Homogeneity index (dimensionless) |
References
ICRU Report 83 (2010)
Examples:
>>> hi = compute_homogeneity_index(dose, ptv)
>>> print(f"Homogeneity Index: {hi:.3f}")
>>> if hi < 0.15:
... print("Excellent dose homogeneity")
Source code in src/dosemetrics/metrics/homogeneity.py
compute_gradient_index ¶
compute_gradient_index(dose: Dose, target: Structure, prescription_dose: float, half_prescription_volume_method: bool = True) -> float
Compute Gradient Index (GI) for dose fall-off outside target.
Two calculation methods: 1. Half-prescription volume: GI = V_50% / V_100% 2. Distance-based: Ratio of volumes at specific distances
Where: - V_100% = volume receiving >= prescription dose - V_50% = volume receiving >= 50% prescription dose
Lower values indicate steeper dose fall-off (better for sparing OARs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure |
required |
prescription_dose
|
float
|
Prescription dose in Gy |
required |
half_prescription_volume_method
|
bool
|
Use V_50%/V_100% method (default True) |
True
|
Returns:
| Type | Description |
|---|---|
float
|
Gradient index (dimensionless, typically 2-8) |
References
Paddick and Lippitz, J Neurosurg 2006
Examples:
>>> gi = compute_gradient_index(dose, ptv, prescription_dose=60.0)
>>> print(f"Gradient Index: {gi:.2f}")
>>> if gi < 3.0:
... print("Excellent dose fall-off")
Source code in src/dosemetrics/metrics/homogeneity.py
compute_dose_homogeneity ¶
Compute coefficient of variation (CV) of dose within target.
CV = std_dose / mean_dose
Alternative measure of dose homogeneity. Lower values indicate more uniform dose distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure |
required |
Returns:
| Type | Description |
|---|---|
float
|
Coefficient of variation (dimensionless) |
Examples:
Source code in src/dosemetrics/metrics/homogeneity.py
compute_uniformity_index ¶
Compute uniformity index.
UI = 1 - (D_max - D_min) / D_prescription
Values closer to 1.0 indicate better uniformity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
target
|
Structure
|
Target structure |
required |
Returns:
| Type | Description |
|---|---|
float
|
Uniformity index (0-1) |
Note
Requires prescription dose in target metadata or as parameter. Currently uses median dose as approximation.
Examples:
Source code in src/dosemetrics/metrics/homogeneity.py
Geometric Module¶
geometric ¶
Geometric similarity and overlap metrics for structure comparison.
This module provides metrics to compare two structure sets, typically used for evaluating auto-segmentation algorithms or inter-observer variability.
Classes¶
Functions:¶
compute_dice_coefficient ¶
Compute Dice coefficient (Sørensen-Dice index).
Dice = 2 * |A ∩ B| / (|A| + |B|)
Measures overlap between two structures. Range [0, 1], where 1 is perfect overlap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure1
|
Structure
|
First structure |
required |
structure2
|
Structure
|
Second structure |
required |
Returns:
| Type | Description |
|---|---|
float
|
Dice coefficient (0-1) |
References
Dice, Ecology 1945; Sørensen, Biologiske Skrifter 1948
Examples:
>>> auto_ptv = structures_auto.get_structure("PTV")
>>> manual_ptv = structures_manual.get_structure("PTV")
>>> dice = compute_dice_coefficient(auto_ptv, manual_ptv)
>>> print(f"Dice: {dice:.3f}")
Source code in src/dosemetrics/metrics/geometric.py
compute_jaccard_index ¶
Compute Jaccard index (Intersection over Union, IoU).
Jaccard = |A ∩ B| / |A ∪ B|
Measures overlap between two structures. Range [0, 1], where 1 is perfect overlap. More conservative than Dice coefficient.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure1
|
Structure
|
First structure |
required |
structure2
|
Structure
|
Second structure |
required |
Returns:
| Type | Description |
|---|---|
float
|
Jaccard index (0-1) |
References
Jaccard, New Phytologist 1912
Examples:
Source code in src/dosemetrics/metrics/geometric.py
compute_volume_difference ¶
Compute absolute volume difference.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure1
|
Structure
|
First structure |
required |
structure2
|
Structure
|
Second structure |
required |
Returns:
| Type | Description |
|---|---|
float
|
Absolute volume difference in cubic centimeters |
Examples:
>>> vol_diff = compute_volume_difference(auto_ptv, manual_ptv)
>>> print(f"Volume difference: {vol_diff:.2f} cc")
Source code in src/dosemetrics/metrics/geometric.py
compute_volume_ratio ¶
Compute volume ratio V1/V2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure1
|
Structure
|
First structure (numerator) |
required |
structure2
|
Structure
|
Second structure (denominator) |
required |
Returns:
| Type | Description |
|---|---|
float
|
Volume ratio (dimensionless) |
Examples:
Source code in src/dosemetrics/metrics/geometric.py
compute_sensitivity ¶
Compute sensitivity (recall, true positive rate).
Sensitivity = TP / (TP + FN) = |A ∩ B| / |B|
Measures how much of structure2 is covered by structure1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure1
|
Structure
|
Predicted/test structure |
required |
structure2
|
Structure
|
Reference/ground truth structure |
required |
Returns:
| Type | Description |
|---|---|
float
|
Sensitivity (0-1) |
Examples:
>>> sens = compute_sensitivity(auto_structure, manual_structure)
>>> print(f"Sensitivity: {sens:.3f}")
Source code in src/dosemetrics/metrics/geometric.py
compute_specificity ¶
compute_specificity(structure1: Structure, structure2: Structure, background_mask: Optional[ndarray] = None) -> float
Compute specificity (true negative rate).
Specificity = TN / (TN + FP)
Requires definition of background/universe. If not provided, uses the bounding box union of both structures.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure1
|
Structure
|
Predicted/test structure |
required |
structure2
|
Structure
|
Reference/ground truth structure |
required |
background_mask
|
Optional[ndarray]
|
Mask defining the universe (optional) |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Specificity (0-1) |
Examples:
>>> spec = compute_specificity(auto_structure, manual_structure)
>>> print(f"Specificity: {spec:.3f}")
Source code in src/dosemetrics/metrics/geometric.py
compute_hausdorff_distance ¶
compute_hausdorff_distance(structure1: Structure, structure2: Structure, percentile: Optional[float] = None) -> float
Compute Hausdorff distance between two structures.
If percentile is specified, computes the percentile Hausdorff distance (e.g., 95th percentile HD95), which is more robust to outliers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure1
|
Structure
|
First structure |
required |
structure2
|
Structure
|
Second structure |
required |
percentile
|
Optional[float]
|
If specified, compute percentile HD (e.g., 95 for HD95) |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Hausdorff distance in mm |
Examples:
>>> hd = compute_hausdorff_distance(auto_structure, manual_structure)
>>> hd95 = compute_hausdorff_distance(auto_structure, manual_structure, percentile=95)
Source code in src/dosemetrics/metrics/geometric.py
compute_mean_surface_distance ¶
Compute mean surface distance between two structures.
Average of all point-to-surface distances (symmetric).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure1
|
Structure
|
First structure |
required |
structure2
|
Structure
|
Second structure |
required |
Returns:
| Type | Description |
|---|---|
float
|
Mean surface distance in mm |
Source code in src/dosemetrics/metrics/geometric.py
compare_structure_sets ¶
compare_structure_sets(structure_set1: StructureSet, structure_set2: StructureSet, structure_names: Optional[list] = None) -> pd.DataFrame
Compute geometric metrics between two structure sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure_set1
|
StructureSet
|
First structure set (e.g., auto-segmentation) |
required |
structure_set2
|
StructureSet
|
Second structure set (e.g., manual segmentation) |
required |
structure_names
|
Optional[list]
|
List of structure names to compare (optional) |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with geometric metrics for each structure |
Examples:
>>> auto_structures = load_structure_set("auto/")
>>> manual_structures = load_structure_set("manual/")
>>> comparison = compare_structure_sets(auto_structures, manual_structures)
>>> print(comparison)
Source code in src/dosemetrics/metrics/geometric.py
Gamma Module¶
gamma ¶
Gamma analysis for dose distribution comparison.
This module provides gamma index calculation following the methodology of Low et al. (1998) and subsequent refinements.
References
- Low DA, Harms WB, Mutic S, Purdy JA. "A technique for the quantitative evaluation of dose distributions." Med Phys. 1998;25(5):656-61.
- Depuydt T, Van Esch A, Huyskens DP. "A quantitative evaluation of IMRT dose distributions: refinement and clinical assessment of the gamma evaluation." Radiother Oncol. 2002;62(3):309-19.
Classes¶
Functions:¶
compute_gamma_index ¶
compute_gamma_index(dose_reference: Dose, dose_evaluated: Dose, dose_criterion_percent: float = 3.0, distance_criterion_mm: float = 3.0, dose_threshold_percent: float = 10.0, global_normalization: bool = True, max_search_distance_mm: Optional[float] = None) -> np.ndarray
Compute 3D gamma index between reference and evaluated dose distributions.
The gamma index quantifies the agreement between two dose distributions by combining dose difference and distance-to-agreement criteria.
Parameters¶
dose_reference : Dose Reference (planned) dose distribution. dose_evaluated : Dose Evaluated (measured/calculated) dose distribution to compare. dose_criterion_percent : float, optional Dose difference criterion as percentage (default: 3.0 for 3%). distance_criterion_mm : float, optional Distance-to-agreement criterion in mm (default: 3.0 for 3mm). dose_threshold_percent : float, optional Low dose threshold below which gamma is not calculated (default: 10%). global_normalization : bool, optional If True, normalize to global maximum dose. If False, use local dose (default: True). max_search_distance_mm : float, optional Maximum search distance for gamma calculation. If None, uses 3 * distance_criterion_mm (default: None).
Returns¶
gamma : np.ndarray 3D array of gamma values. Values < 1 indicate passing points, values >= 1 indicate failing points. NaN for points below threshold.
Notes¶
Common gamma criteria: - Clinical QA: 3%/3mm (dose_criterion=3.0, distance_criterion=3.0) - Stricter QA: 2%/2mm - Research: 1%/1mm
The gamma passing rate is typically calculated as the percentage of points with gamma <= 1.0.
Examples¶
gamma = compute_gamma_index(planned_dose, measured_dose) passing_rate = np.sum(gamma <= 1.0) / np.sum(~np.isnan(gamma)) * 100 print(f"Gamma passing rate: {passing_rate:.1f}%")
Raises¶
ValueError If dose distributions have incompatible geometry.
Source code in src/dosemetrics/metrics/gamma.py
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
compute_gamma_passing_rate ¶
Compute gamma passing rate from gamma index array.
Parameters¶
gamma : np.ndarray Gamma index values from compute_gamma_index(). threshold : float, optional Gamma threshold for passing (default: 1.0).
Returns¶
passing_rate : float Percentage of points with gamma <= threshold (0-100).
Source code in src/dosemetrics/metrics/gamma.py
compute_gamma_statistics ¶
Compute comprehensive statistics from gamma index array.
Parameters¶
gamma : np.ndarray Gamma index values.
Returns¶
stats : dict Dictionary containing: - 'passing_rate_1_0': Passing rate at gamma=1.0 - 'mean_gamma': Mean gamma value - 'max_gamma': Maximum gamma value - 'gamma_50': Median gamma value - 'gamma_95': 95th percentile gamma
Source code in src/dosemetrics/metrics/gamma.py
compute_2d_gamma ¶
compute_2d_gamma(dose_reference_slice: ndarray, dose_evaluated_slice: ndarray, dose_criterion_percent: float = 3.0, distance_criterion_mm: float = 3.0, pixel_spacing: Tuple[float, float] = (1.0, 1.0)) -> np.ndarray
Compute 2D gamma index for a single slice (faster than 3D).
Parameters¶
dose_reference_slice : np.ndarray 2D reference dose slice. dose_evaluated_slice : np.ndarray 2D evaluated dose slice. dose_criterion_percent : float Dose criterion (%). distance_criterion_mm : float Distance criterion (mm). pixel_spacing : tuple of float Pixel spacing in mm (row_spacing, col_spacing).
Returns¶
gamma : np.ndarray 2D gamma index array.
Raises¶
ValueError If slice shapes don't match or are not 2D.
Source code in src/dosemetrics/metrics/gamma.py
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 | |
compute_gamma_index_gpu ¶
compute_gamma_index_gpu(dose_reference: Dose, dose_evaluated: Dose, dose_criterion_percent: float = 3.0, distance_criterion_mm: float = 3.0) -> np.ndarray
GPU-accelerated gamma index calculation (requires CuPy or similar).
Note: This is a placeholder for future GPU acceleration using CuPy or similar.
Parameters¶
dose_reference : Dose Reference dose. dose_evaluated : Dose Evaluated dose. dose_criterion_percent : float Dose criterion (%). distance_criterion_mm : float Distance criterion (mm).
Returns¶
gamma : np.ndarray Gamma index array.
Raises¶
NotImplementedError GPU acceleration not implemented yet.
Source code in src/dosemetrics/metrics/gamma.py
Dose Comparison Module¶
dose_comparison ¶
Dose distribution comparison metrics beyond DVH.
This module provides image-based metrics for comparing 3D dose distributions, including SSIM, MSE, MAE, and other similarity measures.
Future Implementation TODOs
- Structural Similarity Index (SSIM) for dose volumes
- Mean Squared Error (MSE) and variants
- Peak Signal-to-Noise Ratio (PSNR)
- Mutual Information
- Normalized Cross-Correlation
- Dose-volume histogram difference maps
Classes¶
Functions:¶
compute_ssim ¶
compute_ssim(dose1: Dose, dose2: Dose, structure: Optional[Structure] = None, window_size: int = 11, k1: float = 0.01, k2: float = 0.03) -> float
Compute Structural Similarity Index (SSIM) between two dose distributions.
SSIM is a perceptual metric that quantifies image quality degradation based on luminance, contrast, and structure. Originally developed for image comparison, it's applicable to dose distributions.
Parameters¶
dose1 : Dose Reference dose distribution. dose2 : Dose Comparison dose distribution. structure : Structure, optional If provided, compute SSIM only within structure volume. If None, compute for entire dose grid. window_size : int, optional Size of sliding window for local SSIM computation (default: 11). k1 : float, optional Algorithm parameter (default: 0.01). k2 : float, optional Algorithm parameter (default: 0.03).
Returns¶
ssim : float Mean SSIM value (0-1, where 1 is perfect similarity).
Notes¶
SSIM ranges from -1 to 1: - 1: Perfect similarity - 0: No structural similarity - -1: Perfect anti-correlation
SSIM considers three components
- Luminance: Compares mean intensities
- Contrast: Compares standard deviations
- Structure: Compares correlation
References¶
- Wang Z, Bovik AC, Sheikh HR, Simoncelli EP. "Image quality assessment: from error visibility to structural similarity." IEEE Trans Image Process. 2004;13(4):600-12.
Examples¶
ssim = compute_ssim(planned_dose, delivered_dose, ptv) print(f"Dose SSIM: {ssim:.3f}") if ssim > 0.95: ... print("Excellent agreement")
Raises¶
NotImplementedError This function is a stub for future implementation. ValueError If dose distributions have incompatible geometry.
Source code in src/dosemetrics/metrics/dose_comparison.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
compute_mse ¶
Compute Mean Squared Error between two dose distributions.
Parameters¶
dose1 : Dose Reference dose. dose2 : Dose Comparison dose. structure : Structure, optional If provided, compute MSE only within structure.
Returns¶
mse : float Mean squared error in Gy^2.
Raises¶
ValueError If dose distributions have incompatible shapes.
Source code in src/dosemetrics/metrics/dose_comparison.py
compute_mae ¶
Compute Mean Absolute Error between two dose distributions.
Parameters¶
dose1 : Dose Reference dose. dose2 : Dose Comparison dose. structure : Structure, optional If provided, compute MAE only within structure.
Returns¶
mae : float Mean absolute error in Gy.
Notes¶
MAE is often more interpretable than MSE for dose comparison as it's in the same units as dose (Gy).
Raises¶
ValueError If dose distributions have incompatible shapes.
Source code in src/dosemetrics/metrics/dose_comparison.py
compute_psnr ¶
compute_psnr(dose1: Dose, dose2: Dose, structure: Optional[Structure] = None, data_range: Optional[float] = None) -> float
Compute Peak Signal-to-Noise Ratio between two dose distributions.
Parameters¶
dose1 : Dose Reference dose. dose2 : Dose Comparison dose. structure : Structure, optional If provided, compute PSNR only within structure. data_range : float, optional Data range (max - min). If None, computed from doses.
Returns¶
psnr : float Peak signal-to-noise ratio in dB.
Notes¶
PSNR is defined as: PSNR = 10 * log10((MAX^2) / MSE) Higher values indicate better similarity.
Raises¶
ValueError If dose distributions have incompatible shapes or MSE is zero.
Source code in src/dosemetrics/metrics/dose_comparison.py
compute_mutual_information ¶
compute_mutual_information(dose1: Dose, dose2: Dose, structure: Optional[Structure] = None, bins: int = 256) -> float
Compute Mutual Information between two dose distributions.
Parameters¶
dose1 : Dose First dose distribution. dose2 : Dose Second dose distribution. structure : Structure, optional If provided, compute MI only within structure. bins : int, optional Number of histogram bins (default: 256).
Returns¶
mi : float Mutual information value (higher indicates more similarity).
Notes¶
Mutual Information quantifies the information shared between two distributions. It's particularly useful for multimodal comparison.
Raises¶
ValueError If dose distributions have incompatible shapes.
Source code in src/dosemetrics/metrics/dose_comparison.py
compute_normalized_cross_correlation ¶
compute_normalized_cross_correlation(dose1: Dose, dose2: Dose, structure: Optional[Structure] = None) -> float
Compute Normalized Cross-Correlation between two dose distributions.
Parameters¶
dose1 : Dose First dose distribution. dose2 : Dose Second dose distribution. structure : Structure, optional If provided, compute NCC only within structure.
Returns¶
ncc : float Normalized cross-correlation (-1 to 1).
Notes¶
NCC is Pearson correlation coefficient for images/volumes. Values close to 1 indicate high positive correlation.
Raises¶
ValueError If dose distributions have incompatible shapes.
Source code in src/dosemetrics/metrics/dose_comparison.py
compute_dose_difference_map ¶
Compute voxel-wise dose difference map.
Parameters¶
dose1 : Dose Reference dose. dose2 : Dose Comparison dose. absolute : bool, optional If True, return absolute differences (default: False).
Returns¶
diff_dose : Dose Dose object containing difference map.
Notes¶
Useful for visualizing spatial dose discrepancies.
Raises¶
ValueError If dose distributions have incompatible shapes.
Source code in src/dosemetrics/metrics/dose_comparison.py
compute_dose_comparison_metrics ¶
compute_dose_comparison_metrics(dose1: Dose, dose2: Dose, structure: Optional[Structure] = None) -> Dict[str, float]
Compute comprehensive set of dose comparison metrics.
Parameters¶
dose1 : Dose Reference dose. dose2 : Dose Comparison dose. structure : Structure, optional If provided, compute metrics only within structure.
Returns¶
metrics : dict Dictionary containing: - 'ssim': Structural similarity index - 'mse': Mean squared error - 'mae': Mean absolute error - 'psnr': Peak signal-to-noise ratio - 'ncc': Normalized cross-correlation - 'mi': Mutual information
Examples¶
metrics = compute_dose_comparison_metrics(dose1, dose2, ptv) print(f"SSIM: {metrics['ssim']:.3f}") print(f"MAE: {metrics['mae']:.2f} Gy")
Raises¶
ValueError If dose distributions have incompatible shapes.
Source code in src/dosemetrics/metrics/dose_comparison.py
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 | |
compute_3d_dose_gradient ¶
Compute 3D dose gradient (useful for dose falloff analysis).
Parameters¶
dose : Dose Dose distribution.
Returns¶
grad_x : np.ndarray Gradient in x direction. grad_y : np.ndarray Gradient in y direction. grad_z : np.ndarray Gradient in z direction.
Notes¶
Uses numpy gradient function which computes central differences in the interior and first differences at the boundaries.
The gradient is useful for analyzing dose falloff regions and identifying high-gradient areas.
Source code in src/dosemetrics/metrics/dose_comparison.py
compute_variance_of_laplacian ¶
Compute the Variance of Laplacian (VoL) as a measure of dose distribution sharpness.
A higher variance indicates sharper, more spatially complex dose gradients (common in modern IMRT/VMAT plans). A lower variance indicates smoother, more homogeneous dose distributions.
The Laplacian operator highlights regions of rapid dose change (edges/interfaces). For 3D volumes, the Laplacian is applied slice-by-slice along the first axis and the variance is averaged across all slices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose
|
Dose
|
Dose distribution object |
required |
structure
|
Optional[Structure]
|
If provided, only consider voxels within this structure for variance computation. If None, uses the full dose volume. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Average variance of the Laplacian (dimensionless). Higher = sharper dose edges. |
References
Adapted from VarianceOfLaplacian metric in GDP-HMM AAPM Challenge. Laplacian kernel: [[0,1,0],[1,-4,1],[0,1,0]]
Examples:
>>> vol = compute_variance_of_laplacian(dose)
>>> print(f"Dose sharpness (VoL): {vol:.4f}")
>>>
>>> # Compare sharpness within target vs globally
>>> vol_ptv = compute_variance_of_laplacian(dose, ptv)
>>> vol_global = compute_variance_of_laplacian(dose)
Source code in src/dosemetrics/metrics/dose_comparison.py
compute_normalized_mae ¶
compute_normalized_mae(dose_reference: Dose, dose_evaluated: Dose, structure: Optional[Structure] = None, normalization_value: Optional[float] = None, dose_threshold_gy: Optional[float] = None) -> float
Compute Normalized MAE with optional threshold masking.
Normalized MAE = mean(|dose_ref - dose_eval|) / normalization_value
Optionally restricts computation to voxels where the reference dose exceeds a threshold, focusing the metric on clinically relevant dose regions.
This is a generalization of the GDP-HMM Challenge MAE metric, adapted for use with arbitrary structures and normalization values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dose_reference
|
Dose
|
Reference dose distribution |
required |
dose_evaluated
|
Dose
|
Evaluated dose distribution to compare |
required |
structure
|
Optional[Structure]
|
If provided, restrict computation to this structure. Uses the full dose volume if None. |
None
|
normalization_value
|
Optional[float]
|
Value to normalize the MAE by (e.g., prescription dose). If None, returns un-normalized MAE (equivalent to compute_mae). |
None
|
dose_threshold_gy
|
Optional[float]
|
If provided, only include voxels where the reference dose exceeds this threshold in Gy. Useful for focusing on high-dose regions and ignoring low-dose areas outside the treatment field. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Normalized MAE (dimensionless if normalization_value provided, else Gy). |
float
|
Returns NaN if no voxels remain after applying the threshold mask. |
References
Adapted from ChallengeMAE in GDP-HMM AAPM Challenge evaluation.
Examples:
>>> # Normalized by prescription dose (60 Gy), only high-dose region
>>> nMAE = compute_normalized_mae(
... reference_dose, predicted_dose,
... structure=body,
... normalization_value=60.0,
... dose_threshold_gy=5.0
... )
>>> print(f"Normalized MAE: {nMAE:.4f}")
Source code in src/dosemetrics/metrics/dose_comparison.py
Advanced DVH Module¶
advanced_dvh ¶
Advanced DVH metrics and comparison tools.
This module provides advanced DVH-based metrics for comparing dose distributions including Wasserstein distance, area between curves, and other statistical measures.
Future Implementation TODOs
- Wasserstein distance (Earth Mover's Distance) between DVHs
- Area between DVH curves (L1/L2 norms)
- DVH bandwidth and confidence intervals
- Chi-square and Kolmogorov-Smirnov tests for DVH comparison
- DVH-based TCP/NTCP models
Classes¶
Functions:¶
compute_dvh_wasserstein_distance ¶
Compute Wasserstein distance (Earth Mover's Distance) between two DVHs.
The Wasserstein distance quantifies the minimum "work" required to transform one DVH into another, providing a meaningful metric for DVH similarity.
Parameters¶
dose1 : Dose First dose distribution. dose2 : Dose Second dose distribution. structure : Structure Structure for which to compute DVHs.
Returns¶
distance : float Wasserstein distance between the two DVHs.
Notes¶
The Wasserstein distance is also known as: - Earth Mover's Distance (EMD) - Kantorovich-Rubinstein metric - Mallows distance
It satisfies the triangle inequality and is a true metric, unlike simple area-between-curves measures.
References¶
- Rubner Y, Tomasi C, Guibas LJ. "The Earth Mover's Distance as a Metric for Image Retrieval." Int J Comput Vision. 2000;40(2):99-121.
Examples¶
from dosemetrics.metrics import advanced_dvh distance = advanced_dvh.compute_dvh_wasserstein_distance( ... planned_dose, delivered_dose, ptv ... ) print(f"DVH Wasserstein distance: {distance:.2f} Gy")
Raises¶
NotImplementedError This function is a stub for future implementation.
Source code in src/dosemetrics/metrics/advanced_dvh.py
compute_area_between_dvh_curves ¶
compute_area_between_dvh_curves(dose1: Dose, dose2: Dose, structure: Structure, norm: str = 'L2') -> float
Compute area between two DVH curves.
Parameters¶
dose1 : Dose First dose distribution. dose2 : Dose Second dose distribution. structure : Structure Structure for which to compute DVHs. norm : {'L1', 'L2'}, optional Norm to use for area calculation: - 'L1': Sum of absolute differences - 'L2': Sum of squared differences (default)
Returns¶
area : float Area between the two DVH curves.
Notes¶
The L1 norm gives the Manhattan distance, while L2 gives Euclidean distance. For DVH comparison, L1 is often more interpretable.
Raises¶
ValueError If norm is not 'L1' or 'L2'.
Source code in src/dosemetrics/metrics/advanced_dvh.py
compute_dvh_chi_square ¶
Perform chi-square test comparing two DVHs.
Parameters¶
dose1 : Dose First (expected) dose distribution. dose2 : Dose Second (observed) dose distribution. structure : Structure Structure for DVH computation.
Returns¶
chi2_statistic : float Chi-square test statistic. p_value : float P-value for the test.
Notes¶
Tests the null hypothesis that the two DVHs come from the same distribution.
Source code in src/dosemetrics/metrics/advanced_dvh.py
compute_dvh_ks_test ¶
Perform Kolmogorov-Smirnov test comparing two DVHs.
Parameters¶
dose1 : Dose First dose distribution. dose2 : Dose Second dose distribution. structure : Structure Structure for DVH computation.
Returns¶
ks_statistic : float KS test statistic (maximum difference between CDFs). p_value : float P-value for the test.
Notes¶
The KS test is non-parametric and tests whether two samples come from the same distribution.
Source code in src/dosemetrics/metrics/advanced_dvh.py
compute_dvh_confidence_interval ¶
compute_dvh_confidence_interval(doses: List[Dose], structure: Structure, confidence: float = 0.95) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
Compute DVH confidence intervals from multiple dose distributions.
Useful for uncertainty quantification from multiple treatment plans or Monte Carlo dose simulations.
Parameters¶
doses : list of Dose Multiple dose distributions (e.g., from robust optimization). structure : Structure Structure for DVH computation. confidence : float, optional Confidence level (default: 0.95 for 95% CI).
Returns¶
dose_bins : np.ndarray Dose bin values. mean_dvh : np.ndarray Mean DVH curve. ci_lower : np.ndarray Lower confidence interval. ci_upper : np.ndarray Upper confidence interval.
Examples¶
dose_bins, mean, lower, upper = compute_dvh_confidence_interval( ... [dose1, dose2, dose3], ptv ... ) plt.fill_between(dose_bins, lower, upper, alpha=0.3) plt.plot(dose_bins, mean, 'k-', linewidth=2)
Source code in src/dosemetrics/metrics/advanced_dvh.py
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
compute_dvh_bandwidth ¶
Compute DVH bandwidth (maximum difference at each dose level).
Parameters¶
doses : list of Dose Multiple dose distributions. structure : Structure Structure for DVH computation.
Returns¶
bandwidth : np.ndarray Maximum difference between DVHs at each dose bin.
Notes¶
Useful for robust plan evaluation - smaller bandwidth indicates more robust plan.
Source code in src/dosemetrics/metrics/advanced_dvh.py
compute_dvh_similarity_index ¶
compute_dvh_similarity_index(dose1: Dose, dose2: Dose, structure: Structure, method: str = 'dice') -> float
Compute DVH similarity index using various methods.
Parameters¶
dose1 : Dose First dose distribution. dose2 : Dose Second dose distribution. structure : Structure Structure for DVH computation. method : {'dice', 'jaccard', 'correlation', 'cosine'}, optional Similarity metric to use (default: 'dice').
Returns¶
similarity : float Similarity score (0-1, higher is more similar).
Raises¶
ValueError If method is not recognized.
Source code in src/dosemetrics/metrics/advanced_dvh.py
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 | |
Usage Examples¶
Computing a Basic DVH¶
from dosemetrics import Dose, Structure
from dosemetrics.metrics.dvh import compute_dvh
dose = Dose.from_nifti("dose.nii.gz")
ptv = Structure.from_nifti("ptv.nii.gz", name="PTV")
dose_bins, volumes = compute_dvh(dose, ptv)
Conformity and Homogeneity Metrics¶
from dosemetrics.metrics.conformity import (
compute_conformity_index, # ICRU CI = V_target_rx / V_rx
compute_rtog_conformity_index, # RTOG CI = V_rx / V_target
compute_paddick_conformity_index, # Paddick / van't Riet CI = (V_target_rx)² / (V_target × V_rx)
compute_conformity_number, # van't Riet CN — same formula as Paddick CI
compute_coverage, # V_target_rx / V_target
compute_spillage, # (V_rx - V_target_rx) / V_rx
compute_prescription_mae, # mean |dose - prescription| in target
)
from dosemetrics.metrics.homogeneity import (
compute_homogeneity_index, # ICRU HI = (D2 - D98) / D50
compute_gradient_index, # Paddick-Lippitz GI = V_50% / V_100%
compute_dose_homogeneity, # coefficient of variation
compute_uniformity_index, # UI = 1 - (Dmax - Dmin) / Dref
)
prescription = 60.0 # Gy
ci_icru = compute_conformity_index(dose, ptv, prescription)
ci_rtog = compute_rtog_conformity_index(dose, ptv, prescription)
ci_pad = compute_paddick_conformity_index(dose, ptv, prescription)
hi = compute_homogeneity_index(dose, ptv)
gi = compute_gradient_index(dose, ptv, prescription)
rx_mae = compute_prescription_mae(dose, ptv, prescription)
print(f"ICRU CI: {ci_icru:.3f}")
print(f"RTOG CI: {ci_rtog:.3f}")
print(f"Paddick CI: {ci_pad:.3f}")
print(f"HI (ICRU 83): {hi:.3f}")
print(f"Gradient Index: {gi:.2f}")
print(f"Prescription MAE: {rx_mae:.2f} Gy")
DVH Comparison Metrics¶
from dosemetrics.metrics.dvh import compute_dvh_score, compute_dvh_auc
from dosemetrics.metrics.dose_comparison import (
compute_normalized_mae,
compute_variance_of_laplacian,
)
# DVH Score: average |D1|, |D95|, |D99| difference (Gy)
score = compute_dvh_score(dose_reference, dose_evaluated, ptv)
# DVH AUC: integral of DVH curve, normalised to [0, 1]
auc = compute_dvh_auc(dose, ptv, normalize=True)
# Normalized MAE with high-dose masking
n_mae = compute_normalized_mae(
dose_reference,
dose_evaluated,
normalization_value=60.0,
dose_threshold_gy=5.0,
)
# Dose sharpness (Variance of Laplacian)
vol = compute_variance_of_laplacian(dose)
print(f"DVH Score: {score:.2f} Gy")
print(f"DVH AUC: {auc:.3f}")
print(f"Normalized MAE: {n_mae:.4f}")
print(f"VoL (sharpness): {vol:.4f}")
Dose Statistics¶
from dosemetrics.metrics.dvh import compute_dose_statistics
stats = compute_dose_statistics(dose, ptv)
print(f"Mean dose: {stats['mean_dose']:.2f} Gy")
print(f"D95: {stats['D95']:.2f} Gy")
print(f"D2 (near-max): {stats['D02']:.2f} Gy")
See Also¶
- I/O Module — Loading and saving data
- Utils Module — Plotting and compliance checking
- User Guide: Quality Metrics
- User Guide: DVH Analysis