From b378a20a23a62a55ef2614377465a034c42eb128 Mon Sep 17 00:00:00 2001 From: "Markus D. Herrmann" Date: Wed, 19 May 2021 09:49:49 -0400 Subject: [PATCH] Add import statements (#70) * Include import statements into __init__ * Add unit tests * Update usage section of documentation * Fix all sphinx warnings * Updated API doc and user guide Co-authored-by: Chris Bridge --- data/test_files/frame_rgb_empty.jpeg | Bin 0 -> 635 bytes docs/package.rst | 128 ---------------- docs/usage.rst | 125 ++++++--------- requirements_docs.txt | 10 +- src/highdicom/__init__.py | 45 ++++++ src/highdicom/base.py | 10 +- src/highdicom/coding_schemes.py | 99 ++++++++++++ src/highdicom/content.py | 32 ++-- src/highdicom/legacy/__init__.py | 11 ++ src/highdicom/sc/__init__.py | 7 + src/highdicom/sc/sop.py | 4 +- src/highdicom/seg/__init__.py | 25 +++ src/highdicom/seg/content.py | 18 +-- src/highdicom/seg/sop.py | 14 +- src/highdicom/sr/__init__.py | 134 ++++++++++++++++ src/highdicom/sr/coding.py | 100 +----------- src/highdicom/sr/content.py | 48 +++--- src/highdicom/sr/sop.py | 8 +- src/highdicom/sr/templates.py | 172 ++++++++++----------- src/highdicom/sr/utils.py | 22 +-- src/highdicom/sr/value_types.py | 92 +++++------ src/highdicom/utils.py | 4 +- tests/test_color.py | 34 +++++ tests/test_content.py | 108 +++++++++++++ tests/test_frame.py | 218 +++++++++++++++++++++++++++ tests/test_sc.py | 4 +- tests/test_seg.py | 8 +- tests/test_sr.py | 12 +- tests/test_utils.py | 2 +- 29 files changed, 956 insertions(+), 538 deletions(-) create mode 100644 data/test_files/frame_rgb_empty.jpeg create mode 100644 src/highdicom/coding_schemes.py create mode 100644 tests/test_color.py create mode 100644 tests/test_content.py create mode 100644 tests/test_frame.py diff --git a/data/test_files/frame_rgb_empty.jpeg b/data/test_files/frame_rgb_empty.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..eb7d451a3b8b0701044e5161afc1991b2c9a3382 GIT binary patch literal 635 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$#!}nhriyF9q*#7@b0Cj}W1poj5 literal 0 HcmV?d00001 diff --git a/docs/package.rst b/docs/package.rst index 04083eea..39806c79 100644 --- a/docs/package.rst +++ b/docs/package.rst @@ -23,15 +23,6 @@ highdicom.color module :undoc-members: :show-inheritance: -highdicom.content module -++++++++++++++++++++++++ - -.. automodule:: highdicom.content - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - highdicom.enum module +++++++++++++++++++++ @@ -68,15 +59,6 @@ highdicom.spatial module :undoc-members: :show-inheritance: -highdicom.uid module -++++++++++++++++++++++ - -.. automodule:: highdicom.uid - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - highdicom.utils module ++++++++++++++++++++++ @@ -99,16 +81,6 @@ highdicom.legacy package :show-inheritance: -highdicom.legacy.sop module -+++++++++++++++++++++++++++ - -.. automodule:: highdicom.legacy.sop - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - - .. _highdicom-seg-subpackage: highdicom.seg package @@ -120,33 +92,6 @@ highdicom.seg package :undoc-members: :show-inheritance: -highdicom.seg.content module -++++++++++++++++++++++++++++ - -.. automodule:: highdicom.seg.content - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - -highdicom.seg.enum module -+++++++++++++++++++++++++ - -.. automodule:: highdicom.seg.enum - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - -highdicom.seg.sop module -++++++++++++++++++++++++ - -.. automodule:: highdicom.seg.sop - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - highdicom.seg.utils module ++++++++++++++++++++++++++ @@ -167,42 +112,6 @@ highdicom.sr package :undoc-members: :show-inheritance: -highdicom.sr.coding module -++++++++++++++++++++++++++ - -.. automodule:: highdicom.sr.coding - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - -highdicom.sr.content module -+++++++++++++++++++++++++++ - -.. automodule:: highdicom.sr.content - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - -highdicom.sr.enum module -++++++++++++++++++++++++ - -.. automodule:: highdicom.sr.enum - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - -highdicom.sr.sop module -+++++++++++++++++++++++ - -.. automodule:: highdicom.sr.sop - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - highdicom.sr.utils module +++++++++++++++++++++++++ @@ -212,25 +121,6 @@ highdicom.sr.utils module :undoc-members: :show-inheritance: -highdicom.sr.templates module -+++++++++++++++++++++++++++++ - -.. automodule:: highdicom.sr.templates - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - -highdicom.sr.value\_types module -++++++++++++++++++++++++++++++++ - -.. automodule:: highdicom.sr.value_types - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - - .. _highdicom-sc-subpackage: highdicom.sc package @@ -241,21 +131,3 @@ highdicom.sc package :special-members: __call__ :undoc-members: :show-inheritance: - -highdicom.sc.enum module -+++++++++++++++++++++++++ - -.. automodule:: highdicom.sc.enum - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: - -highdicom.sc.sop module -++++++++++++++++++++++++ - -.. automodule:: highdicom.sc.sop - :members: - :special-members: __call__ - :undoc-members: - :show-inheritance: diff --git a/docs/usage.rst b/docs/usage.rst index 6099718c..b9fbcb9f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -17,18 +17,10 @@ Derive a Segmentation image from a series of single-frame Computed Tomography from pathlib import Path + import highdicom as hd import numpy as np from pydicom.sr.codedict import codes from pydicom.filereader import dcmread - from pydicom.uid import generate_uid - - from highdicom.content import AlgorithmIdentificationSequence - from highdicom.seg.content import SegmentDescription - from highdicom.seg.enum import ( - SegmentAlgorithmTypeValues, - SegmentationTypeValues - ) - from highdicom.seg.sop import Segmentation # Path to directory containing single-frame legacy CT Image instances # stored as PS3.10 files @@ -50,33 +42,33 @@ Derive a Segmentation image from a series of single-frame Computed Tomography mask[1:-1, 10:-10, 100:-100] = True # Describe the algorithm that created the segmentation - algorithm_identification = AlgorithmIdentificationSequence( + algorithm_identification = hd.AlgorithmIdentificationSequence( name='test', version='v1.0', family=codes.cid7162.ArtificialIntelligence ) # Describe the segment - description_segment_1 = SegmentDescription( + description_segment_1 = hd.seg.SegmentDescription( segment_number=1, segment_label='first segment', segmented_property_category=codes.cid7150.Tissue, segmented_property_type=codes.cid7166.ConnectiveTissue, - algorithm_type=SegmentAlgorithmTypeValues.AUTOMATIC, + algorithm_type=hd.seg.SegmentAlgorithmTypeValues.AUTOMATIC, algorithm_identification=algorithm_identification, - tracking_uid=generate_uid(), + tracking_uid=hd.UID(), tracking_id='test segmentation of computed tomography image' ) # Create the Segmentation instance - seg_dataset = Segmentation( + seg_dataset = hd.seg.Segmentation( source_images=image_datasets, pixel_array=mask, - segmentation_type=SegmentationTypeValues.BINARY, + segmentation_type=hd.seg.SegmentationTypeValues.BINARY, segment_descriptions=[description_segment_1], - series_instance_uid=generate_uid(), + series_instance_uid=hd.UID(), series_number=2, - sop_instance_uid=generate_uid(), + sop_instance_uid=hd.UID(), instance_number=1, manufacturer='Manufacturer', manufacturer_model_name='Model', @@ -95,18 +87,10 @@ Derive a Segmentation image from a multi-frame Slide Microscopy (SM) image: from pathlib import Path + import highdicom as hd import numpy as np from pydicom.sr.codedict import codes from pydicom.filereader import dcmread - from pydicom.uid import generate_uid - - from highdicom.content import AlgorithmIdentificationSequence - from highdicom.seg.content import SegmentDescription - from highdicom.seg.enum import ( - SegmentAlgorithmTypeValues, - SegmentationTypeValues - ) - from highdicom.seg.sop import Segmentation # Path to multi-frame SM image instance stored as PS3.10 file image_file = Path('/path/to/image/file') @@ -118,21 +102,21 @@ Derive a Segmentation image from a multi-frame Slide Microscopy (SM) image: mask = np.max(image_dataset.pixel_array, axis=3) > 1 # Describe the algorithm that created the segmentation - algorithm_identification = AlgorithmIdentificationSequence( + algorithm_identification = hd.AlgorithmIdentificationSequence( name='test', version='v1.0', family=codes.cid7162.ArtificialIntelligence ) # Describe the segment - description_segment_1 = SegmentDescription( + description_segment_1 = hd.seg.SegmentDescription( segment_number=1, segment_label='first segment', segmented_property_category=codes.cid7150.Tissue, segmented_property_type=codes.cid7166.ConnectiveTissue, - algorithm_type=SegmentAlgorithmTypeValues.AUTOMATIC, + algorithm_type=hd.seg.SegmentAlgorithmTypeValues.AUTOMATIC, algorithm_identification=algorithm_identification, - tracking_uid=generate_uid(), + tracking_uid=hd.UID(), tracking_id='test segmentation of slide microscopy image' ) @@ -140,11 +124,11 @@ Derive a Segmentation image from a multi-frame Slide Microscopy (SM) image: seg_dataset = Segmentation( source_images=[image_dataset], pixel_array=mask, - segmentation_type=SegmentationTypeValues.BINARY, + segmentation_type=hd.seg.SegmentationTypeValues.BINARY, segment_descriptions=[description_segment_1], - series_instance_uid=generate_uid(), + series_instance_uid=hd.UID(), series_number=2, - sop_instance_uid=generate_uid(), + sop_instance_uid=hd.UID(), instance_number=1, manufacturer='Manufacturer', manufacturer_model_name='Model', @@ -165,10 +149,9 @@ Iterating over segments in a segmentation image instance: from pathlib import Path + import highdicom as hd from pydicom.filereader import dcmread - from highdicom.seg.utils import iter_segments - # Path to multi-frame SEG image instance stored as PS3.10 file seg_file = Path('/path/to/seg/file') @@ -177,7 +160,7 @@ Iterating over segments in a segmentation image instance: # Iterate over segments and print the information about the frames # that encode the segment across different image positions - for frames, frame_descriptions, description in iter_segments(seg_dataset): + for frames, frame_descriptions, description in hd.seg.utils.iter_segments(seg_dataset): print(frames.shape) print( set([ @@ -201,30 +184,11 @@ image: from pathlib import Path + import highdicom as hd import numpy as np - from pydicom.uid import generate_uid from pydicom.filereader import dcmread from pydicom.sr.codedict import codes - from highdicom.sr.content import ( - FindingSite, - ImageRegion3D, - ) - from highdicom.sr.enum import GraphicTypeValues3D - from highdicom.sr.sop import Comprehensive3DSR - from highdicom.sr.templates import ( - DeviceObserverIdentifyingAttributes, - Measurement, - MeasurementProperties, - MeasurementReport, - ObservationContext, - ObserverContext, - PersonObserverIdentifyingAttributes, - PlanarROIMeasurementsAndQualitativeEvaluations, - TrackingIdentifier, - ) - from highdicom.sr.value_types import CodedConcept - # Path to single-frame CT image instance stored as PS3.10 file image_file = Path('/path/to/image/file') @@ -233,27 +197,27 @@ image: # Describe the context of reported observations: the person that reported # the observations and the device that was used to make the observations - observer_person_context = ObserverContext( + observer_person_context = hd.sr.ObserverContext( observer_type=codes.DCM.Person, - observer_identifying_attributes=PersonObserverIdentifyingAttributes( + observer_identifying_attributes=hd.sr.PersonObserverIdentifyingAttributes( name='Foo' ) ) - observer_device_context = ObserverContext( + observer_device_context = hd.sr.ObserverContext( observer_type=codes.DCM.Device, - observer_identifying_attributes=DeviceObserverIdentifyingAttributes( - uid=generate_uid() + observer_identifying_attributes=hd.sr.DeviceObserverIdentifyingAttributes( + uid=hd.UID() ) ) - observation_context = ObservationContext( + observation_context = hd.sr.ObservationContext( observer_person_context=observer_person_context, observer_device_context=observer_device_context, ) # Describe the image region for which observations were made # (in physical space based on the frame of reference) - referenced_region = ImageRegion3D( - graphic_type=GraphicTypeValues3D.POLYGON, + referenced_region = hd.sr.ImageRegion3D( + graphic_type=hd.sr.GraphicTypeValues3D.POLYGON, graphic_data=np.array([ (165.0, 200.0, 134.0), (170.0, 200.0, 134.0), @@ -276,11 +240,11 @@ image: measurements = [ Measurement( name=codes.SCT.AreaOfDefinedRegion, - tracking_identifier=TrackingIdentifier(uid=generate_uid()), + tracking_identifier=hd.sr.TrackingIdentifier(uid=generate_uid()), value=1.7, unit=codes.UCUM.SquareMillimeter, - properties=MeasurementProperties( - normality=CodedConcept( + properties=hd.sr.MeasurementProperties( + normality=hd.sr.CodedConcept( value="17621005", meaning="Normal", scheme_designator="SCT" @@ -290,9 +254,9 @@ image: ) ] imaging_measurements = [ - PlanarROIMeasurementsAndQualitativeEvaluations( + hd.sr.PlanarROIMeasurementsAndQualitativeEvaluations( tracking_identifier=TrackingIdentifier( - uid=generate_uid(), + uid=hd.UID(), identifier='Planar ROI Measurements' ), referenced_region=referenced_region, @@ -303,19 +267,19 @@ image: ] # Create the report content - measurement_report = MeasurementReport( + measurement_report = hd.sr.MeasurementReport( observation_context=observation_context, procedure_reported=codes.LN.CTUnspecifiedBodyRegion, imaging_measurements=imaging_measurements ) # Create the Structured Report instance - sr_dataset = Comprehensive3DSR( + sr_dataset = hd.sr.Comprehensive3DSR( evidence=[image_dataset], content=measurement_report[0], series_number=1, - series_instance_uid=generate_uid(), - sop_instance_uid=generate_uid(), + series_instance_uid=hd.UID(), + sop_instance_uid=hd.UID(), instance_number=1, manufacturer='Manufacturer' ) @@ -334,13 +298,10 @@ Finding relevant content in the nested SR content tree: from pathlib import Path + import highdicom as hd from pydicom.filereader import dcmread from pydicom.sr.codedict import codes - from highdicom.sr.enum import ValueTypeValues, RelationshipTypeValues - from highdicom.sr.utils import find_content_items - - # Path to SR document instance stored as PS3.10 file document_file = Path('/path/to/document/file') @@ -348,7 +309,7 @@ Finding relevant content in the nested SR content tree: sr_dataset = dcmread(str(document_file)) # Find all content items that may contain other content items. - containers = find_content_items( + containers = hd.sr.utils.find_content_items( dataset=sr_dataset, relationship_type=RelationshipTypeValues.CONTAINS ) @@ -358,14 +319,14 @@ Finding relevant content in the nested SR content tree: # to TID 1500 "Measurment Report" if sr_dataset.ContentTemplateSequence[0].TemplateIdentifier == 'TID1500': # Determine who made the observations reported in the document - observers = find_content_items( + observers = hd.sr.utils.find_content_items( dataset=sr_dataset, name=codes.DCM.PersonObserverName ) print(observers) # Find all imaging measurements reported in the document - measurements = find_content_items( + measurements = hd.sr.utils.find_content_items( dataset=sr_dataset, name=codes.DCM.ImagingMeasurements, recursive=True @@ -373,7 +334,7 @@ Finding relevant content in the nested SR content tree: print(measurements) # Find all findings reported in the document - findings = find_content_items( + findings = hd.sr.utils.find_content_items( dataset=sr_dataset, name=codes.DCM.Finding, recursive=True @@ -382,7 +343,7 @@ Finding relevant content in the nested SR content tree: # Find regions of interest (ROI) described in the document # in form of spatial coordinates (SCOORD) - regions = find_content_items( + regions = hd.sr.utils.find_content_items( dataset=sr_dataset, value_type=ValueTypeValues.SCOORD, recursive=True diff --git a/requirements_docs.txt b/requirements_docs.txt index e0798286..905765f7 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -1,5 +1,5 @@ -sphinx-pyreverse==0.0.12 -sphinx-rtd-theme==0.2.4 -sphinxcontrib-autoprogram==0.1.4 -sphinxcontrib-websupport==1.0.1 -sphinx-autodoc-typehints==1.10.3 +sphinx-autodoc-typehints==1.12.0 +sphinx-pyreverse==0.0.16 +sphinx-rtd-theme==0.5.2 +sphinxcontrib-autoprogram==0.1.7 +sphinxcontrib-websupport==1.2.4 diff --git a/src/highdicom/__init__.py b/src/highdicom/__init__.py index e69de29b..8d1f99b4 100644 --- a/src/highdicom/__init__.py +++ b/src/highdicom/__init__.py @@ -0,0 +1,45 @@ +from highdicom import legacy +from highdicom import sc +from highdicom import seg +from highdicom import sr +from highdicom import color +from highdicom.content import ( + AlgorithmIdentificationSequence, + IssuerOfIdentifier, + PixelMeasuresSequence, + PlanePositionSequence, + PlaneOrientationSequence, + SpecimenCollection, + SpecimenDescription, + SpecimenPreparationStep, + SpecimenSampling, + SpecimenStaining, +) +from highdicom import frame +from highdicom import io +from highdicom import spatial +from highdicom.uid import UID +from highdicom import utils + +__all__ = [ + 'AlgorithmIdentificationSequence', + 'color', + 'frame', + 'io', + 'IssuerOfIdentifier', + 'legacy', + 'PixelMeasuresSequence', + 'PlanePositionSequence', + 'PlaneOrientationSequence', + 'sc', + 'seg', + 'spatial', + 'SpecimenCollection', + 'SpecimenDescription', + 'SpecimenPreparationStep', + 'SpecimenSampling', + 'SpecimenStaining', + 'sr', + 'UID', + 'utils', +] diff --git a/src/highdicom/base.py b/src/highdicom/base.py index 321b534c..18548cae 100644 --- a/src/highdicom/base.py +++ b/src/highdicom/base.py @@ -9,7 +9,7 @@ from pydicom.uid import ExplicitVRBigEndian, ImplicitVRLittleEndian from pydicom.valuerep import DA, TM -from highdicom.sr.coding import CodingSchemeIdentificationItem +from highdicom.coding_schemes import CodingSchemeIdentificationItem from highdicom.enum import ContentQualificationValues from highdicom.version import __version__ from highdicom._iods import IOD_MODULE_MAP, SOP_CLASS_UID_IOD_KEY_MAP @@ -93,7 +93,7 @@ def __init__( Name of the referring physician content_qualification: Union[str, highdicom.enum.ContentQualificationValues], optional Indicator of content qualification - coding_schemes: Sequence[highdicom.sr.coding.CodingSchemeIdentificationItem], optional + coding_schemes: Sequence[highdicom.sr.CodingSchemeIdentificationItem], optional private or public coding schemes that are not part of the DICOM standard series_description: str, optional @@ -186,9 +186,9 @@ def __init__( self.CodingSchemeIdentificationSequence.append(item) def _copy_attribute( - self, - dataset: Dataset, - keyword: str + self, + dataset: Dataset, + keyword: str ) -> None: """Copies an attribute from `dataset` to `self`. diff --git a/src/highdicom/coding_schemes.py b/src/highdicom/coding_schemes.py new file mode 100644 index 00000000..7b5341ca --- /dev/null +++ b/src/highdicom/coding_schemes.py @@ -0,0 +1,99 @@ +from typing import Optional, Sequence + +from pydicom.dataset import Dataset + + +class CodingSchemeResourceItem(Dataset): + + """Class for items of the Coding Scheme Resource Sequence.""" + + def __init__(self, url: str, url_type: str) -> None: + """ + Parameters + ---------- + url: str + unique resource locator + url_type: str + type of resource `url` points to (options: `{"DOC", "OWL", "CSV"}`) + + """ + super().__init__() + self.CodingSchemeURL = str(url) + if url_type not in {"DOC", "OWL", "CSV"}: + raise ValueError('Unknonw URL type.') + self.CodingSchemeURLType = str(url_type) + + +class CodingSchemeIdentificationItem(Dataset): + + """Class for items of the Coding Scheme Identification Sequence.""" + + def __init__( + self, + designator: str, + name: Optional[str] = None, + version: Optional[str] = None, + registry: Optional[str] = None, + uid: Optional[str] = None, + external_id: Optional[str] = None, + responsible_organization: Optional[str] = None, + resources: Optional[Sequence[CodingSchemeResourceItem]] = None + ) -> None: + """ + Parameters + ---------- + designator: str + value of the Coding Scheme Designator attribute of a `CodedConcept` + name: str, optional + name of the scheme + version: str, optional + version of the scheme + registry: str, optional + name of an external registry where scheme may be obtained from; + required if scheme is registered + uid: str, optional + unique identifier of the scheme; required if the scheme is + registered by an ISO 8824 object identifier compatible with the + UI value representation (VR) + external_id: str, optional + external identifier of the scheme; required if the scheme is + registered and `uid` is not available + responsible_organization: str, optional + name of the organization that is responsible for the scheme + resources: Sequence[pydicom.sr.coding.CodingSchemeResourceItem], optional + one or more resources related to the scheme + + """ # noqa + super().__init__() + self.CodingSchemeDesignator = str(designator) + if name is not None: + self.CodingSchemeName = str(name) + if version is not None: + self.CodingSchemeVersion = str(version) + if responsible_organization is not None: + self.CodingSchemeResponsibleOrganization = \ + str(responsible_organization) + if registry is not None: + self.CodingSchemeRegistry = str(registry) + if uid is None and external_id is None: + raise ValueError( + 'UID or external ID is required if coding scheme is ' + 'registered.' + ) + if uid is not None and external_id is not None: + raise ValueError( + 'Either UID or external ID should be specified for ' + 'registered coding scheme.' + ) + if uid is not None: + self.CodingSchemeUID = str(uid) + elif external_id is not None: + self.CodingSchemeExternalID = str(external_id) + if resources is not None: + self.CodingSchemeResourcesSequence: Sequence[Dataset] = [] + for r in resources: + if not isinstance(r, CodingSchemeResourceItem): + raise TypeError( + 'Resources must have type CodingSchemeResourceItem.' + ) + self.CodingSchemeResourcesSequence.append(r) diff --git a/src/highdicom/content.py b/src/highdicom/content.py index 626dd2d9..ce878302 100644 --- a/src/highdicom/content.py +++ b/src/highdicom/content.py @@ -41,7 +41,7 @@ def __init__( ---------- name: str Name of the algorithm - family: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept] + family: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept] Kind of algorithm family version: str Version of the algorithm @@ -175,7 +175,7 @@ def __eq__(self, other: Any) -> bool: Parameters ---------- - other: highdicom.content.PlanePositionSequence + other: highdicom.PlanePositionSequence Plane position of other image that should be compared Returns @@ -255,7 +255,7 @@ def __eq__(self, other: Any) -> bool: Parameters ---------- - other: highdicom.content.PlaneOrientationSequence + other: highdicom.PlaneOrientationSequence Plane position of other image that should be compared Returns @@ -338,7 +338,7 @@ def __init__( """ Parameters ---------- - procedure: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept] + procedure: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept] Procedure used to collect the examined specimen """ # noqa @@ -368,13 +368,13 @@ def __init__( """ Parameters ---------- - method: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept] + method: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept] Method used to sample the examined specimen from a parent specimen parent_specimen_id: str Identifier of the parent specimen - parent_specimen_type: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept] + parent_specimen_type: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept] Type of the parent specimen - issuer_of_parent_specimen_id: highdicom.content.IssuerOfIdentifier, optional + issuer_of_parent_specimen_id: highdicom.IssuerOfIdentifier, optional Issuer who created the parent specimen """ # noqa @@ -423,7 +423,7 @@ def __init__( """ Parameters ---------- - substances: Sequence[Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept]] + substances: Sequence[Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept]] Substances used to stain examined specimen(s) """ # noqa @@ -467,18 +467,18 @@ def __init__( ---------- specimen_id: str Identifier of the processed specimen - processing_type: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept] + processing_type: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept] Type of processing - processing_procedure: Union[highdicom.content.SpecimenCollection, highdicom.content.SpecimenSampling, highdicom.content.SpecimenStaining] + processing_procedure: Union[highdicom.SpecimenCollection, highdicom.SpecimenSampling, highdicom.SpecimenStaining] Procedure used during processing processing_datetime: datetime.datetime, optional Datetime of processing - processing_description: Union[str, pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept], optional + processing_description: Union[str, pydicom.sr.coding.Code, highdicom.sr.CodedConcept], optional Description of processing - issuer_of_specimen_id: highdicom.content.IssuerOfIdentifier, optional - fixative: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept], optional + issuer_of_specimen_id: highdicom.IssuerOfIdentifier, optional + fixative: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept], optional Fixative used during processing - embedding_medium: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept], optional + embedding_medium: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept], optional Embedding medium used during processing """ # noqa @@ -566,10 +566,10 @@ def __init__( provided either in form of text or in form of spatial x, y, z coordinates specifying the position (offset) relative to the three-dimensional slide coordinate system - specimen_preparation_steps: Sequence[highdicom.content.SpecimenPreparationStep], optional + specimen_preparation_steps: Sequence[highdicom.SpecimenPreparationStep], optional Steps that were applied during the preparation of the examined specimen in the laboratory prior to image acquisition - issuer_of_specimen_id: highdicom.content.IssuerOfIdentifier, optional + issuer_of_specimen_id: highdicom.IssuerOfIdentifier, optional Description of the issuer of the specimen identifier """ # noqa diff --git a/src/highdicom/legacy/__init__.py b/src/highdicom/legacy/__init__.py index 551ef9f2..d0768cc2 100644 --- a/src/highdicom/legacy/__init__.py +++ b/src/highdicom/legacy/__init__.py @@ -1,9 +1,20 @@ """Package for creation of Legacy Converted Enhanced CT, MR or PET Image instances. """ +from highdicom.legacy.sop import ( + LegacyConvertedEnhancedCTImage, + LegacyConvertedEnhancedMRImage, + LegacyConvertedEnhancedPETImage, +) SOP_CLASS_UIDS = { '1.2.840.10008.5.1.4.1.1.4.4', # Legacy Converted Enhanced MR Image '1.2.840.10008.5.1.4.1.1.2.2', # Legacy Converted Enhanced CT Image '1.2.840.10008.5.1.4.1.1.128.1', # Legacy Converted Enhanced PET Image } + +__all__ = [ + 'LegacyConvertedEnhancedCTImage', + 'LegacyConvertedEnhancedMRImage', + 'LegacyConvertedEnhancedPETImage', +] diff --git a/src/highdicom/sc/__init__.py b/src/highdicom/sc/__init__.py index fb78d900..1d5841e6 100644 --- a/src/highdicom/sc/__init__.py +++ b/src/highdicom/sc/__init__.py @@ -1,5 +1,12 @@ """Package for creation of Secondary Capture (SC) Image instances.""" +from highdicom.sc.sop import SCImage +from highdicom.sc.enum import ConversionTypeValues SOP_CLASS_UIDS = { '1.2.840.10008.5.1.4.1.1.7', # SC Image } + +__all__ = [ + 'ConversionTypeValues', + 'SCImage', +] diff --git a/src/highdicom/sc/sop.py b/src/highdicom/sc/sop.py index 9e53118a..1bc5553b 100644 --- a/src/highdicom/sc/sop.py +++ b/src/highdicom/sc/sop.py @@ -158,9 +158,9 @@ def __init__( container_identifier: str, optional Identifier of the container holding the specimen (required if `coordinate_system` is ``"SLIDE"``) - issuer_of_container_identifier: highdicom.content.IssuerOfIdentifier, optional + issuer_of_container_identifier: highdicom.IssuerOfIdentifier, optional Issuer of `container_identifier` - specimen_descriptions: Sequence[highdicom.content.SpecimenDescriptions], optional + specimen_descriptions: Sequence[highdicom.SpecimenDescriptions], optional Description of each examined specimen (required if `coordinate_system` is ``"SLIDE"``) transfer_syntax_uid: str, optional diff --git a/src/highdicom/seg/__init__.py b/src/highdicom/seg/__init__.py index 9acb81e6..a71c68c4 100644 --- a/src/highdicom/seg/__init__.py +++ b/src/highdicom/seg/__init__.py @@ -1,6 +1,31 @@ """Package for creation of Segmentation (SEG) instances.""" +from highdicom.seg.sop import Segmentation +from highdicom.seg.enum import ( + SegmentAlgorithmTypeValues, + SegmentationTypeValues, + SegmentationFractionalTypeValues, + SpatialLocationsPreservedValues, + SegmentsOverlapValues, +) +from highdicom.seg.content import ( + SegmentDescription, + DimensionIndexSequence, +) +from highdicom.seg import utils SOP_CLASS_UIDS = { '1.2.840.10008.5.1.4.1.1.66.4', # Segmentation '1.2.840.10008.5.1.4.1.1.66.5', # Surface Segmentation } + +__all__ = [ + 'DimensionIndexSequence', + 'Segmentation', + 'SegmentAlgorithmTypeValues', + 'SegmentationFractionalTypeValues', + 'SegmentationTypeValues', + 'SegmentDescription', + 'SegmentsOverlapValues', + 'SpatialLocationsPreservedValues', + 'utils', +] diff --git a/src/highdicom/seg/content.py b/src/highdicom/seg/content.py index 52ddf504..99f5fc06 100644 --- a/src/highdicom/seg/content.py +++ b/src/highdicom/seg/content.py @@ -47,32 +47,32 @@ def __init__( Number of the segment. segment_label: str Label of the segment - segmented_property_category: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept] + segmented_property_category: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept] Category of the property the segment represents, e.g. ``Code("49755003", "SCT", "Morphologically Abnormal Structure")`` (see `CID 7150 `_ "Segmentation Property Categories") - segmented_property_type: Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept] + segmented_property_type: Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept] Property the segment represents, e.g. ``Code("108369006", "SCT", "Neoplasm")`` (see `CID 7151 `_ "Segmentation Property Types") - algorithm_type: Union[str, highdicom.seg.enum.SegmentAlgorithmTypeValues] + algorithm_type: Union[str, highdicom.seg.SegmentAlgorithmTypeValues] Type of algorithm - algorithm_identification: highdicom.content.AlgorithmIdentificationSequence, optional + algorithm_identification: highdicom.AlgorithmIdentificationSequence, optional Information useful for identification of the algorithm, such as its name or version. Required unless the algorithm type is `MANUAL` tracking_uid: str, optional Unique tracking identifier (universally unique) tracking_id: str, optional Tracking identifier (unique only with the domain of use) - anatomic_regions: Sequence[Union[pydicom.sr.coding.Code, highdicom.sr.coding.CodedConcept]], optional + anatomic_regions: Sequence[Union[pydicom.sr.coding.Code, highdicom.sr.CodedConcept]], optional Anatomic region(s) into which segment falls, e.g. ``Code("41216001", "SCT", "Prostate")`` (see `CID 4 `_ "Anatomic Region", `CID 4031 `_ "Common Anatomic Regions", as as well as other CIDs for domain-specific anatomic regions) - primary_anatomic_structures: Sequence[Union[highdicom.sr.coding.Code, highdicom.sr.coding.CodedConcept]], optional + primary_anatomic_structures: Sequence[Union[highdicom.sr.Code, highdicom.sr.CodedConcept]], optional Anatomic structure(s) the segment represents (see CIDs for domain-specific primary anatomic structures) @@ -307,7 +307,7 @@ def get_plane_positions_of_image( Returns ------- - List[PlanePositionSequence] + List[highdicom.PlanePositionSequence] Plane position of each frame in the image """ @@ -348,7 +348,7 @@ def get_plane_positions_of_series( Returns ------- - List[PlanePositionSequence] + List[highdicom.PlanePositionSequence] Plane position of each frame in the image """ @@ -376,7 +376,7 @@ def get_index_values( Parameters ---------- - plane_positions: Sequence[PlanePositionSequence] + plane_positions: Sequence[highdicom.PlanePositionSequence] Plane position of frames in a multi-frame image or in a series of single-frame images diff --git a/src/highdicom/seg/sop.py b/src/highdicom/seg/sop.py index a81286b3..976bc649 100644 --- a/src/highdicom/seg/sop.py +++ b/src/highdicom/seg/sop.py @@ -113,9 +113,9 @@ def __init__( the column dimension, which are defined in the three-dimensional slide coordinate system by the direction cosines encoded by the *Image Orientation (Slide)* attribute). - segmentation_type: Union[str, highdicom.seg.enum.SegmentationTypeValues] + segmentation_type: Union[str, highdicom.seg.SegmentationTypeValues] Type of segmentation, either ``"BINARY"`` or ``"FRACTIONAL"`` - segment_descriptions: Sequence[highdicom.seg.content.SegmentDescription] + segment_descriptions: Sequence[highdicom.seg.SegmentDescription] Description of each segment encoded in `pixel_array`. In the case of pixel arrays with multiple integer values, the segment description with the corresponding segment number is used to describe each segment. @@ -135,7 +135,7 @@ def __init__( application) that creates the instance software_versions: Union[str, Tuple[str]] Version(s) of the software that creates the instance - fractional_type: Union[str, highdicom.seg.enum.SegmentationFractionalTypeValues], optional + fractional_type: Union[str, highdicom.seg.SegmentationFractionalTypeValues], optional Type of fractional segmentation that indicates how pixel data should be interpreted max_fractional_value: int, optional @@ -156,12 +156,12 @@ def __init__( Physical spacing of image pixels in `pixel_array`. If ``None``, it will be assumed that the segmentation image has the same pixel measures as the source image(s). - plane_orientation: highdicom.content.PlaneOrientationSequence, optional + plane_orientation: highdicom.PlaneOrientationSequence, optional Orientation of planes in `pixel_array` relative to axes of three-dimensional patient or slide coordinate space. If ``None``, it will be assumed that the segmentation image as the same plane orientation as the source image(s). - plane_positions: Sequence[highdicom.content.PlanePositionSequence], optional + plane_positions: Sequence[highdicom.PlanePositionSequence], optional Position of each plane in `pixel_array` in the three-dimensional patient or slide coordinate space. If ``None``, it will be assumed that the segmentation image has the @@ -467,12 +467,12 @@ def add_segments( the column dimension, which are defined in the three-dimensional slide coordinate system by the direction cosines encoded by the *Image Orientation (Slide)* attribute). - segment_descriptions: Sequence[highdicom.seg.content.SegmentDescription] + segment_descriptions: Sequence[highdicom.seg.SegmentDescription] Description of each segment encoded in `pixel_array`. In the case of pixel arrays with multiple integer values, the segment description with the corresponding segment number is used to describe each segment. - plane_positions: Sequence[highdicom.content.PlanePositionSequence], optional + plane_positions: Sequence[highdicom.PlanePositionSequence], optional Position of each plane in `pixel_array` relative to the three-dimensional patient or slide coordinate system. diff --git a/src/highdicom/sr/__init__.py b/src/highdicom/sr/__init__.py index b81557f9..a5944ce2 100644 --- a/src/highdicom/sr/__init__.py +++ b/src/highdicom/sr/__init__.py @@ -1,4 +1,74 @@ """Package for creationg of Structured Report (SR) instances.""" +from highdicom.sr.coding import CodedConcept +from highdicom.sr.content import ( + FindingSite, + ImageRegion, + ImageRegion3D, + LongitudinalTemporalOffsetFromEvent, + SourceImageForMeasurement, + SourceImageForSegmentation, + SourceImageForRegion, + SourceSeriesForSegmentation, + RealWorldValueMap, + ReferencedSegment, + ReferencedSegmentationFrame, + VolumeSurface, +) +from highdicom.sr.enum import ( + GraphicTypeValues, + GraphicTypeValues3D, + PixelOriginInterpretationValues, + RelationshipTypeValues, + TemporalRangeTypeValues, + ValueTypeValues, +) +from highdicom.sr.sop import ( + EnhancedSR, + ComprehensiveSR, + Comprehensive3DSR, +) +from highdicom.sr.templates import ( + AlgorithmIdentification, + DeviceObserverIdentifyingAttributes, + ImageLibrary, + LanguageOfContentItemAndDescendants, + Measurement, + MeasurementProperties, + MeasurementReport, + MeasurementsAndQualitativeEvaluations, + MeasurementsDerivedFromMultipleROIMeasurements, + MeasurementStatisticalProperties, + NormalRangeProperties, + ObserverContext, + ObservationContext, + PersonObserverIdentifyingAttributes, + PlanarROIMeasurementsAndQualitativeEvaluations, + SubjectContext, + SubjectContextDevice, + SubjectContextFetus, + SubjectContextSpecimen, + TrackingIdentifier, + TimePointContext, + VolumetricROIMeasurementsAndQualitativeEvaluations, +) +from highdicom.sr import utils +from highdicom.sr.value_types import ( + ContentSequence, + CodeContentItem, + ContainerContentItem, + CompositeContentItem, + DateContentItem, + DateTimeContentItem, + ImageContentItem, + NumContentItem, + PnameContentItem, + ScoordContentItem, + Scoord3DContentItem, + TcoordContentItem, + TextContentItem, + TimeContentItem, + UIDRefContentItem, +) SOP_CLASS_UIDS = { '1.2.840.10008.5.1.4.1.1.88.11', # Basic Text SR @@ -17,3 +87,67 @@ '1.2.840.10008.5.1.4.1.1.88.72', # Simplified Adult Echo SR '1.2.840.10008.5.1.4.1.1.88.73', # Patient Radiation Dose SR } + +__all__ = [ + 'AlgorithmIdentification', + 'EnhancedSR', + 'CodedConcept', + 'ContentSequence', + 'ComprehensiveSR', + 'Comprehensive3DSR', + 'ContentSequence', + 'CodeContentItem', + 'ContainerContentItem', + 'CompositeContentItem', + 'DateContentItem', + 'DateTimeContentItem', + 'DeviceObserverIdentifyingAttributes', + 'FindingSite', + 'GraphicTypeValues', + 'GraphicTypeValues3D', + 'ImageContentItem', + 'ImageLibrary', + 'ImageRegion', + 'ImageRegion3D', + 'LanguageOfContentItemAndDescendants', + 'LongitudinalTemporalOffsetFromEvent', + 'Measurement', + 'MeasurementProperties', + 'MeasurementReport', + 'MeasurementsAndQualitativeEvaluations', + 'MeasurementsDerivedFromMultipleROIMeasurements', + 'MeasurementStatisticalProperties', + 'NormalRangeProperties', + 'NumContentItem', + 'ObserverContext', + 'ObservationContext', + 'PersonObserverIdentifyingAttributes', + 'PlanarROIMeasurementsAndQualitativeEvaluations', + 'PixelOriginInterpretationValues', + 'PnameContentItem', + 'RealWorldValueMap', + 'ReferencedSegment', + 'ReferencedSegmentationFrame', + 'RelationshipTypeValues', + 'ScoordContentItem', + 'Scoord3DContentItem', + 'SourceImageForMeasurement', + 'SourceImageForSegmentation', + 'SourceImageForRegion', + 'SourceSeriesForSegmentation', + 'SubjectContext', + 'SubjectContextDevice', + 'SubjectContextFetus', + 'SubjectContextSpecimen', + 'TcoordContentItem', + 'TemporalRangeTypeValues', + 'TextContentItem', + 'TimeContentItem', + 'TimePointContext', + 'TrackingIdentifier', + 'UIDRefContentItem', + 'utils', + 'ValueTypeValues', + 'VolumeSurface', + 'VolumetricROIMeasurementsAndQualitativeEvaluations', +] diff --git a/src/highdicom/sr/coding.py b/src/highdicom/sr/coding.py index 265ef807..a64a2378 100644 --- a/src/highdicom/sr/coding.py +++ b/src/highdicom/sr/coding.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Optional, Sequence +from typing import Any, Optional from pydicom.dataset import Dataset from pydicom.sr.coding import Code @@ -52,7 +52,7 @@ def __eq__(self, other: Any) -> bool: Parameters ---------- - other: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + other: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] code Returns @@ -109,99 +109,3 @@ def scheme_designator(self) -> str: def scheme_version(self) -> str: """Union[str, None]: version of the coding scheme (if specified)""" return getattr(self, 'CodingSchemeVersion', None) - - -class CodingSchemeResourceItem(Dataset): - - """Class for items of the Coding Scheme Resource Sequence.""" - - def __init__(self, url: str, url_type: str) -> None: - """ - Parameters - ---------- - url: str - unique resource locator - url_type: str - type of resource `url` points to (options: `{"DOC", "OWL", "CSV"}`) - - """ - super().__init__() - self.CodingSchemeURL = str(url) - if url_type not in {"DOC", "OWL", "CSV"}: - raise ValueError('Unknonw URL type.') - self.CodingSchemeURLType = str(url_type) - - -class CodingSchemeIdentificationItem(Dataset): - - """Class for items of the Coding Scheme Identification Sequence.""" - - def __init__( - self, - designator: str, - name: Optional[str] = None, - version: Optional[str] = None, - registry: Optional[str] = None, - uid: Optional[str] = None, - external_id: Optional[str] = None, - responsible_organization: Optional[str] = None, - resources: Optional[Sequence[CodingSchemeResourceItem]] = None - ) -> None: - """ - Parameters - ---------- - designator: str - value of the Coding Scheme Designator attribute of a `CodedConcept` - name: str, optional - name of the scheme - version: str, optional - version of the scheme - registry: str, optional - name of an external registry where scheme may be obtained from; - required if scheme is registered - uid: str, optional - unique identifier of the scheme; required if the scheme is - registered by an ISO 8824 object identifier compatible with the - UI value representation (VR) - external_id: str, optional - external identifier of the scheme; required if the scheme is - registered and `uid` is not available - responsible_organization: str, optional - name of the organization that is responsible for the scheme - resources: Sequence[pydicom.sr.coding.CodingSchemeResourceItem], optional - one or more resources related to the scheme - - """ # noqa - super().__init__() - self.CodingSchemeDesignator = str(designator) - if name is not None: - self.CodingSchemeName = str(name) - if version is not None: - self.CodingSchemeVersion = str(version) - if responsible_organization is not None: - self.CodingSchemeResponsibleOrganization = \ - str(responsible_organization) - if registry is not None: - self.CodingSchemeRegistry = str(registry) - if uid is None and external_id is None: - raise ValueError( - 'UID or external ID is required if coding scheme is ' - 'registered.' - ) - if uid is not None and external_id is not None: - raise ValueError( - 'Either UID or external ID should be specified for ' - 'registered coding scheme.' - ) - if uid is not None: - self.CodingSchemeUID = str(uid) - elif external_id is not None: - self.CodingSchemeExternalID = str(external_id) - if resources is not None: - self.CodingSchemeResourcesSequence: Sequence[Dataset] = [] - for r in resources: - if not isinstance(r, CodingSchemeResourceItem): - raise TypeError( - 'Resources must have type CodingSchemeResourceItem.' - ) - self.CodingSchemeResourcesSequence.append(r) diff --git a/src/highdicom/sr/content.py b/src/highdicom/sr/content.py index 9cdd6198..ca084a0a 100644 --- a/src/highdicom/sr/content.py +++ b/src/highdicom/sr/content.py @@ -110,9 +110,9 @@ def __init__( ---------- value: Union[int, float], optional offset in time from a particular event of significance - unit: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + unit: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional unit of time, e.g., "Days" or "Seconds" - event_type: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + event_type: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional type of event to which offset is relative, e.g., "Baseline" or "Enrollment" @@ -204,7 +204,7 @@ def from_source_image( Returns ------- - highdicom.sr.content.SourceImageForMeasurement + highdicom.sr.SourceImageForMeasurement Content item representing a reference to the image dataset """ @@ -286,7 +286,7 @@ def from_source_image( Returns ------- - highdicom.sr.content.SourceImageForRegion + highdicom.sr.SourceImageForRegion Content item representing a reference to the image dataset """ @@ -368,7 +368,7 @@ def from_source_image( Returns ------- - highdicom.sr.content.SourceImageForSegmentation + highdicom.sr.SourceImageForSegmentation Content item representing a reference to the image dataset """ @@ -424,7 +424,7 @@ def from_source_image( Returns ------- - highdicom.sr.content.SourceSeriesForSegmentation + highdicom.sr.SourceSeriesForSegmentation Content item representing a reference to the image dataset """ @@ -452,21 +452,21 @@ def __init__( """ Parameters ---------- - graphic_type: Union[highdicom.sr.enum.GraphicTypeValues, str] + graphic_type: Union[highdicom.sr.GraphicTypeValues, str] name of the graphic type graphic_data: numpy.ndarray array of ordered spatial coordinates, where each row of the array represents a (column, row) coordinate pair source_image: highdicom.sr.template.SourceImageForRegion source image to which `graphic_data` relates - pixel_origin_interpretation: Union[highdicom.sr.enum.PixelOriginInterpretationValues, str], optional + pixel_origin_interpretation: Union[highdicom.sr.PixelOriginInterpretationValues, str], optional whether pixel coordinates specified by `graphic_data` are defined relative to the total pixel matrix - (``highdicom.sr.enum.PixelOriginInterpretationValues.VOLUME``) or + (``highdicom.sr.PixelOriginInterpretationValues.VOLUME``) or relative to an individual frame - (``highdicom.sr.enum.PixelOriginInterpretationValues.FRAME``) + (``highdicom.sr.PixelOriginInterpretationValues.FRAME``) of the source image - (default: ``highdicom.sr.enum.PixelOriginInterpretationValues.VOLUME``) + (default: ``highdicom.sr.PixelOriginInterpretationValues.VOLUME``) """ # noqa graphic_type = GraphicTypeValues(graphic_type) @@ -516,7 +516,7 @@ def __init__( """ Parameters ---------- - graphic_type: Union[highdicom.sr.enum.GraphicTypeValues3D, str] + graphic_type: Union[highdicom.sr.GraphicTypeValues3D, str] name of the graphic type graphic_data: numpy.ndarray array of ordered spatial coordinates, where each row of the array @@ -566,16 +566,16 @@ def __init__( """ Parameters ---------- - graphic_type: Union[highdicom.sr.enum.GraphicTypeValues3D, str] + graphic_type: Union[highdicom.sr.GraphicTypeValues3D, str] name of the graphic type graphic_data: Sequence[Sequence[int]] ordered set of (row, column, frame) coordinate pairs frame_of_reference_uid: str unique identifier of the frame of reference within which the coordinates are defined - source_images: Sequence[highdicom.sr.content.SourceImageForSegmentation], optional + source_images: Sequence[highdicom.sr.SourceImageForSegmentation], optional source images for segmentation - source_series: highdicom.sr.content.SourceSeriesForSegmentation, optional + source_series: highdicom.sr.SourceSeriesForSegmentation, optional source series for segmentation Note @@ -660,7 +660,7 @@ def from_source_value_map( Returns ------- - highdicom.sr.content.RealWorldValueMap + highdicom.sr.RealWorldValueMap Content item representing a reference to the image dataset """ @@ -686,13 +686,13 @@ def __init__( """ Parameters ---------- - anatomic_location: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + anatomic_location: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] coded anatomic location (region or structure) - laterality: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + laterality: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional coded laterality (see `CID 244 `_ "Laterality" for options) - topographical_modifier: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + topographical_modifier: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional coded modifier of anatomic location """ # noqa @@ -757,7 +757,7 @@ def __init__( number of the segment to which the refernce applies frame_number: int number of the frame to which the reference applies - source_image: highdicom.sr.content.SourceImageForSegmentation + source_image: highdicom.sr.SourceImageForSegmentation source image for segmentation """ @@ -801,7 +801,7 @@ def from_segmentation( Returns ------- - highdicom.sr.content.ReferencedSegment + highdicom.sr.ReferencedSegment Content item representing a reference to the segment Notes @@ -940,9 +940,9 @@ def __init__( frame_numbers: Sequence[int], optional numbers of the frames to which the reference applies (in case a segmentation instance is referenced) - source_images: Sequence[highdicom.sr.content.SourceImageForSegmentation], optional + source_images: Sequence[highdicom.sr.SourceImageForSegmentation], optional source images for segmentation - source_series: highdicom.sr.content.SourceSeriesForSegmentation, optional + source_series: highdicom.sr.SourceSeriesForSegmentation, optional source series for segmentation Note @@ -1010,7 +1010,7 @@ def from_segmentation( Returns ------- - highdicom.sr.content.ReferencedSegment + highdicom.sr.ReferencedSegment Content item representing a reference to the segment Notes diff --git a/src/highdicom/sr/sop.py b/src/highdicom/sr/sop.py index 060b3965..69fd260b 100644 --- a/src/highdicom/sr/sop.py +++ b/src/highdicom/sr/sop.py @@ -96,7 +96,7 @@ def __init__( verifying_organization: str, optional Name of the organization that verfied the SR document (required if `is_verified`) - performed_procedure_codes: List[highdicom.sr.coding.CodedConcept], optional + performed_procedure_codes: List[highdicom.sr.CodedConcept], optional Codes of the performed procedures that resulted in the SR document requested_procedures: List[pydicom.dataset.Dataset], optional Requested procedures that are being fullfilled by creation of the @@ -423,7 +423,7 @@ def __init__( verifying_organization: str, optional Name of the organization that verfied the SR document (required if `is_verified`) - performed_procedure_codes: List[highdicom.sr.coding.CodedConcept], optional + performed_procedure_codes: List[highdicom.sr.CodedConcept], optional Codes of the performed procedures that resulted in the SR document requested_procedures: List[pydicom.dataset.Dataset], optional Requested procedures that are being fullfilled by creation of the @@ -551,7 +551,7 @@ def __init__( verifying_organization: str, optional Name of the organization that verfied the SR document (required if `is_verified`) - performed_procedure_codes: List[highdicom.sr.coding.CodedConcept], optional + performed_procedure_codes: List[highdicom.sr.CodedConcept], optional Codes of the performed procedures that resulted in the SR document requested_procedures: List[pydicom.dataset.Dataset] Requested procedures that are being fullfilled by creation of the @@ -679,7 +679,7 @@ def __init__( verifying_organization: str, optional Name of the organization that verfied the SR document (required if `is_verified`) - performed_procedure_codes: List[highdicom.sr.coding.CodedConcept], optional + performed_procedure_codes: List[highdicom.sr.CodedConcept], optional Codes of the performed procedures that resulted in the SR document requested_procedures: List[pydicom.dataset.Dataset] Requested procedures that are being fullfilled by creation of the diff --git a/src/highdicom/sr/templates.py b/src/highdicom/sr/templates.py index 34c660d3..804a1d6d 100644 --- a/src/highdicom/sr/templates.py +++ b/src/highdicom/sr/templates.py @@ -178,7 +178,7 @@ def __init__( ---------- time_point: str actual value representation of the time point - time_point_type: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + time_point_type: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional coded type of time point, e.g., "Baseline" or "Posttreatment" (see `CID 6146 `_ "Time Point Types" for options) @@ -193,7 +193,7 @@ def __init__( identifier of a specific time point in a time series, which is unique within an appropriate local context and specific to a particular protocol using the same value for different subjects - temporal_offset_from_event: highdicom.sr.content.LongitudinalTemporalOffsetFromEvent, optional + temporal_offset_from_event: highdicom.sr.LongitudinalTemporalOffsetFromEvent, optional offset in time from a particular event of significance, e.g., the baseline of an imaging study or enrollment into a clinical trial @@ -279,7 +279,7 @@ def __init__( Parameters ---------- - values: Sequence[highdicom.sr.value_types.NumContentItem] + values: Sequence[highdicom.sr.NumContentItem] reference values of the population of measurements, e.g., its mean or standard deviation (see `CID 226 `_ @@ -342,7 +342,7 @@ def __init__( Parameters ---------- - values: Sequence[highdicom.sr.value_types.NumContentItem] + values: Sequence[highdicom.sr.NumContentItem] reference values of the normal range, e.g., its upper and lower bound (see `CID 223 `_ @@ -400,23 +400,23 @@ def __init__( Parameters ---------- - normality: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + normality: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional the extend to which the measurement is considered normal or abnormal (see `CID 222 `_ "Normality Codes" for options) - level_of_significance: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + level_of_significance: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional the extend to which the measurement is considered normal or abnormal (see `CID 220 `_ "Level of Significance" for options) - selection_status: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + selection_status: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional how the measurement value was selected or computed from a set of available values (see `CID 224 `_ "Selection Method" for options) - measurement_statistical_properties: highdicom.sr.templates.MeasurementStatisticalProperties, optional + measurement_statistical_properties: highdicom.sr.MeasurementStatisticalProperties, optional statistical properties of a reference population for a measurement and/or the position of a measurement in such a reference population - normal_range_properties: highdicom.sr.templates.NormalRangeProperties, optional + normal_range_properties: highdicom.sr.NormalRangeProperties, optional statistical properties of a reference population for a measurement and/or the position of a measurement in such a reference population upper_measurement_uncertainty: Union[int, float], optional @@ -522,9 +522,9 @@ def __init__( login name of the person organization_name: str, optional name of the person's organization - role_in_organization: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + role_in_organization: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional role of the person within the organization - role_in_procedure: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + role_in_procedure: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional role of the person in the reported procedure """ # noqa @@ -697,11 +697,11 @@ def __init__( Parameters ---------- - observer_type: highdicom.sr.coding.CodedConcept + observer_type: highdicom.sr.CodedConcept type of observer (see `CID 270 `_ "Observer Type" for options) - observer_identifying_attributes: Union[highdicom.sr.templates.PersonObserverIdentifyingAttributes, highdicom.sr.templates.DeviceObserverIdentifyingAttributes] + observer_identifying_attributes: Union[highdicom.sr.PersonObserverIdentifyingAttributes, highdicom.sr.DeviceObserverIdentifyingAttributes] observer identifying attributes """ # noqa @@ -794,7 +794,7 @@ def __init__( container_identifier: str, optional identifier of the container holding the speciment (e.g., a glass slide) - specimen_type: highdicom.sr.coding.CodedConcept, optional + specimen_type: highdicom.sr.CodedConcept, optional type of the specimen (see `CID 8103 `_ "Anatomic Pathology Specimen Types" for options) @@ -954,11 +954,11 @@ def __init__( Parameters ---------- - subject_class: highdicom.sr.coding.CodedConcept + subject_class: highdicom.sr.CodedConcept type of subject if the subject of the report is not the patient (see `CID 271 `_ "Observation Subject Class" for options) - subject_class_specific_context: Union[highdicom.sr.templates.SubjectContextFetus, highdicom.sr.templates.SubjectContextSpecimen, highdicom.sr.templates.SubjectContextDevice], optional + subject_class_specific_context: Union[highdicom.sr.SubjectContextFetus, highdicom.sr.SubjectContextSpecimen, highdicom.sr.SubjectContextDevice], optional additional context information specific to `subject_class` """ # noqa @@ -992,12 +992,12 @@ def __init__( Parameters ---------- - observer_person_context: [highdicom.sr.templates.ObserverContext, None], optional + observer_person_context: [highdicom.sr.ObserverContext, None], optional description of the person that reported the observation - observer_device_context: highdicom.sr.templates.ObserverContext, optional + observer_device_context: highdicom.sr.ObserverContext, optional description of the device that was involved in reporting the observation - subject_context: highdicom.sr.templates.SubjectContext, optional + subject_context: highdicom.sr.SubjectContext, optional description of the imaging subject in case it is not the patient for which the report is generated (e.g., a pathology specimen in a whole-slide microscopy image, a fetus in an ultrasound image, or @@ -1043,7 +1043,7 @@ def __init__(self, language: CodedConcept): Parameters ---------- - language: highdicom.sr.coding.CodedConcept + language: highdicom.sr.CodedConcept language used for content items included in report """ @@ -1084,45 +1084,45 @@ def __init__( Parameters ---------- - name: highdicom.sr.coding.CodedConcept + name: highdicom.sr.CodedConcept Name of the measurement (see `CID 7469 `_ "Generic Intensity and Size Measurements" and `CID 7468 `_ "Texture Measurements" for options) - tracking_identifier: highdicom.sr.templates.TrackingIdentifier, optional + tracking_identifier: highdicom.sr.TrackingIdentifier, optional Identifier for tracking measurements value: Union[int, float], optional Numeric measurement value - unit: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + unit: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional Unit of the numeric measurement value (see `CID 7181 `_ "Abstract Multi-dimensional Image Model Component Units" for options) - qualifier: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + qualifier: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional Qualification of numeric measurement value or as an alternative qualitative description - algorithm_id: highdicom.sr.templates.AlgorithmIdentification, optional + algorithm_id: highdicom.sr.AlgorithmIdentification, optional Identification of algorithm used for making measurements - derivation: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + derivation: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional How the value was computed (see `CID 7464 `_ "General Region of Interest Measurement Modifiers" for options) - finding_sites: Sequence[highdicom.sr.content.FindingSite], optional + finding_sites: Sequence[highdicom.sr.FindingSite], optional Coded description of one or more anatomic locations corresonding to the image region from which measurement was taken - method: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + method: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional Measurement method (see `CID 6147 `_ "Response Criteria" for options) - properties: highdicom.sr.templates.MeasurementProperties, optional + properties: highdicom.sr.MeasurementProperties, optional Measurement properties, including evaluations of its normality and/or significance, its relationship to a reference population, and an indication of its selection from a set of measurements - referenced_images: Sequence[highdicom.sr.content.SourceImageForMeasurement], optional + referenced_images: Sequence[highdicom.sr.SourceImageForMeasurement], optional Referenced images which were used as sources for the measurement - referenced_real_world_value_map: highdicom.sr.content.RealWorldValueMap, optional + referenced_real_world_value_map: highdicom.sr.RealWorldValueMap, optional Referenced real world value map for referenced source images """ # noqa @@ -1232,28 +1232,28 @@ def __init__( Parameters ---------- - tracking_identifier: highdicom.sr.templates.TrackingIdentifier + tracking_identifier: highdicom.sr.TrackingIdentifier Identifier for tracking measurements - referenced_real_world_value_map: highdicom.sr.content.RealWorldValueMap, optional + referenced_real_world_value_map: highdicom.sr.RealWorldValueMap, optional Referenced real world value map for region of interest - time_point_context: highdicom.sr.templates.TimePointContext, optional + time_point_context: highdicom.sr.TimePointContext, optional Description of the time point context - finding_type: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + finding_type: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional Type of object that was measured, e.g., organ or tumor - method: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + method: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional coded measurement method (see `CID 6147 `_ "Response Criteria" for options) - algorithm_id: highdicom.sr.templates.AlgorithmIdentification, optional + algorithm_id: highdicom.sr.AlgorithmIdentification, optional identification of algorithm used for making measurements - finding_sites: Sequence[highdicom.sr.content.FindingSite], optional + finding_sites: Sequence[highdicom.sr.FindingSite], optional Coded description of one or more anatomic locations corresonding to the image region from which measurement was taken session: str, optional Description of the session - measurements: Sequence[highdicom.sr.templates.Measurement], optional + measurements: Sequence[highdicom.sr.Measurement], optional Numeric measurements - qualitative_evaluations: Sequence[highdicom.sr.value_types.CodeContentItem], optional + qualitative_evaluations: Sequence[highdicom.sr.CodeContentItem], optional Coded name-value pairs that describe measurements in qualitative terms @@ -1398,37 +1398,37 @@ def __init__( Parameters ---------- - tracking_identifier: highdicom.sr.templates.TrackingIdentifier + tracking_identifier: highdicom.sr.TrackingIdentifier identifier for tracking measurements - referenced_regions: Union[Sequence[highdicom.sr.content.ImageRegion], Sequence[highdicom.sr.content.ImageRegion3D]], optional + referenced_regions: Union[Sequence[highdicom.sr.ImageRegion], Sequence[highdicom.sr.ImageRegion3D]], optional regions of interest in source image(s) - referenced_segment: Union[highdicom.sr.content.ReferencedSegment, highdicom.sr.content.ReferencedSegmentationFrame], optional + referenced_segment: Union[highdicom.sr.ReferencedSegment, highdicom.sr.ReferencedSegmentationFrame], optional segmentation for region of interest in source image referenced_volume_surface: hidicom.sr.content.VolumeSurface, optional surface segmentation for region of interest in source image - referenced_real_world_value_map: highdicom.sr.content.RealWorldValueMap, optional + referenced_real_world_value_map: highdicom.sr.RealWorldValueMap, optional referenced real world value map for region of interest - time_point_context: highdicom.sr.templates.TimePointContext, optional + time_point_context: highdicom.sr.TimePointContext, optional description of the time point context - finding_type: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + finding_type: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional type of object that was measured, e.g., organ or tumor - method: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + method: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional coded measurement method (see `CID 6147 `_ "Response Criteria" for options) - algorithm_id: highdicom.sr.templates.AlgorithmIdentification, optional + algorithm_id: highdicom.sr.AlgorithmIdentification, optional identification of algorithm used for making measurements - finding_sites: Sequence[highdicom.sr.content.FindingSite], optional + finding_sites: Sequence[highdicom.sr.FindingSite], optional Coded description of one or more anatomic locations corresonding to the image region from which measurement was taken session: str, optional description of the session - measurements: Sequence[highdicom.sr.templates.Measurement], optional + measurements: Sequence[highdicom.sr.Measurement], optional numeric measurements - qualitative_evaluations: Sequence[highdicom.sr.value_types.CodeContentItem], optional + qualitative_evaluations: Sequence[highdicom.sr.CodeContentItem], optional coded name-value (question-answer) pairs that describe the measurements in qualitative terms - geometric_purpose: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + geometric_purpose: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional geometric interpretation of region of interest (see `CID 219 `_ "Geometry Graphical Representation" for options) @@ -1544,35 +1544,35 @@ def __init__( Parameters ---------- - tracking_identifier: highdicom.sr.templates.TrackingIdentifier + tracking_identifier: highdicom.sr.TrackingIdentifier identifier for tracking measurements - referenced_region: Union[highdicom.sr.content.ImageRegion, highdicom.sr.content.ImageRegion3D], optional + referenced_region: Union[highdicom.sr.ImageRegion, highdicom.sr.ImageRegion3D], optional region of interest in source image - referenced_segment: highdicom.sr.content.ReferencedSegmentationFrame, optional + referenced_segment: highdicom.sr.ReferencedSegmentationFrame, optional segmentation for region of interest in source image - referenced_real_world_value_map: highdicom.sr.content.RealWorldValueMap, optional + referenced_real_world_value_map: highdicom.sr.RealWorldValueMap, optional referenced real world value map for region of interest - time_point_context: highdicom.sr.templates.TimePointContext, optional + time_point_context: highdicom.sr.TimePointContext, optional description of the time point context - finding_type: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + finding_type: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional type of object that was measured, e.g., organ or tumor - method: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + method: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional coded measurement method (see `CID 6147 `_ "Response Criteria" for options) - algorithm_id: highdicom.sr.templates.AlgorithmIdentification, optional + algorithm_id: highdicom.sr.AlgorithmIdentification, optional identification of algorithm used for making measurements - finding_sites: Sequence[highdicom.sr.content.FindingSite], optional + finding_sites: Sequence[highdicom.sr.FindingSite], optional Coded description of one or more anatomic locations corresonding to the image region from which measurement was taken session: str, optional description of the session - measurements: Sequence[highdicom.sr.templates.Measurement], optional + measurements: Sequence[highdicom.sr.Measurement], optional measurements for a region of interest - qualitative_evaluations: Sequence[highdicom.sr.value_types.CodeContentItem], optional + qualitative_evaluations: Sequence[highdicom.sr.CodeContentItem], optional coded name-value (question-answer) pairs that describe the measurements in qualitative terms for a region of interest - geometric_purpose: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + geometric_purpose: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional geometric interpretation of region of interest (see `CID 219 `_ "Geometry Graphical Representation" for options) @@ -1649,37 +1649,37 @@ def __init__( Parameters ---------- - tracking_identifier: highdicom.sr.templates.TrackingIdentifier + tracking_identifier: highdicom.sr.TrackingIdentifier identifier for tracking measurements - referenced_regions: Union[Sequence[highdicom.sr.content.ImageRegion], Sequence[highdicom.sr.content.ImageRegion3D]], optional + referenced_regions: Union[Sequence[highdicom.sr.ImageRegion], Sequence[highdicom.sr.ImageRegion3D]], optional regions of interest in source image(s) - referenced_volume_surface: highdicom.sr.content.VolumeSurface, optional + referenced_volume_surface: highdicom.sr.VolumeSurface, optional volume of interest in source image(s) - referenced_segment: highdicom.sr.content.ReferencedSegment, optional + referenced_segment: highdicom.sr.ReferencedSegment, optional segmentation for region of interest in source image - referenced_real_world_value_map: highdicom.sr.content.RealWorldValueMap, optional + referenced_real_world_value_map: highdicom.sr.RealWorldValueMap, optional referenced real world value map for region of interest - time_point_context: highdicom.sr.templates.TimePointContext, optional + time_point_context: highdicom.sr.TimePointContext, optional description of the time point context - finding_type: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + finding_type: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional type of object that was measured, e.g., organ or tumor - method: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + method: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional coded measurement method (see `CID 6147 `_ "Response Criteria" for options) - algorithm_id: highdicom.sr.templates.AlgorithmIdentification, optional + algorithm_id: highdicom.sr.AlgorithmIdentification, optional identification of algorithm used for making measurements - finding_sites: Sequence[highdicom.sr.content.FindingSite], optional + finding_sites: Sequence[highdicom.sr.FindingSite], optional Coded description of one or more anatomic locations corresonding to the image region from which measurement was taken session: str, optional description of the session - measurements: Sequence[highdicom.sr.templates.Measurement], optional + measurements: Sequence[highdicom.sr.Measurement], optional measurements for a volume of interest - qualitative_evaluations: Sequence[highdicom.sr.value_types.CodeContentItem], optional + qualitative_evaluations: Sequence[highdicom.sr.CodeContentItem], optional coded name-value (question-answer) pairs that describe the measurements in qualitative terms for a volume of interest - geometric_purpose: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + geometric_purpose: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional geometric interpretation of region of interest (see `CID 219 `_ "Geometry Graphical Representation" for options) @@ -1726,16 +1726,16 @@ def __init__( Parameters ---------- - derivation: Sequence[highdicom.sr.coding.CodedConcept] + derivation: Sequence[highdicom.sr.CodedConcept] methods for derivation of measurements from multiple ROIs measurements (see `CID 7465 `_ "Measurements Derived From Multiple ROI Measurements" for options) - measurement_groups: Union[Sequence[highdicom.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations], Sequence[highdicom.sr.templates.VolumetricROIMeasurementsAndQualitativeEvaluations]] + measurement_groups: Union[Sequence[highdicom.sr.PlanarROIMeasurementsAndQualitativeEvaluations], Sequence[highdicom.sr.VolumetricROIMeasurementsAndQualitativeEvaluations]] one or more groups of either planar or volumetric ROI measurements and qualitative evaluations - measurement_properties: highdicom.sr.templates.MeasurementProperties, optional + measurement_properties: highdicom.sr.MeasurementProperties, optional measurement properties, including evaluations of its normality and/or significance, its relationship to a reference population, and an indication of its selection from a set of measurements @@ -1800,24 +1800,24 @@ def __init__( Parameters ---------- - observation_context: highdicom.sr.templates.ObservationContext + observation_context: highdicom.sr.ObservationContext description of the observation context - procedure_reported: Union[Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], Sequence[Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code]]] + procedure_reported: Union[Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], Sequence[Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code]]] one or more coded description(s) of the procedure (see `CID 100 `_ "Quantitative Diagnostic Imaging Procedures" for options) - imaging_measurements: Sequence[Union[highdicom.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations, highdicom.sr.templates.VolumetricROIMeasurementsAndQualitativeEvaluations, highdicom.sr.templates.MeasurementsAndQualitativeEvaluations]], optional + imaging_measurements: Sequence[Union[highdicom.sr.PlanarROIMeasurementsAndQualitativeEvaluations, highdicom.sr.VolumetricROIMeasurementsAndQualitativeEvaluations, highdicom.sr.MeasurementsAndQualitativeEvaluations]], optional measurements and qualitative evaluations of images or regions within images - derived_imaging_measurements: Sequence[highdicom.sr.templates.MeasurementsDerivedFromMultipleROIMeasurements], optional + derived_imaging_measurements: Sequence[highdicom.sr.MeasurementsDerivedFromMultipleROIMeasurements], optional measurements derived from other measurements of images or regions within images qualitative evaluations of images - title: highdicom.sr.coding.CodedConcept, optional + title: highdicom.sr.CodedConcept, optional title of the report (see `CID 7021 `_ "Measurement Report Document Titles" for options) - language_of_content_item_and_descendants: highdicom.sr.templates.LanguageOfContentItemAndDescendants, optional + language_of_content_item_and_descendants: highdicom.sr.LanguageOfContentItemAndDescendants, optional specification of the language of report content items (defaults to English) diff --git a/src/highdicom/sr/utils.py b/src/highdicom/sr/utils.py index 7da33843..ea1b1172 100644 --- a/src/highdicom/sr/utils.py +++ b/src/highdicom/sr/utils.py @@ -23,14 +23,14 @@ def find_content_items( ---------- dataset: pydicom.dataset.Dataset SR document instance - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional Coded name that items should have - value_type: Union[highdicom.sr.enum.ValueTypeValues, str], optional + value_type: Union[highdicom.sr.ValueTypeValues, str], optional Type of value that items should have - (e.g. ``highdicom.sr.enum.ValueTypeValues.CONTAINER``) - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + (e.g. ``highdicom.sr.ValueTypeValues.CONTAINER``) + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional Type of relationship that items should have with its parent - (e.g. ``highdicom.sr.enum.RelationshipTypeValues.CONTAINS``) + (e.g. ``highdicom.sr.RelationshipTypeValues.CONTAINS``) recursive: bool, optional Whether search should be performed recursively, i.e. whether contained child content items should also be queried @@ -121,14 +121,14 @@ def search_tree( def get_coded_name(item: Dataset) -> CodedConcept: """Gets the concept name of a SR Content Item. - Parameter - --------- + Parameters + ---------- item: pydicom.dataset.Dataset Content Item Returns ------- - highdicom.sr.coding.CodedConcept + highdicom.sr.CodedConcept Concept name """ @@ -150,14 +150,14 @@ def get_coded_name(item: Dataset) -> CodedConcept: def get_coded_value(item: Dataset) -> CodedConcept: """Gets the value of a SR Content Item with Value Type CODE. - Parameter - --------- + Parameters + ---------- item: pydicom.dataset.Dataset Content Item Returns ------- - highdicom.sr.coding.CodedConcept + highdicom.sr.CodedConcept Value """ diff --git a/src/highdicom/sr/value_types.py b/src/highdicom/sr/value_types.py index c47810d5..1641204a 100644 --- a/src/highdicom/sr/value_types.py +++ b/src/highdicom/sr/value_types.py @@ -36,11 +36,11 @@ def __init__( """ Parameters ---------- - value_type: Union[str, highdicom.sr.enum.ValueTypeValues] + value_type: Union[str, highdicom.sr.ValueTypeValues] type of value encoded in a content item - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] coded name or an enumerated item representing a coded name - relationship_type: Union[str, highdicom.sr.enum.RelationshipTypeValues], optional + relationship_type: Union[str, highdicom.sr.RelationshipTypeValues], optional type of relationship with parent content item """ # noqa @@ -66,13 +66,13 @@ def __setattr__(self, name: str, value: Any) -> None: @property def name(self) -> CodedConcept: - """CodedConcept: coded name of the content item""" + """highdicom.sr.CodedConcept: coded name of the content item""" return self.ConceptNameCodeSequence[0] @property def value_type(self) -> str: """str: type of the content item - (see `highdicom.sr.value_types.ValueTypeValues`) + (see `highdicom.sr.ValueTypeValues`) """ return self.ValueType @@ -80,7 +80,7 @@ def value_type(self) -> str: @property def relationship_type(self) -> str: """str: type of relationship the content item has with its parent - (see `highdicom.sr.enum.RelationshipTypeValues`) + (see `highdicom.sr.RelationshipTypeValues`) """ return getattr(self, 'RelationshipType', None) @@ -112,7 +112,7 @@ def get_nodes(self) -> 'ContentSequence': Returns ------- - highdicom.sr.value_types.ContentSequence[highdicom.sr.value_types.ContentItem] + highdicom.sr.ContentSequence[highdicom.sr.ContentItem] matched content items """ @@ -126,7 +126,7 @@ def append(self, item: ContentItem) -> None: Parameters ---------- - item: highdicom.sr.value_types.ContentItem + item: highdicom.sr.ContentItem content item """ @@ -143,7 +143,7 @@ def extend(self, items: Sequence[ContentItem]) -> None: Parameters ---------- - items: Sequence[highdicom.sr.value_types.ContentItem] + items: Sequence[highdicom.sr.ContentItem] content items """ @@ -157,7 +157,7 @@ def insert(self, position: int, item: ContentItem) -> None: ---------- position: int index position - item: highdicom.sr.value_types.ContentItem + item: highdicom.sr.ContentItem content item """ @@ -185,11 +185,11 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name - value: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + value: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] coded value or an enumerated item representing a coded value - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -220,11 +220,11 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name value: Union[str, pydicom.valuerep.PersonName] name of the person - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -249,11 +249,11 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name value: str description of the concept in free text - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -278,11 +278,11 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name value: Union[str, datetime.time, pydicom.valuerep.TM] time - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -307,11 +307,11 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name value: Union[str, datetime.date, pydicom.valuerep.DA] date - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -336,11 +336,11 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name value: Union[str, datetime.datetime, pydicom.valuerep.DT] datetime - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -365,11 +365,11 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name value: Union[pydicom.uid.UID, str] unique identifier - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -396,19 +396,19 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name value: Union[int, float], optional numeric value - unit: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + unit: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional coded units of measurement (see `CID 7181 `_ "Abstract Multi-dimensional Image Model Component Units") - qualifier: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional + qualifier: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code], optional qualification of numeric value or as an alternative to numeric value, e.g., reason for absence of numeric value (see `CID 42 `_ "Numeric Value Qualifier" for options) - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item Note @@ -469,7 +469,7 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name is_content_continous: bool, optional whether contained content items are logically linked in a @@ -510,13 +510,13 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name referenced_sop_class_uid: Union[pydicom.uid.UID, str] SOP Class UID of the referenced object referenced_sop_instance_uid: Union[pydicom.uid.UID, str] SOP Instance UID of the referenced object - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -551,7 +551,7 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name referenced_sop_class_uid: Union[pydicom.uid.UID, str] SOP Class UID of the referenced image object @@ -563,7 +563,7 @@ def __init__( referenced_segment_numbers: Union[int, Sequence[int]], optional number of segment(s) to which the refernce applies in case of a segmentation image - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -607,22 +607,22 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name - graphic_type: Union[highdicom.sr.enum.GraphicTypeValues, str] + graphic_type: Union[highdicom.sr.GraphicTypeValues, str] name of the graphic type graphic_data: numpy.ndarray[numpy.int] array of ordered spatial coordinates, where each row of the array represents a (column, row) coordinate pair - pixel_origin_interpretation: Union[highdicom.sr.enum.PixelOriginInterpretationValues, str] + pixel_origin_interpretation: Union[highdicom.sr.PixelOriginInterpretationValues, str] whether pixel coordinates specified by `graphic_data` are defined relative to the total pixel matrix - (``highdicom.sr.enum.PixelOriginInterpretationValues.VOLUME``) or + (``highdicom.sr.PixelOriginInterpretationValues.VOLUME``) or relative to an individual frame - (``highdicom.sr.enum.PixelOriginInterpretationValues.FRAME``) + (``highdicom.sr.PixelOriginInterpretationValues.FRAME``) fiducial_uid: Union[pydicom.uid.UID, str, None], optional unique identifier for the content item - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -702,9 +702,9 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name - graphic_type: Union[highdicom.sr.enum.GraphicTypeValues3D, str] + graphic_type: Union[highdicom.sr.GraphicTypeValues3D, str] name of the graphic type graphic_data: numpy.ndarray[numpy.float] array of spatial coordinates, where each row of the array @@ -714,7 +714,7 @@ def __init__( coordinates are defined fiducial_uid: str, optional unique identifier for the content item - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa @@ -775,9 +775,9 @@ def __init__( """ Parameters ---------- - name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] + name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name - temporal_range_type: Union[highdicom.sr.enum.TemporalRangeTypeValues, str] + temporal_range_type: Union[highdicom.sr.TemporalRangeTypeValues, str] name of the temporal range type referenced_sample_positions: Sequence[int], optional one-based relative sample position of acquired time points @@ -786,7 +786,7 @@ def __init__( seconds after start of the acquisition of the time series referenced_date_time: Sequence[datetime.datetime], optional absolute time points - relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional + relationship_type: Union[highdicom.sr.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index eea35425..a1dd8cff 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -103,7 +103,7 @@ def compute_plane_position_tiled_full( Returns ------- - highdicom.content.PlanePositionSequence + highdicom.PlanePositionSequence Positon of the plane in the slide coordinate system Raises @@ -160,7 +160,7 @@ def compute_plane_position_slide_per_frame( Returns ------- - List[highdicom.content.PlanePositionSequence] + List[highdicom.PlanePositionSequence] Plane Position Sequence per frame Raises diff --git a/tests/test_color.py b/tests/test_color.py new file mode 100644 index 00000000..ea59a41c --- /dev/null +++ b/tests/test_color.py @@ -0,0 +1,34 @@ +import unittest + +import numpy as np +import pytest +from PIL.ImageCms import ImageCmsProfile, createProfile + +from highdicom.color import ColorManager + + +class TestColorManager(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self._icc_profile = ImageCmsProfile(createProfile('sRGB')).tobytes() + + def test_construction(self) -> None: + ColorManager(self._icc_profile) + + def test_construction_without_profile(self) -> None: + with pytest.raises(TypeError): + ColorManager() # type: ignore + + def test_transform_frame(self) -> None: + manager = ColorManager(self._icc_profile) + frame = np.ones((10, 10, 3), dtype=np.uint8) * 255 + output = manager.transform_frame(frame) + assert output.shape == frame.shape + assert output.dtype == frame.dtype + + def test_transform_frame_wrong_shape(self) -> None: + manager = ColorManager(self._icc_profile) + frame = np.ones((10, 10), dtype=np.uint8) * 255 + with pytest.raises(ValueError): + manager.transform_frame(frame) diff --git a/tests/test_content.py b/tests/test_content.py new file mode 100644 index 00000000..65efa1b3 --- /dev/null +++ b/tests/test_content.py @@ -0,0 +1,108 @@ +from unittest import TestCase + +import pytest + +from highdicom import ( + PixelMeasuresSequence, + PlaneOrientationSequence, + PlanePositionSequence, +) + + +class TestPlanePosititionSequence(TestCase): + + def test_construction_slide(self): + coordinate_system = 'SLIDE' + image_position = [0., 0., 0.] + matrix_position = [0, 0] + seq = PlanePositionSequence( + coordinate_system=coordinate_system, + image_position=image_position, + pixel_matrix_position=matrix_position + ) + assert len(seq) == 1 + item = seq[0] + assert float(item.XOffsetInSlideCoordinateSystem) == image_position[0] + assert float(item.YOffsetInSlideCoordinateSystem) == image_position[1] + assert float(item.ZOffsetInSlideCoordinateSystem) == image_position[2] + assert item.RowPositionInTotalImagePixelMatrix == matrix_position[1] + assert item.ColumnPositionInTotalImagePixelMatrix == matrix_position[0] + + def test_construction_patient(self): + coordinate_system = 'PATIENT' + image_position = [0., 0., 0.] + seq = PlanePositionSequence( + coordinate_system=coordinate_system, + image_position=image_position, + ) + assert len(seq) == 1 + item = seq[0] + assert item.ImagePositionPatient[0] == image_position[0] + assert item.ImagePositionPatient[1] == image_position[1] + assert item.ImagePositionPatient[2] == image_position[2] + + +class TestPlaneOrientationSequence(TestCase): + + def test_construction_slide(self): + coordinate_system = 'SLIDE' + image_orientation = [0., 1., 0., 1., 0., 0.] + seq = PlaneOrientationSequence( + coordinate_system=coordinate_system, + image_orientation=image_orientation, + ) + assert len(seq) == 1 + item = seq[0] + assert item.ImageOrientationSlide[0] == image_orientation[0] + assert item.ImageOrientationSlide[1] == image_orientation[1] + assert item.ImageOrientationSlide[2] == image_orientation[2] + assert item.ImageOrientationSlide[3] == image_orientation[3] + assert item.ImageOrientationSlide[4] == image_orientation[4] + assert item.ImageOrientationSlide[5] == image_orientation[5] + + def test_construction_patient(self): + coordinate_system = 'PATIENT' + image_orientation = [0., 1., 0., 1., 0., 0.] + seq = PlaneOrientationSequence( + coordinate_system=coordinate_system, + image_orientation=image_orientation, + ) + assert len(seq) == 1 + item = seq[0] + assert item.ImageOrientationPatient[0] == image_orientation[0] + assert item.ImageOrientationPatient[1] == image_orientation[1] + assert item.ImageOrientationPatient[2] == image_orientation[2] + assert item.ImageOrientationPatient[3] == image_orientation[3] + assert item.ImageOrientationPatient[4] == image_orientation[4] + assert item.ImageOrientationPatient[5] == image_orientation[5] + + +class TestPixelMeasuresSequence(TestCase): + + def test_construction(self): + pixel_spacing = [0., 0.] + slice_thickness = 0. + seq = PixelMeasuresSequence( + pixel_spacing=pixel_spacing, + slice_thickness=slice_thickness + ) + assert len(seq) == 1 + item = seq[0] + assert item.PixelSpacing[0] == pixel_spacing[0] + assert item.PixelSpacing[1] == pixel_spacing[1] + assert item.SliceThickness == slice_thickness + with pytest.raises(AttributeError): + item.SpacingBetweenSlices + + def test_construction_with_spacing_between_slices(self): + pixel_spacing = [0., 0.] + slice_thickness = 0. + spacing_between_slices = 0. + seq = PixelMeasuresSequence( + pixel_spacing=pixel_spacing, + slice_thickness=slice_thickness, + spacing_between_slices=spacing_between_slices + ) + assert len(seq) == 1 + item = seq[0] + assert item.SpacingBetweenSlices == spacing_between_slices diff --git a/tests/test_frame.py b/tests/test_frame.py new file mode 100644 index 00000000..f44aa775 --- /dev/null +++ b/tests/test_frame.py @@ -0,0 +1,218 @@ +from unittest import TestCase +from pathlib import Path + +import numpy as np +import pytest +from pydicom.uid import ( + JPEG2000Lossless, + JPEGBaseline, +) + +from highdicom.frame import decode_frame, encode_frame + + +class TestDecodeFrame(TestCase): + + def setUp(self): + super().setUp() + file_path = Path(__file__) + self._test_files_dir = file_path.parent.parent.joinpath( + 'data', + 'test_files' + ) + + def test_jpeg_rgb(self): + filepath = str(self._test_files_dir.joinpath('frame_rgb_empty.jpeg')) + with open(filepath, 'br') as fp: + compressed_frame = fp.read() + rows = 16 + columns = 32 + samples_per_pixel = 3 + bits_allocated = 8 + frame = decode_frame( + value=compressed_frame, + transfer_syntax_uid=JPEGBaseline, + rows=rows, + columns=columns, + samples_per_pixel=samples_per_pixel, + bits_allocated=bits_allocated, + bits_stored=bits_allocated, + photometric_interpretation='YBR_FULL', + pixel_representation=0, + planar_configuration=0 + ) + assert frame.shape[0] == rows + assert frame.shape[1] == columns + assert frame.shape[2] == samples_per_pixel + assert str(frame.dtype) == f'uint{bits_allocated}' + + def test_jpeg_rgb_wrong_photometric_interpretation(self): + with pytest.raises(ValueError): + decode_frame( + value=b'', + transfer_syntax_uid=JPEGBaseline, + rows=16, + columns=32, + samples_per_pixel=3, + bits_allocated=8, + bits_stored=8, + photometric_interpretation='MONOCHROME', + pixel_representation=0, + planar_configuration=0 + ) + + def test_jpeg_rgb_missing_planar_configuration(self): + with pytest.raises(ValueError): + decode_frame( + value=b'', + transfer_syntax_uid=JPEGBaseline, + rows=16, + columns=32, + samples_per_pixel=3, + bits_allocated=8, + bits_stored=8, + photometric_interpretation='RGB', + pixel_representation=0, + ) + + +class TestEncodeFrame(TestCase): + + def setUp(self): + super().setUp() + + def test_jpeg_rgb(self): + bits_allocated = 8 + frame = np.ones((16, 32, 3), dtype=np.dtype(f'uint{bits_allocated}')) + frame *= 255 + compressed_frame = encode_frame( + frame, + transfer_syntax_uid=JPEGBaseline, + bits_allocated=bits_allocated, + bits_stored=bits_allocated, + photometric_interpretation='YBR_FULL_422', + pixel_representation=0, + planar_configuration=0 + ) + assert compressed_frame.startswith(b'\xFF\xD8') + assert compressed_frame.endswith(b'\xFF\xD9') + + def test_jpeg_monochrome(self): + bits_allocated = 8 + frame = np.zeros((16, 32), dtype=np.dtype(f'uint{bits_allocated}')) + compressed_frame = encode_frame( + frame, + transfer_syntax_uid=JPEGBaseline, + bits_allocated=bits_allocated, + bits_stored=bits_allocated, + photometric_interpretation='MONOCHROME1', + pixel_representation=0 + ) + assert compressed_frame.startswith(b'\xFF\xD8') + assert compressed_frame.endswith(b'\xFF\xD9') + + def test_jpeg2000_rgb(self): + bits_allocated = 8 + frame = np.ones((16, 32, 3), dtype=np.dtype(f'uint{bits_allocated}')) + frame *= 255 + compressed_frame = encode_frame( + frame, + transfer_syntax_uid=JPEG2000Lossless, + bits_allocated=bits_allocated, + bits_stored=bits_allocated, + photometric_interpretation='YBR_FULL', + pixel_representation=0, + planar_configuration=0 + ) + assert compressed_frame.startswith(b'\x00\x00\x00\x0C\x6A\x50\x20') + assert compressed_frame.endswith(b'\xFF\xD9') + decoded_frame = decode_frame( + value=compressed_frame, + transfer_syntax_uid=JPEG2000Lossless, + rows=frame.shape[0], + columns=frame.shape[1], + samples_per_pixel=frame.shape[2], + bits_allocated=bits_allocated, + bits_stored=bits_allocated, + photometric_interpretation='YBR_FULL', + pixel_representation=0, + planar_configuration=0 + ) + np.testing.assert_array_equal(frame, decoded_frame) + + def test_jpeg2000_monochrome(self): + bits_allocated = 16 + frame = np.zeros((16, 32), dtype=np.dtype(f'uint{bits_allocated}')) + compressed_frame = encode_frame( + frame, + transfer_syntax_uid=JPEG2000Lossless, + bits_allocated=bits_allocated, + bits_stored=bits_allocated, + photometric_interpretation='MONOCHROME1', + pixel_representation=0, + ) + assert compressed_frame.startswith(b'\x00\x00\x00\x0C\x6A\x50\x20') + assert compressed_frame.endswith(b'\xFF\xD9') + decoded_frame = decode_frame( + value=compressed_frame, + transfer_syntax_uid=JPEG2000Lossless, + rows=frame.shape[0], + columns=frame.shape[1], + samples_per_pixel=1, + bits_allocated=bits_allocated, + bits_stored=bits_allocated, + photometric_interpretation='MONOCHROME1', + pixel_representation=0, + planar_configuration=0 + ) + np.testing.assert_array_equal(frame, decoded_frame) + + def test_jpeg_rgb_wrong_photometric_interpretation(self): + frame = np.ones((16, 32, 3), dtype=np.uint8) + with pytest.raises(ValueError): + encode_frame( + frame, + transfer_syntax_uid=JPEGBaseline, + bits_allocated=8, + bits_stored=8, + photometric_interpretation='RGB', + pixel_representation=0, + planar_configuration=0 + ) + + def test_jpeg_rgb_wrong_planar_configuration(self): + frame = np.ones((16, 32, 3), dtype=np.uint8) + with pytest.raises(ValueError): + encode_frame( + frame, + transfer_syntax_uid=JPEGBaseline, + bits_allocated=8, + bits_stored=8, + photometric_interpretation='YBR_FULL_422', + pixel_representation=0, + planar_configuration=1 + ) + + def test_jpeg2000_monochrome_wrong_photometric_interpretation(self): + frame = np.zeros((16, 32), dtype=np.uint16) + with pytest.raises(ValueError): + encode_frame( + frame, + transfer_syntax_uid=JPEG2000Lossless, + bits_allocated=16, + bits_stored=16, + photometric_interpretation='MONOCHROME', + pixel_representation=0, + ) + + def test_jpeg2000_monochrome_wrong_pixel_representation(self): + frame = np.zeros((16, 32), dtype=np.uint16) + with pytest.raises(ValueError): + encode_frame( + frame, + transfer_syntax_uid=JPEG2000Lossless, + bits_allocated=16, + bits_stored=16, + photometric_interpretation='MONOCHROME2', + pixel_representation=1, + ) diff --git a/tests/test_sc.py b/tests/test_sc.py index cd6a065e..e416f117 100644 --- a/tests/test_sc.py +++ b/tests/test_sc.py @@ -6,8 +6,8 @@ from pydicom import dcmread from pydicom.uid import generate_uid, RLELossless -from highdicom.content import SpecimenDescription -from highdicom.sc.sop import SCImage +from highdicom import SpecimenDescription +from highdicom.sc import SCImage class TestSCImage(unittest.TestCase): diff --git a/tests/test_seg.py b/tests/test_seg.py index 428ac8ec..41542422 100644 --- a/tests/test_seg.py +++ b/tests/test_seg.py @@ -15,23 +15,23 @@ RLELossless, ) -from highdicom.content import ( +from highdicom import ( AlgorithmIdentificationSequence, PlanePositionSequence, PixelMeasuresSequence, PlaneOrientationSequence, ) from highdicom.enum import CoordinateSystemNames -from highdicom.seg.content import ( +from highdicom.seg import ( DimensionIndexSequence, SegmentDescription, ) -from highdicom.seg.enum import ( +from highdicom.seg import ( SegmentAlgorithmTypeValues, SegmentsOverlapValues, SegmentationTypeValues, ) -from highdicom.seg.sop import Segmentation +from highdicom.seg import Segmentation from highdicom.seg.utils import iter_segments diff --git a/tests/test_sr.py b/tests/test_sr.py index c600078f..549b7702 100644 --- a/tests/test_sr.py +++ b/tests/test_sr.py @@ -14,8 +14,8 @@ from pydicom.uid import generate_uid, UID from pydicom.valuerep import DA, DS, DT, TM -from highdicom.sr.coding import CodedConcept -from highdicom.sr.content import ( +from highdicom.sr import CodedConcept +from highdicom.sr import ( FindingSite, ImageRegion, ImageRegion3D, @@ -29,14 +29,14 @@ SourceSeriesForSegmentation, VolumeSurface, ) -from highdicom.sr.enum import ( +from highdicom.sr import ( GraphicTypeValues, GraphicTypeValues3D, RelationshipTypeValues, ValueTypeValues, ) from highdicom.sr.utils import find_content_items -from highdicom.sr.value_types import ( +from highdicom.sr import ( CodeContentItem, ContainerContentItem, CompositeContentItem, @@ -50,12 +50,12 @@ TimeContentItem, UIDRefContentItem, ) -from highdicom.sr.sop import ( +from highdicom.sr import ( ComprehensiveSR, Comprehensive3DSR, EnhancedSR, ) -from highdicom.sr.templates import ( +from highdicom.sr import ( AlgorithmIdentification, DeviceObserverIdentifyingAttributes, Measurement, diff --git a/tests/test_utils.py b/tests/test_utils.py index 01921c47..e65221e5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,6 @@ import pytest -from highdicom.content import PlanePositionSequence +from highdicom import PlanePositionSequence from highdicom.enum import CoordinateSystemNames from highdicom.utils import compute_plane_position_tiled_full