Skip to content

DVH: Contours lying on dose-voxel edges produce “Empty DVH” #389

@SaharYaz

Description

@SaharYaz

When a structure’s contour lies exactly on a dose voxel edge, the DVH mask generation treats all voxel centres as outside the polygon, yielding an empty mask. For small/degenerate ROIs (e.g., single-voxel cases), _calculate_dvh then returns an “Empty DVH” (single zero bin) instead of a non-empty histogram, and the structure volume is not recorded in the final bin.

I've made a test that reproduces this issue.

def test_histogram_preserves_final_bin(self):
    """Regression test for maxdose computation."""
    # Create minimal RT Dose dataset with max dose slightly above an
    # integer bin boundary so rounding down would truncate it.
    dose = Dataset()
    dose.SOPClassUID = "1.2.840.10008.5.1.4.1.1.481.2"
    dose.SOPInstanceUID = generate_uid()
    dose.Modality = "RTDOSE"
    dose.Rows = 1
    dose.Columns = 1
    dose.NumberOfFrames = 1
    dose.GridFrameOffsetVector = [0.0]
    dose.FrameIncrementPointer = (0x3004, 0x000c)
    dose.ImagePositionPatient = [0.0, 0.0, 0.0]
    dose.ImageOrientationPatient = [1, 0, 0, 0, 1, 0]
    dose.PixelSpacing = [1.0, 1.0]
    dose.SamplesPerPixel = 1
    dose.PhotometricInterpretation = "MONOCHROME2"
    dose.BitsAllocated = 32
    dose.BitsStored = 32
    dose.HighBit = 31
    dose.PixelRepresentation = 0
    dose.DoseUnits = "GY"
    dose.DoseType = "PHYSICAL"
    dose.DoseSummationType = "PLAN"
    dose.DoseGridScaling = 0.00001

    arr = numpy.array([[[53001]]], dtype=numpy.uint32)
    dose.PixelData = arr.tobytes()

    file_meta = FileMetaDataset()
    file_meta.FileMetaInformationVersion = b"\x00\x01"
    file_meta.MediaStorageSOPClassUID = dose.SOPClassUID
    file_meta.MediaStorageSOPInstanceUID = dose.SOPInstanceUID
    file_meta.TransferSyntaxUID = "1.2.840.10008.1.2"
    dose.file_meta = file_meta
    dose.is_implicit_VR = True
    dose.is_little_endian = True

    # Define a simple square structure covering the single dose voxel
    structure = {
        "id": 1,
        "name": "square",
        "thickness": 1,
        "planes": {
            0.0: [
                {
                    "type": "CLOSED_PLANAR",
                    "num_points": 4,
                    "data": [
                        [0.0, 0.0, 0.0],
                        [1.0, 0.0, 0.0],
                        [1.0, 1.0, 0.0],
                        [0.0, 1.0, 0.0],
                    ],
                }
            ]
        },
    }

    dvh_data = dvhcalc._calculate_dvh(structure, dicomparser.DicomParser(dose))
    self.assertEqual(dvh_data.histogram.size, 54)
    # # Final bin should contain volume of single 1×1×1 mm voxel = 0.001 cm³
    expected_vol = (
        dose.PixelSpacing[0]
        * dose.PixelSpacing[1]
        * structure["thickness"]
    ) / 1000.0
    self.assertAlmostEqual(dvh_data.histogram[-1], expected_vol)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions