From 0a51e4143d80f1658e48f8464a52ed51e19493f1 Mon Sep 17 00:00:00 2001 From: "Markus D. Herrmann" Date: Sun, 2 May 2021 15:24:57 -0400 Subject: [PATCH] Fix referencing of current requested procedure or pertinent other evidence (#67) * Fix recording of evidence in structured reports * Increase package version * Assert that evidence is provided for references * Add test for report referencing multiple studies --- src/highdicom/sr/sop.py | 326 ++++++++++++++++++++++++++------------- src/highdicom/version.py | 2 +- tests/test_sr.py | 286 +++++++++++++++++++++++----------- 3 files changed, 412 insertions(+), 202 deletions(-) diff --git a/src/highdicom/sr/sop.py b/src/highdicom/sr/sop.py index 51bfb7e0..060b3965 100644 --- a/src/highdicom/sr/sop.py +++ b/src/highdicom/sr/sop.py @@ -3,10 +3,10 @@ import datetime import logging from collections import defaultdict -from typing import Any, Dict, List, Optional, Sequence, Union +from typing import Any, Mapping, List, Optional, Sequence, Tuple, Union -from pydicom.sr.coding import Code from pydicom.dataset import Dataset +from pydicom.sr.coding import Code from pydicom.valuerep import DT from pydicom._storage_sopclass_uids import ( ComprehensiveSRStorage, @@ -28,30 +28,30 @@ class _SR(SOPClass): """Abstract base class for Structured Report (SR) SOP classes.""" def __init__( - self, - evidence: Sequence[Dataset], - content: Dataset, - series_instance_uid: str, - series_number: int, - sop_instance_uid: str, - sop_class_uid: str, - instance_number: int, - manufacturer: Optional[str] = None, - is_complete: bool = False, - is_final: bool = False, - is_verified: bool = False, - institution_name: Optional[str] = None, - institutional_department_name: Optional[str] = None, - verifying_observer_name: Optional[str] = None, - verifying_organization: Optional[str] = None, - performed_procedure_codes: Optional[ - Sequence[Union[Code, CodedConcept]] - ] = None, - requested_procedures: Optional[Sequence[Dataset]] = None, - previous_versions: Optional[Sequence[Dataset]] = None, - record_evidence: bool = True, - **kwargs: Any - ) -> None: + self, + evidence: Sequence[Dataset], + content: Dataset, + series_instance_uid: str, + series_number: int, + sop_instance_uid: str, + sop_class_uid: str, + instance_number: int, + manufacturer: Optional[str] = None, + is_complete: bool = False, + is_final: bool = False, + is_verified: bool = False, + institution_name: Optional[str] = None, + institutional_department_name: Optional[str] = None, + verifying_observer_name: Optional[str] = None, + verifying_organization: Optional[str] = None, + performed_procedure_codes: Optional[ + Sequence[Union[Code, CodedConcept]] + ] = None, + requested_procedures: Optional[Sequence[Dataset]] = None, + previous_versions: Optional[Sequence[Dataset]] = None, + record_evidence: bool = True, + **kwargs: Any + ) -> None: """ Parameters ---------- @@ -104,18 +104,21 @@ def __init__( previous_versions: List[pydicom.dataset.Dataset], optional Instances representing previous versions of the SR document record_evidence: bool, optional - Whether provided `evidence` should be recorded, i.e. included - in Current Requested Procedure Evidence Sequence or Pertinent - Other Evidence Sequence (default: ``True``) + Whether provided `evidence` should be recorded (i.e. included + in Pertinent Other Evidence Sequence) even if not referenced by + content items in the document tree (default: ``True``) **kwargs: Any, optional Additional keyword arguments that will be passed to the constructor of `highdicom.base.SOPClass` - Note - ---- - Each dataset in `evidence` must be part of the same study. + Raises + ------ + ValueError + When no `evidence` is provided """ # noqa: E501 + if len(evidence) == 0: + raise ValueError('No evidence was provided.') super().__init__( study_instance_uid=evidence[0].StudyInstanceUID, series_instance_uid=series_instance_uid, @@ -182,67 +185,168 @@ def __init__( for tag, value in content.items(): self[tag] = value - evd_collection: Dict[str, List[Dataset]] = defaultdict(list) - for evd in evidence: - if evd.StudyInstanceUID != evidence[0].StudyInstanceUID: - raise ValueError( - 'Referenced data sets must all belong to the same study.' - ) - evd_instance_item = Dataset() - evd_instance_item.ReferencedSOPClassUID = evd.SOPClassUID - evd_instance_item.ReferencedSOPInstanceUID = evd.SOPInstanceUID - evd_collection[evd.SeriesInstanceUID].append( - evd_instance_item - ) - evd_study_item = Dataset() - evd_study_item.StudyInstanceUID = evidence[0].StudyInstanceUID - evd_study_item.ReferencedSeriesSequence = [] - for evd_series_uid, evd_instance_items in evd_collection.items(): - evd_series_item = Dataset() - evd_series_item.SeriesInstanceUID = evd_series_uid - evd_series_item.ReferencedSOPSequence = evd_instance_items - evd_study_item.ReferencedSeriesSequence.append(evd_series_item) + ref_items, unref_items = self._collect_evidence(evidence, content) + if len(ref_items) > 0: + self.CurrentRequestedProcedureEvidenceSequence = ref_items + if len(unref_items) > 0 and record_evidence: + self.PertinentOtherEvidenceSequence = unref_items + if requested_procedures is not None: self.ReferencedRequestSequence = requested_procedures - self.CurrentRequestedProcedureEvidenceSequence = [evd_study_item] - else: - if record_evidence: - self.PertinentOtherEvidenceSequence = [evd_study_item] if previous_versions is not None: - pre_collection: Dict[str, List[Dataset]] = defaultdict(list) - for pre in previous_versions: - if pre.StudyInstanceUID != evidence[0].StudyInstanceUID: - raise ValueError( - 'Previous version data sets must belong to the ' - 'same study.' - ) - pre_instance_item = Dataset() - pre_instance_item.ReferencedSOPClassUID = pre.SOPClassUID - pre_instance_item.ReferencedSOPInstanceUID = pre.SOPInstanceUID - pre_collection[pre.SeriesInstanceUID].append( - pre_instance_item - ) - pre_study_item = Dataset() - pre_study_item.StudyInstanceUID = pre.StudyInstanceUID - pre_study_item.ReferencedSeriesSequence = [] - for pre_series_uid, pre_instance_items in pre_collection.items(): - pre_series_item = Dataset() - pre_series_item.SeriesInstanceUID = pre_series_uid - pre_series_item.ReferencedSOPSequence = pre_instance_items - pre_study_item.ReferencedSeriesSequence.append(pre_series_item) - self.PredecessorDocumentsSequence = [pre_study_item] + pre_items = self._collect_predecessors(previous_versions) + self.PredecessorDocumentsSequence = pre_items if performed_procedure_codes is not None: self.PerformedProcedureCodeSequence = performed_procedure_codes else: self.PerformedProcedureCodeSequence = [] - # TODO + # TODO: unclear how this would work self.ReferencedPerformedProcedureStepSequence: List[Dataset] = [] self.copy_patient_and_study_information(evidence[0]) + @staticmethod + def _create_references( + collection: Mapping[Tuple[str, str], Sequence[Dataset]] + ) -> List[Dataset]: + """Create references. + + Parameters + ---------- + collection: Mapping[Tuple[str, str], Sequence[pydicom.dataset.Dataset]] + Items of the Referenced SOP Sequence grouped by Study and Series + Instance UID + + Returns + ------- + List[pydicom.dataset.Dataset] + Items containing the Study Instance UID and the + Referenced Series Sequence attributes + + """ + study_collection: Mapping[str, List[Dataset]] = defaultdict(list) + for (study_uid, series_uid), instance_items in collection.items(): + series_item = Dataset() + series_item.SeriesInstanceUID = series_uid + series_item.ReferencedSOPSequence = instance_items + study_collection[study_uid].append(series_item) + + ref_items = [] + for study_uid, series_items in study_collection.items(): + study_item = Dataset() + study_item.StudyInstanceUID = study_uid + study_item.ReferencedSeriesSequence = series_items + ref_items.append(study_item) + + return ref_items + + def _collect_predecessors( + self, + previous_versions: Sequence[Dataset] + ) -> List[Dataset]: + """Collect predecessors of the SR document. + + Parameters + ---------- + previous_versions: List[pydicom.dataset.Dataset] + Metadata of instances that represent previous versions of the + SR document content + + Returns + ------- + List[pydicom.dataset.Dataset] + Items of the Predecessor Documents Sequence + + """ + group: Mapping[Tuple[str, str], List[Dataset]] = defaultdict(list) + for pre in previous_versions: + pre_instance_item = Dataset() + pre_instance_item.ReferencedSOPClassUID = pre.SOPClassUID + pre_instance_item.ReferencedSOPInstanceUID = pre.SOPInstanceUID + key = (pre.StudyInstanceUID, pre.SeriesInstanceUID) + group[key].append(pre_instance_item) + return self._create_references(group) + + def _collect_evidence( + self, + evidence: Sequence[Dataset], + content: Dataset + ) -> Tuple[List[Dataset], List[Dataset]]: + """Collect evidence for the SR document. + + Any `evidence` that is referenced in `content` via IMAGE or + COMPOSITE content items will be grouped together for inclusion in the + Current Requested Procedure Evidence Sequence and all remaining + evidence will be grouped for potential inclusion in the Pertinent Other + Evidence Sequence. + + Parameters + ---------- + evidence: List[pydicom.dataset.Dataset] + Metadata of instances that serve as evidence for the SR document + content + content: pydicom.dataset.Dataset + SR document content + + Returns + ------- + Tuple[List[pydicom.dataset.Dataset], List[pydicom.dataset.Dataset]] + Items of the Current Requested Procedure Evidence Sequence and the + Pertinent Other Evidence Sequence + + Raises + ------ + ValueError + When a SOP instance is referenced in `content` but not provided as + `evidence` + + """ # noqa + references = find_content_items( + content, + value_type=ValueTypeValues.IMAGE, + recursive=True + ) + references += find_content_items( + content, + value_type=ValueTypeValues.COMPOSITE, + recursive=True + ) + ref_uids = set([ + ref.ReferencedSOPSequence[0].ReferencedSOPInstanceUID + for ref in references + ]) + evd_uids = set() + ref_group: Mapping[Tuple[str, str], List[Dataset]] = defaultdict(list) + unref_group: Mapping[Tuple[str, str], List[Dataset]] = defaultdict(list) + for evd in evidence: + if evd.SOPInstanceUID in evd_uids: + # Skip potential duplicates + continue + evd_item = Dataset() + evd_item.ReferencedSOPClassUID = evd.SOPClassUID + evd_item.ReferencedSOPInstanceUID = evd.SOPInstanceUID + key = (evd.StudyInstanceUID, evd.SeriesInstanceUID) + if evd.SOPInstanceUID in ref_uids: + ref_group[key].append(evd_item) + else: + unref_group[key].append(evd_item) + evd_uids.add(evd.SOPInstanceUID) + if not(ref_uids.issubset(evd_uids)): + missing_uids = ref_uids.difference(evd_uids) + raise ValueError( + 'No evidence was provided for the following SOP instances, ' + 'which are referenced in the document content: "{}"'.format( + '", "'.join(missing_uids) + ) + ) + + ref_items = self._create_references(ref_group) + unref_items = self._create_references(unref_group) + return (ref_items, unref_items) + class EnhancedSR(_SR): @@ -254,29 +358,29 @@ class EnhancedSR(_SR): """ def __init__( - self, - evidence: Sequence[Dataset], - content: Dataset, - series_instance_uid: str, - series_number: int, - sop_instance_uid: str, - instance_number: int, - manufacturer: Optional[str] = None, - is_complete: bool = False, - is_final: bool = False, - is_verified: bool = False, - institution_name: Optional[str] = None, - institutional_department_name: Optional[str] = None, - verifying_observer_name: Optional[str] = None, - verifying_organization: Optional[str] = None, - performed_procedure_codes: Optional[ - Sequence[Union[Code, CodedConcept]] - ] = None, - requested_procedures: Optional[Sequence[Dataset]] = None, - previous_versions: Optional[Sequence[Dataset]] = None, - record_evidence: bool = True, - **kwargs: Any - ) -> None: + self, + evidence: Sequence[Dataset], + content: Dataset, + series_instance_uid: str, + series_number: int, + sop_instance_uid: str, + instance_number: int, + manufacturer: Optional[str] = None, + is_complete: bool = False, + is_final: bool = False, + is_verified: bool = False, + institution_name: Optional[str] = None, + institutional_department_name: Optional[str] = None, + verifying_observer_name: Optional[str] = None, + verifying_organization: Optional[str] = None, + performed_procedure_codes: Optional[ + Sequence[Union[Code, CodedConcept]] + ] = None, + requested_procedures: Optional[Sequence[Dataset]] = None, + previous_versions: Optional[Sequence[Dataset]] = None, + record_evidence: bool = True, + **kwargs: Any + ) -> None: """ Parameters ---------- @@ -327,9 +431,9 @@ def __init__( previous_versions: List[pydicom.dataset.Dataset], optional Instances representing previous versions of the SR document record_evidence: bool, optional - Whether provided `evidence` should be recorded, i.e. included - in Current Requested Procedure Evidence Sequence or Pertinent - Other Evidence Sequence (default: ``True``) + Whether provided `evidence` should be recorded (i.e. included + in Pertinent Other Evidence Sequence) even if not referenced by + content items in the document tree (default: ``True``) **kwargs: Any, optional Additional keyword arguments that will be passed to the constructor of `highdicom.base.SOPClass` @@ -455,9 +559,9 @@ def __init__( previous_versions: List[pydicom.dataset.Dataset], optional Instances representing previous versions of the SR document record_evidence: bool, optional - Whether provided `evidence` should be recorded, i.e. included - in Current Requested Procedure Evidence Sequence or Pertinent - Other Evidence Sequence (default: ``True``) + Whether provided `evidence` should be recorded (i.e. included + in Pertinent Other Evidence Sequence) even if not referenced by + content items in the document tree (default: ``True``) **kwargs: Any, optional Additional keyword arguments that will be passed to the constructor of `highdicom.base.SOPClass` @@ -583,9 +687,9 @@ def __init__( previous_versions: List[pydicom.dataset.Dataset], optional Instances representing previous versions of the SR document record_evidence: bool, optional - Whether provided `evidence` should be recorded, i.e. included - in Current Requested Procedure Evidence Sequence or Pertinent - Other Evidence Sequence (default: ``True``) + Whether provided `evidence` should be recorded (i.e. included + in Pertinent Other Evidence Sequence) even if not referenced by + content items in the document tree (default: ``True``) **kwargs: Any, optional Additional keyword arguments that will be passed to the constructor of `highdicom.base.SOPClass` diff --git a/src/highdicom/version.py b/src/highdicom/version.py index ef7eb44d..8411e551 100644 --- a/src/highdicom/version.py +++ b/src/highdicom/version.py @@ -1 +1 @@ -__version__ = '0.6.0' +__version__ = '0.6.1' diff --git a/tests/test_sr.py b/tests/test_sr.py index 72d1daf4..c600078f 100644 --- a/tests/test_sr.py +++ b/tests/test_sr.py @@ -56,7 +56,6 @@ EnhancedSR, ) from highdicom.sr.templates import ( - DEFAULT_LANGUAGE, AlgorithmIdentification, DeviceObserverIdentifyingAttributes, Measurement, @@ -486,9 +485,9 @@ def test_container_item_construction(self): ) assert i.ValueType == 'CONTAINER' assert i.ConceptNameCodeSequence[0] == name - content_template_item = i.ContentTemplateSequence[0] - assert content_template_item.TemplateIdentifier == tid - assert content_template_item.MappingResource == 'DCMR' + template_item = i.ContentTemplateSequence[0] + assert template_item.TemplateIdentifier == tid + assert template_item.MappingResource == 'DCMR' assert i.ContinuityOfContent == 'CONTINUOUS' def test_composite_item_construction(self): @@ -2099,47 +2098,35 @@ def setUp(self): tracking_identifier=self._tracking_identifier, referenced_region=self._region ) - self._measurement_report = MeasurementReport( + + def test_construction(self): + measurement_report = MeasurementReport( observation_context=self._observation_context, procedure_reported=self._procedure_reported, imaging_measurements=[self._measurements] ) - - def test_container(self): - item = self._measurement_report[0] + item = measurement_report[0] assert len(item.ContentSequence) == 8 - subitem = item.ContentTemplateSequence[0] - assert subitem.TemplateIdentifier == '1500' - - def test_language(self): - item = self._measurement_report[0].ContentSequence[0] - assert item.ConceptNameCodeSequence[0].CodeValue == '121049' - assert item.ConceptCodeSequence[0] == DEFAULT_LANGUAGE - - def test_observation_context(self): - item = self._measurement_report[0].ContentSequence[1] - assert item.ConceptNameCodeSequence[0].CodeValue == '121005' - item = self._measurement_report[0].ContentSequence[2] - assert item.ConceptNameCodeSequence[0].CodeValue == '121008' - item = self._measurement_report[0].ContentSequence[3] - assert item.ConceptNameCodeSequence[0].CodeValue == '121005' - item = self._measurement_report[0].ContentSequence[4] - assert item.ConceptNameCodeSequence[0].CodeValue == '121012' - def test_procedure_reported(self): - item = self._measurement_report[0].ContentSequence[5] - assert item.ConceptNameCodeSequence[0].CodeValue == '121058' - assert item.ConceptCodeSequence[0] == self._procedure_reported - - def test_image_library(self): - item = self._measurement_report[0].ContentSequence[6] - assert item.ConceptNameCodeSequence[0].CodeValue == '111028' - - def test_imaging_measurements(self): - item = self._measurement_report[0].ContentSequence[7] - assert item.ConceptNameCodeSequence[0].CodeValue == '126010' - subitem = item.ContentSequence[0] - assert subitem.ConceptNameCodeSequence[0].CodeValue == '125007' + template_item = item.ContentTemplateSequence[0] + assert template_item.TemplateIdentifier == '1500' + + content_item_expectations = [ + (0, '121049'), + (1, '121005'), + (2, '121008'), + (3, '121005'), + (4, '121012'), + # Procedure reported + (5, '121058'), + # Image library + (6, '111028'), + # Imaging measurements + (7, '126010'), + ] + for index, value in content_item_expectations: + content_item = item.ContentSequence[index] + assert content_item.ConceptNameCodeSequence[0].CodeValue == value class TestEnhancedSR(unittest.TestCase): @@ -2166,7 +2153,7 @@ def setUp(self): self._sop_instance_uid = generate_uid() self._instance_number = 4 self._institution_name = 'institute' - self._institutional_department_name = 'department' + self._department_name = 'department' self._manufacturer = 'manufacturer' observer_person_context = ObserverContext( @@ -2239,7 +2226,8 @@ def setUp(self): imaging_measurements=imaging_measurements )[0] - self._report = EnhancedSR( + def test_construction(self): + report = EnhancedSR( evidence=[self._ref_dataset], content=self._content, series_instance_uid=self._series_instance_uid, @@ -2247,12 +2235,24 @@ def setUp(self): sop_instance_uid=self._sop_instance_uid, instance_number=self._instance_number, institution_name=self._institution_name, - institutional_department_name=self._institutional_department_name, + institutional_department_name=self._department_name, manufacturer=self._manufacturer ) + assert report.SOPClassUID == '1.2.840.10008.5.1.4.1.1.88.22' - def test_sop_class_uid(self): - assert self._report.SOPClassUID == '1.2.840.10008.5.1.4.1.1.88.22' + def test_evidence_missing(self): + with pytest.raises(ValueError): + EnhancedSR( + evidence=[], + content=self._content, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + institution_name=self._institution_name, + institutional_department_name=self._department_name, + manufacturer=self._manufacturer + ) class TestComprehensiveSR(unittest.TestCase): @@ -2270,8 +2270,9 @@ def setUp(self): self._sop_instance_uid = generate_uid() self._instance_number = 4 self._institution_name = 'institute' - self._institutional_department_name = 'department' + self._department_name = 'department' self._manufacturer = 'manufacturer' + self._procedure_reported = codes.LN.CTUnspecifiedBodyRegion observer_person_context = ObserverContext( observer_type=codes.DCM.Person, @@ -2285,10 +2286,11 @@ def setUp(self): uid=generate_uid() ) ) - observation_context = ObservationContext( + self._observation_context = ObservationContext( observer_person_context=observer_person_context, observer_device_context=observer_device_context, ) + referenced_region = ImageRegion( graphic_type=GraphicTypeValues.POLYLINE, graphic_data=np.array([ @@ -2325,25 +2327,25 @@ def setUp(self): ) ) ] - imaging_measurements = [ - PlanarROIMeasurementsAndQualitativeEvaluations( - tracking_identifier=TrackingIdentifier( - uid=generate_uid(), - identifier='Planar ROI Measurements' - ), - referenced_region=referenced_region, - finding_type=codes.SCT.SpinalCord, - measurements=measurements, - finding_sites=finding_sites - ) - ] + measurement_group = PlanarROIMeasurementsAndQualitativeEvaluations( + tracking_identifier=TrackingIdentifier( + uid=generate_uid(), + identifier='Planar ROI Measurements' + ), + referenced_region=referenced_region, + finding_type=codes.SCT.SpinalCord, + measurements=measurements, + finding_sites=finding_sites + ) + self._imaging_measurements = [measurement_group] self._content = MeasurementReport( - observation_context=observation_context, - procedure_reported=codes.LN.CTUnspecifiedBodyRegion, - imaging_measurements=imaging_measurements + observation_context=self._observation_context, + procedure_reported=self._procedure_reported, + imaging_measurements=self._imaging_measurements )[0] - self._report = ComprehensiveSR( + def test_construction(self): + report = ComprehensiveSR( evidence=[self._ref_dataset], content=self._content, series_instance_uid=self._series_instance_uid, @@ -2351,12 +2353,107 @@ def setUp(self): sop_instance_uid=self._sop_instance_uid, instance_number=self._instance_number, institution_name=self._institution_name, - institutional_department_name=self._institutional_department_name, + institutional_department_name=self._department_name, manufacturer=self._manufacturer ) + assert report.SOPClassUID == '1.2.840.10008.5.1.4.1.1.88.33' - def test_sop_class_uid(self): - assert self._report.SOPClassUID == '1.2.840.10008.5.1.4.1.1.88.33' + ref_evd_items = report.CurrentRequestedProcedureEvidenceSequence + assert len(ref_evd_items) == 1 + with pytest.raises(AttributeError): + assert report.PertinentOtherEvidenceSequence + + def test_evidence_duplication(self): + report = Comprehensive3DSR( + evidence=[self._ref_dataset, self._ref_dataset], + content=self._content, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + institution_name=self._institution_name, + institutional_department_name=self._department_name, + manufacturer=self._manufacturer + ) + ref_evd_items = report.CurrentRequestedProcedureEvidenceSequence + assert len(ref_evd_items) == 1 + + def test_evidence_missing(self): + ref_dataset = deepcopy(self._ref_dataset) + ref_dataset.SOPInstanceUID = '1.2.3.4' + with pytest.raises(ValueError): + Comprehensive3DSR( + evidence=[ref_dataset], + content=self._content, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + institution_name=self._institution_name, + institutional_department_name=self._department_name, + manufacturer=self._manufacturer + ) + + def test_evidence_multiple_studies(self): + ref_dataset = deepcopy(self._ref_dataset) + ref_dataset.StudyInstanceUID = '1.2.6' + ref_dataset.SeriesInstanceUID = '1.2.7' + ref_dataset.SOPInstanceUID = '1.2.9' + referenced_region = ImageRegion( + graphic_type=GraphicTypeValues.POLYLINE, + graphic_data=np.array([ + (65.0, 100.0), + (70.0, 100.0), + (70.0, 120.0), + (65.0, 120.0), + (65.0, 100.0), + ]), + source_image=SourceImageForRegion( + referenced_sop_class_uid=ref_dataset.SOPClassUID, + referenced_sop_instance_uid=ref_dataset.SOPInstanceUID + ) + ) + finding_sites = [ + FindingSite(anatomic_location=codes.SCT.CervicoThoracicSpine), + ] + measurements = [ + Measurement( + name=codes.SCT.AreaOfDefinedRegion, + tracking_identifier=TrackingIdentifier(uid=generate_uid()), + value=0.7, + unit=codes.UCUM.SquareMillimeter, + ) + ] + measurement_group = PlanarROIMeasurementsAndQualitativeEvaluations( + tracking_identifier=TrackingIdentifier( + uid=generate_uid(), + identifier='Planar ROI Measurements' + ), + referenced_region=referenced_region, + finding_type=codes.SCT.SpinalCord, + measurements=measurements, + finding_sites=finding_sites + ) + imaging_measurements = deepcopy(self._imaging_measurements) + imaging_measurements.append(measurement_group) + content = MeasurementReport( + observation_context=self._observation_context, + procedure_reported=self._procedure_reported, + imaging_measurements=imaging_measurements + )[0] + report = Comprehensive3DSR( + evidence=[self._ref_dataset, ref_dataset], + content=content, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + institution_name=self._institution_name, + institutional_department_name=self._department_name, + manufacturer=self._manufacturer + ) + ref_evd_items = report.CurrentRequestedProcedureEvidenceSequence + assert len(ref_evd_items) == 2 class TestComprehensive3DSR(unittest.TestCase): @@ -2374,7 +2471,7 @@ def setUp(self): self._sop_instance_uid = generate_uid() self._instance_number = 4 self._institution_name = 'institute' - self._institutional_department_name = 'department' + self._department_name = 'department' self._manufacturer = 'manufacturer' observer_person_context = ObserverContext( @@ -2444,7 +2541,8 @@ def setUp(self): imaging_measurements=imaging_measurements )[0] - self._report = Comprehensive3DSR( + def test_construction(self): + report = Comprehensive3DSR( evidence=[self._ref_dataset], content=self._content, series_instance_uid=self._series_instance_uid, @@ -2452,34 +2550,42 @@ def setUp(self): sop_instance_uid=self._sop_instance_uid, instance_number=self._instance_number, institution_name=self._institution_name, - institutional_department_name=self._institutional_department_name, + institutional_department_name=self._department_name, manufacturer=self._manufacturer ) + assert report.SOPClassUID == '1.2.840.10008.5.1.4.1.1.88.34' + assert report.PatientID == self._ref_dataset.PatientID + assert report.PatientName == self._ref_dataset.PatientName + assert report.StudyInstanceUID == self._ref_dataset.StudyInstanceUID + assert report.AccessionNumber == self._ref_dataset.AccessionNumber + assert report.SeriesInstanceUID == self._series_instance_uid + assert report.SeriesNumber == self._series_number + assert report.SOPInstanceUID == self._sop_instance_uid + assert report.InstanceNumber == self._instance_number + assert report.SOPClassUID == '1.2.840.10008.5.1.4.1.1.88.34' + assert report.InstitutionName == self._institution_name + assert report.Manufacturer == self._manufacturer + assert report.Modality == 'SR' - def test_sop_class_uid(self): - assert self._report.SOPClassUID == '1.2.840.10008.5.1.4.1.1.88.34' - - def test_patient_attributes(self): - assert self._report.PatientID == self._ref_dataset.PatientID - assert self._report.PatientName == self._ref_dataset.PatientName + with pytest.raises(AttributeError): + assert report.CurrentRequestedProcedureEvidenceSequence + unref_evd_items = report.PertinentOtherEvidenceSequence + assert len(unref_evd_items) == 1 - def test_study_attributes(self): - assert ( - self._report.StudyInstanceUID == self._ref_dataset.StudyInstanceUID + def test_evidence_duplication(self): + report = Comprehensive3DSR( + evidence=[self._ref_dataset, self._ref_dataset], + content=self._content, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + institution_name=self._institution_name, + institutional_department_name=self._department_name, + manufacturer=self._manufacturer ) - assert self._report.AccessionNumber == self._ref_dataset.AccessionNumber - - def test_series_attributes(self): - assert self._report.SeriesInstanceUID == self._series_instance_uid - assert self._report.SeriesNumber == self._series_number - - def test_instance_attributes(self): - assert self._report.SOPInstanceUID == self._sop_instance_uid - assert self._report.InstanceNumber == self._instance_number - assert self._report.SOPClassUID == '1.2.840.10008.5.1.4.1.1.88.34' - assert self._report.InstitutionName == self._institution_name - assert self._report.Manufacturer == self._manufacturer - assert self._report.Modality == 'SR' + unref_evd_items = report.PertinentOtherEvidenceSequence + assert len(unref_evd_items) == 1 class TestSRUtilities(unittest.TestCase):