Skip to content

Commit 27fc624

Browse files
committed
test: ensure histogram preserves final bin volume
Add a unit test constructing a 1×1×1 dose grid slightly above an integer histogram boundary with a matching square contour. The test verifies that: - the histogram has 54 bins - the final bin contains the full voxel volume (1 mm³ = 0.001 cc) Without mask tolerance, the calculation collapses to a one-bin, zero-volume histogram, causing the test to fail.
1 parent 0d5729d commit 27fc624

File tree

1 file changed

+72
-1
lines changed

1 file changed

+72
-1
lines changed

tests/test_dvhcalc.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66

77
import unittest
88
import os
9-
from pydicom.dataset import Dataset
9+
from pydicom.dataset import Dataset, FileMetaDataset
1010
from pydicom.sequence import Sequence
11+
from pydicom.uid import generate_uid
1112
from numpy import arange
13+
import numpy
1214
from numpy.testing import assert_allclose
1315
from .util import fake_rtdose, fake_ss
1416
from dicompylercore import dicomparser, dvhcalc
@@ -255,6 +257,75 @@ def test_dvh_calculation_with_interpolation_between_planes(self):
255257
# Mean dose to structure
256258
self.assertAlmostEqual(dvh.mean, 6.4767105)
257259

260+
def test_histogram_preserves_final_bin(self):
261+
"""Regression test for maxdose computation."""
262+
# Create minimal RT Dose dataset with max dose slightly above an
263+
# integer bin boundary so rounding down would truncate it.
264+
dose = Dataset()
265+
dose.SOPClassUID = "1.2.840.10008.5.1.4.1.1.481.2"
266+
dose.SOPInstanceUID = generate_uid()
267+
dose.Modality = "RTDOSE"
268+
dose.Rows = 1
269+
dose.Columns = 1
270+
dose.NumberOfFrames = 1
271+
dose.GridFrameOffsetVector = [0.0]
272+
dose.FrameIncrementPointer = (0x3004, 0x000c)
273+
dose.ImagePositionPatient = [0.0, 0.0, 0.0]
274+
dose.ImageOrientationPatient = [1, 0, 0, 0, 1, 0]
275+
dose.PixelSpacing = [1.0, 1.0]
276+
dose.SamplesPerPixel = 1
277+
dose.PhotometricInterpretation = "MONOCHROME2"
278+
dose.BitsAllocated = 32
279+
dose.BitsStored = 32
280+
dose.HighBit = 31
281+
dose.PixelRepresentation = 0
282+
dose.DoseUnits = "GY"
283+
dose.DoseType = "PHYSICAL"
284+
dose.DoseSummationType = "PLAN"
285+
dose.DoseGridScaling = 0.00001
286+
287+
arr = numpy.array([[[53001]]], dtype=numpy.uint32)
288+
dose.PixelData = arr.tobytes()
289+
290+
file_meta = FileMetaDataset()
291+
file_meta.FileMetaInformationVersion = b"\x00\x01"
292+
file_meta.MediaStorageSOPClassUID = dose.SOPClassUID
293+
file_meta.MediaStorageSOPInstanceUID = dose.SOPInstanceUID
294+
file_meta.TransferSyntaxUID = "1.2.840.10008.1.2"
295+
dose.file_meta = file_meta
296+
dose.is_implicit_VR = True
297+
dose.is_little_endian = True
298+
299+
# Define a simple square structure covering the single dose voxel
300+
structure = {
301+
"id": 1,
302+
"name": "square",
303+
"thickness": 1,
304+
"planes": {
305+
0.0: [
306+
{
307+
"type": "CLOSED_PLANAR",
308+
"num_points": 4,
309+
"data": [
310+
[0.0, 0.0, 0.0],
311+
[1.0, 0.0, 0.0],
312+
[1.0, 1.0, 0.0],
313+
[0.0, 1.0, 0.0],
314+
],
315+
}
316+
]
317+
},
318+
}
319+
320+
dvh_data = dvhcalc._calculate_dvh(structure, dicomparser.DicomParser(dose))
321+
self.assertEqual(dvh_data.histogram.size, 54)
322+
# # Final bin should contain volume of single 1×1×1 mm voxel = 0.001 cm³
323+
expected_vol = (
324+
dose.PixelSpacing[0]
325+
* dose.PixelSpacing[1]
326+
* structure["thickness"]
327+
) / 1000.0
328+
self.assertAlmostEqual(dvh_data.histogram[-1], expected_vol)
258329

259330
class TestDVHCalcDecubitus(unittest.TestCase):
260331
"""Unit tests for DVH calculation in decubitus orientations."""

0 commit comments

Comments
 (0)