Skip to content

Commit 5a1f14b

Browse files
authored
Change to definition of SR evidence (#373)
Remove use of codes.SCT.Source; change behavior of collect_evidence
1 parent 026c745 commit 5a1f14b

8 files changed

Lines changed: 75 additions & 50 deletions

File tree

docs/quickstart.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ For more information see :doc:`tid1500parsing`.
585585
assert measurement.value == 10.0
586586
587587
# Access the measurement's unit
588-
assert measurement.unit == codes.UCUM.mm
588+
assert measurement.unit == codes.UCUM.Millimeter
589589
590590
# Get the diameter measurement in this group
591591
evaluation = group.get_qualitative_evaluations(

docs/tid1500.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ highdicom test data within the highdicom repository at
641641
nodule_measurement = hd.sr.Measurement(
642642
name=codes.SCT.Diameter,
643643
value=10.0,
644-
unit=codes.UCUM.mm,
644+
unit=codes.UCUM.Millimeter,
645645
)
646646
nodule_evaluation = hd.sr.QualitativeEvaluation(
647647
name=codes.DCM.LevelOfSignificance,
@@ -677,15 +677,15 @@ highdicom test data within the highdicom repository at
677677
aorta_measurement = hd.sr.Measurement(
678678
name=codes.SCT.Diameter,
679679
value=20.0,
680-
unit=codes.UCUM.mm,
680+
unit=codes.UCUM.Millimeter,
681681
)
682682
683683
# Construct the measurement group
684684
planar_group_2 = hd.sr.PlanarROIMeasurementsAndQualitativeEvaluations(
685685
referenced_region=region,
686686
tracking_identifier=aorta_roi_tracking_id,
687687
finding_type=codes.SCT.Aorta,
688-
finding_category=structure_code,
688+
finding_category=codes.SCT.AnatomicalStructure,
689689
measurements=[aorta_measurement],
690690
)
691691
@@ -715,7 +715,7 @@ highdicom test data within the highdicom repository at
715715
vol_group = hd.sr.VolumetricROIMeasurementsAndQualitativeEvaluations(
716716
referenced_volume_surface=volume_surface,
717717
tracking_identifier=volumetric_roi_tracking_id,
718-
finding_category=structure_code,
718+
finding_category=codes.SCT.AnatomicalStructure,
719719
finding_type=codes.SCT.Vertebra,
720720
measurements=[vol_measurement],
721721
)

docs/tid1500parsing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ property (returns a ``float``), and the unit with the ``unit`` property.
304304
assert measurement.value == 10.0
305305
306306
# Access the measurement's unit
307-
assert measurement.unit == codes.UCUM.mm
307+
assert measurement.unit == codes.UCUM.Millimeter
308308
309309
Additionally, the properties ``method``, ``finding_sites``, ``qualifier``,
310310
``referenced_images``, and ``derivation`` allow you to access further optional

src/highdicom/ko/sop.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,14 @@ def __init__(
138138
for tag, value in content[0].items():
139139
self[tag] = value
140140

141-
ref_items, unref_items = collect_evidence(evidence, content[0])
142-
if len(ref_items) > 0:
143-
self.CurrentRequestedProcedureEvidenceSequence = ref_items
144-
if len(ref_items) > 1:
141+
same_study_items, other_study_items = collect_evidence(
142+
evidence,
143+
content[0],
144+
study_instance_uid=self.StudyInstanceUID,
145+
)
146+
if len(same_study_items) > 0:
147+
self.CurrentRequestedProcedureEvidenceSequence = same_study_items
148+
if len(other_study_items) > 1:
145149
raise ValueError(
146150
'Key Object Selection Documents that reference instances '
147151
'from multiple studies are not supported.'

src/highdicom/sr/content.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
logger = logging.getLogger(__name__)
4444

4545

46+
# The pydicom codes.SCT.Source has an unnecessary "(attribute)" in the meaning
47+
_SOURCE = Code(value='260753009', scheme_designator='SCT', meaning='Source')
48+
49+
4650
def _check_valid_source_image_dataset(dataset: Dataset) -> None:
4751
"""Raise an error if the image is not a valid source image reference.
4852
@@ -449,7 +453,7 @@ def __init__(
449453
'1-based.'
450454
)
451455
super().__init__(
452-
name=codes.SCT.Source,
456+
name=_SOURCE,
453457
referenced_sop_class_uid=referenced_sop_class_uid,
454458
referenced_sop_instance_uid=referenced_sop_instance_uid,
455459
referenced_frame_numbers=referenced_frame_numbers,
@@ -718,7 +722,7 @@ def __init__(
718722
graphic_type: GraphicTypeValues | str,
719723
graphic_data: np.ndarray,
720724
source_image: SourceImageForRegion,
721-
purpose: CodedConcept | Code = codes.SCT.Source,
725+
purpose: CodedConcept | Code = _SOURCE,
722726
) -> None:
723727
"""
724728
Parameters
@@ -782,7 +786,7 @@ def __init__(
782786
graphic_data: np.ndarray,
783787
frame_of_reference_uid: str | UID,
784788
fiducial_uid: str | UID | None = None,
785-
purpose: CodedConcept | Code = codes.SCT.Source,
789+
purpose: CodedConcept | Code = _SOURCE,
786790
):
787791
"""
788792
Parameters

src/highdicom/sr/sop.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,11 +242,15 @@ def __init__(
242242
for tag, value in content.items():
243243
self[tag] = value
244244

245-
ref_items, unref_items = collect_evidence(evidence, content)
246-
if len(ref_items) > 0:
247-
self.CurrentRequestedProcedureEvidenceSequence = ref_items
248-
if len(unref_items) > 0 and record_evidence:
249-
self.PertinentOtherEvidenceSequence = unref_items
245+
same_study_items, other_study_items = collect_evidence(
246+
evidence,
247+
content,
248+
study_instance_uid=self.StudyInstanceUID,
249+
)
250+
if len(same_study_items) > 0:
251+
self.CurrentRequestedProcedureEvidenceSequence = same_study_items
252+
if len(other_study_items) > 0 and record_evidence:
253+
self.PertinentOtherEvidenceSequence = other_study_items
250254

251255
if requested_procedures is not None:
252256
self.ReferencedRequestSequence = requested_procedures

src/highdicom/sr/utils.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -235,22 +235,28 @@ def _create_references(
235235

236236
def collect_evidence(
237237
evidence: Sequence[Dataset],
238-
content: Dataset
238+
content: Dataset,
239+
study_instance_uid: str | None = None,
239240
) -> tuple[list[Dataset], list[Dataset]]:
240241
"""Collect evidence for a SR document.
241242
242-
Any `evidence` that is referenced in `content` via IMAGE or
243-
COMPOSITE content items will be grouped together for inclusion in the
244-
Current Requested Procedure Evidence Sequence and all remaining
245-
evidence will be grouped for potential inclusion in the Pertinent Other
246-
Evidence Sequence.
243+
Any ``evidence`` that belongs to the same study as the new SR document will
244+
be grouped together for inclusion in the Current Requested Procedure
245+
Evidence Sequence and all remaining evidence will be grouped for potential
246+
inclusion in the Pertinent Other Evidence Sequence.
247247
248248
Parameters
249249
----------
250250
evidence: List[pydicom.dataset.Dataset]
251251
Metadata of instances that serve as evidence for the SR document content
252252
content: pydicom.dataset.Dataset
253253
SR document content
254+
study_instance_uid: str
255+
Study instance UID of the SR being created. If not provided, the study
256+
instance UID of the first ``evidence`` item is taken to the study
257+
instance UID of the new SR. This is primarily for backwards
258+
compatibility: it is recommended to always explicitly provide the study
259+
instance UID.
254260
255261
Returns
256262
-------
@@ -262,10 +268,13 @@ def collect_evidence(
262268
Raises
263269
------
264270
ValueError
265-
When a SOP instance is referenced in `content` but not provided as
266-
`evidence`
271+
When a SOP instance is referenced in ``content`` but not provided as
272+
``evidence``
267273
268274
""" # noqa: E501
275+
if study_instance_uid is None and len(evidence) > 1:
276+
study_instance_uid = evidence[0].StudyInstanceUID
277+
269278
references = find_content_items(
270279
content,
271280
value_type=ValueTypeValues.IMAGE,
@@ -281,8 +290,12 @@ def collect_evidence(
281290
for ref in references
282291
}
283292
evd_uids = set()
284-
ref_group: Mapping[tuple[str, str], list[Dataset]] = defaultdict(list)
285-
unref_group: Mapping[tuple[str, str], list[Dataset]] = defaultdict(list)
293+
same_study_group: Mapping[
294+
tuple[str, str], list[Dataset]
295+
] = defaultdict(list)
296+
other_study_group: Mapping[
297+
tuple[str, str], list[Dataset]
298+
] = defaultdict(list)
286299
for evd in evidence:
287300
if evd.SOPInstanceUID in evd_uids:
288301
# Skip potential duplicates
@@ -291,10 +304,10 @@ def collect_evidence(
291304
evd_item.ReferencedSOPClassUID = evd.SOPClassUID
292305
evd_item.ReferencedSOPInstanceUID = evd.SOPInstanceUID
293306
key = (evd.StudyInstanceUID, evd.SeriesInstanceUID)
294-
if evd.SOPInstanceUID in ref_uids:
295-
ref_group[key].append(evd_item)
307+
if evd.StudyInstanceUID == study_instance_uid:
308+
same_study_group[key].append(evd_item)
296309
else:
297-
unref_group[key].append(evd_item)
310+
other_study_group[key].append(evd_item)
298311
evd_uids.add(evd.SOPInstanceUID)
299312
if not ref_uids.issubset(evd_uids):
300313
missing_uids = ref_uids.difference(evd_uids)
@@ -305,6 +318,6 @@ def collect_evidence(
305318
)
306319
)
307320

308-
ref_items = _create_references(ref_group)
309-
unref_items = _create_references(unref_group)
310-
return (ref_items, unref_items)
321+
same_study_items = _create_references(same_study_group)
322+
other_study_items = _create_references(other_study_group)
323+
return (same_study_items, other_study_items)

tests/test_sr.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4268,9 +4268,8 @@ def test_evidence_multiple_studies(self):
42684268
manufacturer=self._manufacturer
42694269
)
42704270
ref_evd_items = report.CurrentRequestedProcedureEvidenceSequence
4271-
assert len(ref_evd_items) == 2
4272-
with pytest.raises(AttributeError):
4273-
assert report.PertinentOtherEvidenceSequence
4271+
assert len(ref_evd_items) == 1
4272+
assert len(report.PertinentOtherEvidenceSequence) == 1
42744273

42754274
evidence = report.get_evidence()
42764275
assert len(evidence) == 2
@@ -4299,7 +4298,8 @@ def test_evidence_multiple_studies(self):
42994298

43004299
def test_current_and_other_evidence(self):
43014300
ref_dataset2 = deepcopy(self._ref_dataset)
4302-
ref_dataset2.SeriesInstanceUID = '1.2.3'
4301+
ref_dataset2.StudyInstanceUID = '1.2.3.4.5'
4302+
ref_dataset2.SeriesInstanceUID = '1.2.3.4'
43034303
ref_dataset2.SOPInstanceUID = '1.2.3'
43044304

43054305
report = Comprehensive3DSR(
@@ -4313,10 +4313,10 @@ def test_current_and_other_evidence(self):
43134313
institutional_department_name=self._department_name,
43144314
manufacturer=self._manufacturer
43154315
)
4316-
ref_evd_items = report.CurrentRequestedProcedureEvidenceSequence
4317-
assert len(ref_evd_items) == 1
4318-
unref_evd_items = report.PertinentOtherEvidenceSequence
4319-
assert len(unref_evd_items) == 1
4316+
same_study_items = report.CurrentRequestedProcedureEvidenceSequence
4317+
assert len(same_study_items) == 1
4318+
other_study_items = report.PertinentOtherEvidenceSequence
4319+
assert len(other_study_items) == 1
43204320

43214321
evidence = report.get_evidence()
43224322
assert len(evidence) == 2
@@ -4522,10 +4522,10 @@ def test_construction(self):
45224522
assert report.Manufacturer == self._manufacturer
45234523
assert report.Modality == 'SR'
45244524

4525+
ref_evd_items = report.CurrentRequestedProcedureEvidenceSequence
4526+
assert len(ref_evd_items) == 1
45254527
with pytest.raises(AttributeError):
4526-
assert report.CurrentRequestedProcedureEvidenceSequence
4527-
unref_evd_items = report.PertinentOtherEvidenceSequence
4528-
assert len(unref_evd_items) == 1
4528+
assert report.PertinentOtherEvidenceSequence
45294529

45304530
def test_construction_content_is_sequence(self):
45314531
report = Comprehensive3DSR(
@@ -4554,10 +4554,10 @@ def test_construction_content_is_sequence(self):
45544554
assert report.Manufacturer == self._manufacturer
45554555
assert report.Modality == 'SR'
45564556

4557+
ref_evd_items = report.CurrentRequestedProcedureEvidenceSequence
4558+
assert len(ref_evd_items) == 1
45574559
with pytest.raises(AttributeError):
4558-
assert report.CurrentRequestedProcedureEvidenceSequence
4559-
unref_evd_items = report.PertinentOtherEvidenceSequence
4560-
assert len(unref_evd_items) == 1
4560+
assert report.PertinentOtherEvidenceSequence
45614561

45624562
def test_from_dataset(self):
45634563
report = Comprehensive3DSR.from_dataset(self._sr_document)
@@ -4586,8 +4586,8 @@ def test_evidence_duplication(self):
45864586
institutional_department_name=self._department_name,
45874587
manufacturer=self._manufacturer
45884588
)
4589-
unref_evd_items = report.PertinentOtherEvidenceSequence
4590-
assert len(unref_evd_items) == 1
4589+
ref_evd_items = report.CurrentRequestedProcedureEvidenceSequence
4590+
assert len(ref_evd_items) == 1
45914591

45924592

45934593
class TestSRUtilities(unittest.TestCase):

0 commit comments

Comments
 (0)