Skip to content

Commit 1556453

Browse files
authored
Merge pull request #325 from ImagingDataCommons/bug/report_with_no_measurements
Disallow measurement report with no measurement groups
2 parents a4dfcb5 + d93c3f2 commit 1556453

2 files changed

Lines changed: 145 additions & 66 deletions

File tree

src/highdicom/sr/templates.py

Lines changed: 85 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4269,31 +4269,46 @@ def __init__(
42694269
PlanarROIMeasurementsAndQualitativeEvaluations |
42704270
VolumetricROIMeasurementsAndQualitativeEvaluations
42714271
)
4272-
if imaging_measurements is not None:
4273-
measurement_types = (
4274-
PlanarROIMeasurementsAndQualitativeEvaluations,
4275-
VolumetricROIMeasurementsAndQualitativeEvaluations,
4276-
MeasurementsAndQualitativeEvaluations,
4272+
4273+
# Since only imaging meansurements are currently supported, at least
4274+
# one is required. This could be relaxed in the future if evaluations
4275+
# or derived measurements (rows 10 or 12 of the TID1500 table) are
4276+
# supported
4277+
if imaging_measurements is None:
4278+
raise TypeError(
4279+
"Argument 'imaging_measurements' is required."
42774280
)
4278-
container_item = ContainerContentItem(
4279-
name=codes.DCM.ImagingMeasurements,
4280-
relationship_type=RelationshipTypeValues.CONTAINS
4281+
4282+
if len(imaging_measurements) == 0:
4283+
raise ValueError(
4284+
"Argument 'imaging_measurements' must contain at least "
4285+
"one item."
42814286
)
4282-
container_item.ContentSequence = ContentSequence()
4283-
for measurements in imaging_measurements:
4284-
if not isinstance(measurements, measurement_types):
4285-
raise TypeError(
4286-
'Measurements must have one of the following types: '
4287-
'"{}"'.format(
4288-
'", "'.join(
4289-
[
4290-
t.__name__
4291-
for t in measurement_types
4292-
]
4293-
)
4287+
4288+
measurement_types = (
4289+
PlanarROIMeasurementsAndQualitativeEvaluations,
4290+
VolumetricROIMeasurementsAndQualitativeEvaluations,
4291+
MeasurementsAndQualitativeEvaluations,
4292+
)
4293+
container_item = ContainerContentItem(
4294+
name=codes.DCM.ImagingMeasurements,
4295+
relationship_type=RelationshipTypeValues.CONTAINS
4296+
)
4297+
container_item.ContentSequence = ContentSequence()
4298+
for measurements in imaging_measurements:
4299+
if not isinstance(measurements, measurement_types):
4300+
raise TypeError(
4301+
'Measurements must have one of the following types: '
4302+
'"{}"'.format(
4303+
'", "'.join(
4304+
[
4305+
t.__name__
4306+
for t in measurement_types
4307+
]
42944308
)
42954309
)
4296-
container_item.ContentSequence.extend(measurements)
4310+
)
4311+
container_item.ContentSequence.extend(measurements)
42974312
item.ContentSequence.append(container_item)
42984313
super().__init__([item], is_root=True)
42994314

@@ -5119,58 +5134,63 @@ def __init__(
51195134
51205135
"""
51215136
super().__init__()
5137+
5138+
if len(datasets) == 0:
5139+
raise ValueError(
5140+
"Argument 'datasets' must contain at least one item."
5141+
)
5142+
51225143
library_item = ContainerContentItem(
51235144
name=codes.DCM.ImageLibrary,
51245145
relationship_type=RelationshipTypeValues.CONTAINS
51255146
)
51265147
library_item.ContentSequence = ContentSequence()
5127-
if datasets is not None:
5128-
groups = collections.defaultdict(list)
5129-
for ds in datasets:
5130-
modality = _get_coded_modality(ds.SOPClassUID)
5131-
image_item = ImageContentItem(
5132-
name=CodedConcept(
5133-
value='260753009',
5134-
meaning='Source',
5135-
scheme_designator='SCT'
5136-
),
5137-
referenced_sop_instance_uid=ds.SOPInstanceUID,
5138-
referenced_sop_class_uid=ds.SOPClassUID,
5139-
relationship_type=RelationshipTypeValues.CONTAINS
5148+
groups = collections.defaultdict(list)
5149+
for ds in datasets:
5150+
modality = _get_coded_modality(ds.SOPClassUID)
5151+
image_item = ImageContentItem(
5152+
name=CodedConcept(
5153+
value='260753009',
5154+
meaning='Source',
5155+
scheme_designator='SCT'
5156+
),
5157+
referenced_sop_instance_uid=ds.SOPInstanceUID,
5158+
referenced_sop_class_uid=ds.SOPClassUID,
5159+
relationship_type=RelationshipTypeValues.CONTAINS
5160+
)
5161+
descriptors = ImageLibraryEntryDescriptors(ds)
5162+
5163+
image_item.ContentSequence = ContentSequence()
5164+
image_item.ContentSequence.extend(descriptors)
5165+
if 'FrameOfReferenceUID' in ds:
5166+
# Only type 1 attributes
5167+
shared_descriptors = (
5168+
modality,
5169+
ds.FrameOfReferenceUID,
51405170
)
5141-
descriptors = ImageLibraryEntryDescriptors(ds)
5142-
5143-
image_item.ContentSequence = ContentSequence()
5144-
image_item.ContentSequence.extend(descriptors)
5145-
if 'FrameOfReferenceUID' in ds:
5146-
# Only type 1 attributes
5147-
shared_descriptors = (
5148-
modality,
5149-
ds.FrameOfReferenceUID,
5150-
)
5151-
else:
5152-
shared_descriptors = (
5153-
modality,
5154-
)
5155-
groups[shared_descriptors].append(image_item)
5156-
5157-
for shared_descriptors, image_items in groups.items():
5158-
image = image_items[0]
5159-
group_item = ContainerContentItem(
5160-
name=codes.DCM.ImageLibraryGroup,
5161-
relationship_type=RelationshipTypeValues.CONTAINS
5171+
else:
5172+
shared_descriptors = (
5173+
modality,
51625174
)
5163-
group_item.ContentSequence = ContentSequence()
5164-
5165-
if 'FrameOfReferenceUID' in image:
5166-
group_item.ContentSequence.append(
5167-
UIDRefContentItem(
5168-
name=codes.DCM.FrameOfReferenceUID,
5169-
value=shared_descriptors[1],
5170-
relationship_type=RelationshipTypeValues.HAS_ACQ_CONTEXT # noqa: E501
5171-
)
5175+
groups[shared_descriptors].append(image_item)
5176+
5177+
for shared_descriptors, image_items in groups.items():
5178+
image = image_items[0]
5179+
group_item = ContainerContentItem(
5180+
name=codes.DCM.ImageLibraryGroup,
5181+
relationship_type=RelationshipTypeValues.CONTAINS
5182+
)
5183+
group_item.ContentSequence = ContentSequence()
5184+
5185+
if 'FrameOfReferenceUID' in image:
5186+
group_item.ContentSequence.append(
5187+
UIDRefContentItem(
5188+
name=codes.DCM.FrameOfReferenceUID,
5189+
value=shared_descriptors[1],
5190+
relationship_type=RelationshipTypeValues.HAS_ACQ_CONTEXT # noqa: E501
51725191
)
5173-
group_item.ContentSequence.extend(image_items)
5192+
)
5193+
group_item.ContentSequence.extend(image_items)
51745194
if len(group_item) > 0:
51755195
library_item.ContentSequence.append(group_item)
51765196

tests/test_sr.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3851,6 +3851,18 @@ def test_from_sequence(self):
38513851
assert len(qualitative_evaluations) == 1
38523852
assert isinstance(qualitative_evaluations[0], QualitativeEvaluation)
38533853

3854+
def test_construction_none(self):
3855+
# Since only imaging meansurements are currently supported, at least
3856+
# one is required. This could be relaxed in the future if evaluations
3857+
# or derived measurements (rows 10 or 12 of the TID1500 table) are
3858+
# supported
3859+
msg = ("Argument 'imaging_measurements' is required.")
3860+
with pytest.raises(TypeError, match=msg):
3861+
_ = MeasurementReport(
3862+
observation_context=self._observation_context,
3863+
procedure_reported=self._procedure_reported,
3864+
)
3865+
38543866

38553867
class TestEnhancedSR(unittest.TestCase):
38563868

@@ -5717,7 +5729,9 @@ def test_construction(self):
57175729

57185730
library_items = ImageLibrary([self._ref_sm_dataset])
57195731
assert len(library_items) == 1
5720-
library_group_item = library_items[0].ContentSequence[0]
5732+
library_groups = library_items[0].ContentSequence
5733+
assert len(library_groups) == 1
5734+
library_group_item = library_groups[0]
57215735
assert len(library_group_item.ContentSequence) == 1
57225736
assert library_group_item.name == codes.DCM.ImageLibraryGroup
57235737
content_item = library_group_item.ContentSequence[0]
@@ -5730,3 +5744,48 @@ def test_construction(self):
57305744
self._ref_sm_dataset.SOPInstanceUID
57315745
assert ref_sop_class_uid == \
57325746
self._ref_sm_dataset.SOPClassUID
5747+
5748+
def test_construction_multiple_groups(self):
5749+
file_path = Path(__file__)
5750+
data_dir = file_path.parent.parent.joinpath('data')
5751+
self._ref_sm_dataset = dcmread(
5752+
str(data_dir.joinpath('test_files', 'sm_image.dcm'))
5753+
)
5754+
self._ref_ct_dataset = dcmread(
5755+
str(data_dir.joinpath('test_files', 'ct_image.dcm'))
5756+
)
5757+
5758+
library_items = ImageLibrary(
5759+
[self._ref_sm_dataset, self._ref_ct_dataset]
5760+
)
5761+
assert len(library_items) == 1
5762+
library_groups = library_items[0].ContentSequence
5763+
assert len(library_groups) == 2
5764+
5765+
sm_group_item = library_items[0].ContentSequence[0]
5766+
assert len(sm_group_item.ContentSequence) == 1
5767+
assert sm_group_item.name == codes.DCM.ImageLibraryGroup
5768+
content_item = sm_group_item.ContentSequence[0]
5769+
assert isinstance(content_item, ImageContentItem)
5770+
ref_sop_instance_uid = \
5771+
content_item.ReferencedSOPSequence[0].ReferencedSOPInstanceUID
5772+
ref_sop_class_uid = \
5773+
content_item.ReferencedSOPSequence[0].ReferencedSOPClassUID
5774+
assert ref_sop_instance_uid == \
5775+
self._ref_sm_dataset.SOPInstanceUID
5776+
assert ref_sop_class_uid == \
5777+
self._ref_sm_dataset.SOPClassUID
5778+
5779+
ct_group_item = library_items[0].ContentSequence[1]
5780+
assert len(ct_group_item.ContentSequence) == 1
5781+
assert ct_group_item.name == codes.DCM.ImageLibraryGroup
5782+
content_item = ct_group_item.ContentSequence[0]
5783+
assert isinstance(content_item, ImageContentItem)
5784+
ref_sop_instance_uid = \
5785+
content_item.ReferencedSOPSequence[0].ReferencedSOPInstanceUID
5786+
ref_sop_class_uid = \
5787+
content_item.ReferencedSOPSequence[0].ReferencedSOPClassUID
5788+
assert ref_sop_instance_uid == \
5789+
self._ref_ct_dataset.SOPInstanceUID
5790+
assert ref_sop_class_uid == \
5791+
self._ref_ct_dataset.SOPClassUID

0 commit comments

Comments
 (0)