From 4ee9a4635400ececb9ddcdd280a97990dd04955a Mon Sep 17 00:00:00 2001 From: Chris Bridge Date: Mon, 17 Feb 2020 21:55:06 -0500 Subject: [PATCH 1/7] Added further read/write tests and segments overlap --- src/highdicom/seg/enum.py | 9 ++++ src/highdicom/seg/sop.py | 12 +++++ tests/test_seg.py | 103 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/src/highdicom/seg/enum.py b/src/highdicom/seg/enum.py index 8ae7e5b6..9d4bac9d 100644 --- a/src/highdicom/seg/enum.py +++ b/src/highdicom/seg/enum.py @@ -34,3 +34,12 @@ class SpatialLocationsPreserved(Enum): YES = 'YES' NO = 'NO' REORIENTED_ONLY = 'REORIENTED_ONLY' + + +class SegmentsOverlap(Enum): + + """Enumerated values for attribute Segments Overlap Attribute.""" + + YES = 'YES' + UNDEFINED = 'UNDEFINED' + NO = 'NO' diff --git a/src/highdicom/seg/sop.py b/src/highdicom/seg/sop.py index 7018573d..308047ac 100644 --- a/src/highdicom/seg/sop.py +++ b/src/highdicom/seg/sop.py @@ -31,6 +31,7 @@ from highdicom.seg.enum import ( SegmentationFractionalTypes, SegmentationTypes, + SegmentsOverlap, ) from highdicom.sr.coding import CodedConcept from highdicom.utils import compute_plane_positions_tiled_full @@ -554,6 +555,17 @@ def add_segments( if len(set(described_segment_numbers) & self._segment_inventory) > 0: raise ValueError('Segment with given segment number already exists') + # Set the optional tag value SegmentsOverlap to NO to indicate that the + # segments do not overlap. We can know this for sure if it's the first + # segment (or set of segments) to be added because they are contained + # within a single pixel array. + if len(self._segment_inventory) == 0: + self.SegmentsOverlap = SegmentsOverlap.NO.value + else: + # If this is not the first set of segments to be added, we cannot + # be sure whether there is overlap with the existing segments + self.SegmentsOverlap = SegmentsOverlap.UNDEFINED.value + src_img = self._source_images[0] is_multiframe = hasattr(src_img, 'NumberOfFrames') if self._coordinate_system == CoordinateSystemNames.SLIDE: diff --git a/tests/test_seg.py b/tests/test_seg.py index 0be5ff51..318ebea4 100644 --- a/tests/test_seg.py +++ b/tests/test_seg.py @@ -25,10 +25,20 @@ SegmentDescription, Surface, ) -from highdicom.seg.enum import SegmentAlgorithmTypes, SegmentationTypes +from highdicom.seg.enum import SegmentAlgorithmTypes, SegmentsOverlap, SegmentationTypes from highdicom.seg.sop import Segmentation, SurfaceSegmentation +def interleave_frames(a1, a2): + # Interleave the frames (down the first dimension) coming from two arrays + assert a1.shape == a2.shape + out_shape = (a1.shape[0] + a2.shape[0], ) + a1.shape[1:] + out = np.empty(out_shape) + out[::2] = a1 + out[1::2] = a2 + return out + + class TestAlgorithmIdentificationSequence(unittest.TestCase): def setUp(self): @@ -708,6 +718,7 @@ def test_construction(self): assert len(frame_content_item.DimensionIndexValues) == 2 for derivation_image_item in frame_item.DerivationImageSequence: assert len(derivation_image_item.SourceImageSequence) == 1 + assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.NO with pytest.raises(AttributeError): frame_item.PlanePositionSlideSequence @@ -763,6 +774,7 @@ def test_construction_2(self): assert len(derivation_image_item.SourceImageSequence) == 1 source_image_item = derivation_image_item.SourceImageSequence[0] assert hasattr(source_image_item, 'ReferencedFrameNumber') + assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.NO with pytest.raises(AttributeError): frame_item.PlanePositionSequence @@ -830,6 +842,7 @@ def test_construction_3(self): for dcm in self._ct_series } assert source_uid_to_plane_position == uid_to_plane_position + assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.NO with pytest.raises(AttributeError): frame_item.PlanePositionSlideSequence @@ -905,6 +918,7 @@ def test_construction_4(self): assert source_image_item.ReferencedFrameNumber == i + 1 assert source_image_item.ReferencedSOPInstanceUID == \ self._ct_multiframe.SOPInstanceUID + assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.NO with pytest.raises(AttributeError): frame_item.PlanePositionSlideSequence @@ -945,10 +959,26 @@ def test_pixel_types(self): assert (instance_reread.pixel_array == mask).all() # Add another segment + additional_mask = (1 - mask) instance.add_segments( - mask.astype(pix_type), + additional_mask.astype(pix_type), self._additional_segment_descriptions ) + assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.UNDEFINED + + # Write to buffer and read in again + with BytesIO() as fp: + instance.save_as(fp) + fp.seek(0) + instance_reread = dcmread(fp) + + # Concatenate to create the expected encoding + if mask.ndim == 2: + expected_encoding = np.stack([mask, additional_mask]) + else: + expected_encoding = np.concatenate([mask, additional_mask], axis=0) + + assert np.array_equal(instance_reread.pixel_array, expected_encoding) for source, mask in tests: for pix_type in [np.bool, np.uint8, np.uint16, np.float]: @@ -978,10 +1008,26 @@ def test_pixel_types(self): assert (instance_reread.pixel_array == mask).all() # Add another segment + additional_mask = (1 - mask) instance.add_segments( - mask.astype(pix_type), + additional_mask.astype(pix_type), self._additional_segment_descriptions ) + assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.UNDEFINED + + # Write to buffer and read in again + with BytesIO() as fp: + instance.save_as(fp) + fp.seek(0) + instance_reread = dcmread(fp) + + # Concatenate to create the expected encoding + if mask.ndim == 2: + expected_encoding = np.stack([mask, additional_mask]) + else: + expected_encoding = np.concatenate([mask, additional_mask], axis=0) + + assert np.array_equal(instance_reread.pixel_array, expected_encoding) def test_multi_segments(self): # Test that the multi-segment encoding is behaving as expected @@ -1035,6 +1081,57 @@ def test_multi_segments(self): # Ensure the recovered pixel array matches what is expected assert (instance_reread.pixel_array == expected_encoding).all() + def test_construction_wrong_segment_order(self): + with pytest.raises(ValueError): + Segmentation( + source_images=[self._ct_image], + pixel_array=self._ct_pixel_array, + segmentation_type=SegmentationTypes.FRACTIONAL.value, + segment_descriptions=self._additional_segment_descriptions + self._segment_descriptions, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + manufacturer=self._manufacturer, + manufacturer_model_name=self._manufacturer_model_name, + software_versions=self._software_versions, + device_serial_number=self._device_serial_number + ) + + def test_construction_duplicate_segment_number(self): + with pytest.raises(ValueError): + Segmentation( + source_images=[self._ct_image], + pixel_array=self._ct_pixel_array, + segmentation_type=SegmentationTypes.FRACTIONAL.value, + segment_descriptions=self._segment_descriptions + self._segment_descriptions, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + manufacturer=self._manufacturer, + manufacturer_model_name=self._manufacturer_model_name, + software_versions=self._software_versions, + device_serial_number=self._device_serial_number + ) + + def test_construction_non_described_segment(self): + with pytest.raises(ValueError): + Segmentation( + source_images=[self._ct_image], + pixel_array=self._ct_pixel_array * 3, + segmentation_type=SegmentationTypes.FRACTIONAL.value, + segment_descriptions=self._segment_descriptions + self._segment_descriptions, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + manufacturer=self._manufacturer, + manufacturer_model_name=self._manufacturer_model_name, + software_versions=self._software_versions, + device_serial_number=self._device_serial_number + ) + def test_construction_missing_required_attribute(self): with pytest.raises(TypeError): Segmentation( From eb3158512bfe371810e737db4cfe8b4f1099ca46 Mon Sep 17 00:00:00 2001 From: Christopher Bridge Date: Fri, 21 Feb 2020 18:11:29 -0500 Subject: [PATCH 2/7] Mostly fixed tests --- tests/test_seg.py | 58 ++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/tests/test_seg.py b/tests/test_seg.py index 9b8eea8e..3e81cbfb 100644 --- a/tests/test_seg.py +++ b/tests/test_seg.py @@ -619,6 +619,7 @@ def setUp(self): dtype=np.bool ) self._sm_pixel_array[2:3, 1:5, 7:9] = True + self._sm_pixel_array[6:9, 2:8, 1:4] = True # A series of single frame CT images ct_series = [ @@ -764,8 +765,12 @@ def test_construction_2(self): self._sm_image.ImageOrientationSlide assert len(instance.DimensionOrganizationSequence) == 1 assert len(instance.DimensionIndexSequence) == 6 - assert instance.NumberOfFrames == 1 # sparse! - assert len(instance.PerFrameFunctionalGroupsSequence) == 1 + + # Number of frames should be number of frames in the segmentation mask + # that are non-empty, due to sparsity + num_frames = (self._sm_pixel_array.sum(axis=(1, 2)) > 0).sum() + assert instance.NumberOfFrames == num_frames + assert len(instance.PerFrameFunctionalGroupsSequence) == num_frames frame_item = instance.PerFrameFunctionalGroupsSequence[0] assert len(frame_item.SegmentIdentificationSequence) == 1 assert len(frame_item.DerivationImageSequence) == 1 @@ -926,6 +931,7 @@ def test_construction_4(self): frame_item.PlanePositionSlideSequence def test_pixel_types(self): + # A series of tests on different types of image tests = [ ([self._ct_image], self._ct_pixel_array), ([self._sm_image], self._sm_pixel_array), @@ -934,13 +940,26 @@ def test_pixel_types(self): ] for source, mask in tests: + + # Create a mask for an additional segment as the complement of the original mask + additional_mask = (1 - mask) + + # Find the expected encodings for the masks if mask.ndim > 2: expected_encoding = np.stack([ frame for frame in mask if np.sum(frame) > 0 ]) + expected_additional_encoding = np.stack([ + frame for frame in additional_mask if np.sum(frame) > 0 + ]) + two_segment_expected_encoding = np.concatenate([expected_encoding, expected_additional_encoding], axis=0) expected_encoding = expected_encoding.squeeze() + expected_additional_encoding = expected_additional_encoding.squeeze() else: expected_encoding = mask + two_segment_expected_encoding = np.stack([mask, additional_mask]) + + # Test instance creation for different pixel types for pix_type in [np.bool, np.uint8, np.uint16, np.float]: instance = Segmentation( source, @@ -971,7 +990,6 @@ def test_pixel_types(self): ) # Add another segment - additional_mask = (1 - mask) instance.add_segments( additional_mask.astype(pix_type), self._additional_segment_descriptions @@ -984,27 +1002,27 @@ def test_pixel_types(self): fp.seek(0) instance_reread = dcmread(fp) - # Concatenate to create the expected encoding - if mask.ndim == 2: - expected_encoding = np.stack([mask, additional_mask]) - else: - expected_encoding = np.concatenate([mask, additional_mask], axis=0) - - assert np.array_equal(instance_reread.pixel_array, expected_encoding) + # Ensure the recovered pixel array matches what is expected + assert np.array_equal(instance_reread.pixel_array, two_segment_expected_encoding) for source, mask in tests: + additional_mask = (1 - mask) if mask.ndim > 2: expected_encoding = np.stack([ frame for frame in mask if np.sum(frame) > 0 ]) - if len(np.unique(mask)) > 2: - expected_encoding = np.stack([ - frame == i for i in np.arange(1, len(np.unique(mask))) - for frame in expected_encoding - ]) + expected_additional_encoding = np.stack([ + frame for frame in additional_mask if np.sum(frame) > 0 + ]) + two_segment_expected_encoding = np.concatenate([ + expected_encoding, expected_additional_encoding + ] , axis=0) expected_encoding = expected_encoding.squeeze() + expected_additional_encoding = expected_additional_encoding.squeeze() else: expected_encoding = (mask > 0).astype(mask.dtype) + expected_additional_encoding = (additional_mask > 0).astype(mask.dtype) + two_segment_expected_encoding = np.stack([expected_encoding, expected_additional_encoding]) for pix_type in [np.bool, np.uint8, np.uint16, np.float]: instance = Segmentation( source, @@ -1035,7 +1053,6 @@ def test_pixel_types(self): ) # Add another segment - additional_mask = (1 - mask) instance.add_segments( additional_mask.astype(pix_type), self._additional_segment_descriptions @@ -1048,13 +1065,8 @@ def test_pixel_types(self): fp.seek(0) instance_reread = dcmread(fp) - # Concatenate to create the expected encoding - if mask.ndim == 2: - expected_encoding = np.stack([mask, additional_mask]) - else: - expected_encoding = np.concatenate([mask, additional_mask], axis=0) - - assert np.array_equal(instance_reread.pixel_array, expected_encoding) + # Ensure the recovered pixel array matches what is expected + assert np.array_equal(instance_reread.pixel_array, two_segment_expected_encoding) def test_multi_segments(self): # Test that the multi-segment encoding is behaving as expected From 67609d007a3b797ebfc5c618bf580a123af3df00 Mon Sep 17 00:00:00 2001 From: Christopher Bridge Date: Fri, 21 Feb 2020 21:02:47 -0500 Subject: [PATCH 3/7] Tests passing --- docs/usage.rst | 2 ++ tests/test_seg.py | 88 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index dd56696f..1a3bff32 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -79,6 +79,7 @@ Derive a Segmentation image from a series of single-frame Computed Tomography sop_instance_uid=generate_uid(), instance_number=1, manufacturer='Manufacturer', + manufacturer_model_name='Model', software_versions='v1', device_serial_number='Device XYZ', manufacturer_model_name='The best one' @@ -147,6 +148,7 @@ Derive a Segmentation image from a multi-frame Slide Microscopy (SM) image: sop_instance_uid=generate_uid(), instance_number=1, manufacturer='Manufacturer', + manufacturer_model_name='Model', software_versions='v1', device_serial_number='Device XYZ' ) diff --git a/tests/test_seg.py b/tests/test_seg.py index 3e81cbfb..9df9ee4a 100644 --- a/tests/test_seg.py +++ b/tests/test_seg.py @@ -614,6 +614,9 @@ def setUp(self): self._sm_image = dcmread( os.path.join(data_dir, 'test_files', 'sm_image.dcm') ) + # Override te existing ImageOrientationSlide to make the frame ordering simpler + # for the tests + self._sm_image.ImageOrientationSlide = [0.0, 1.0, 0.0, 1.0, 0.0, 0.0] self._sm_pixel_array = np.zeros( self._sm_image.pixel_array.shape[:3], # remove colour channel axis dtype=np.bool @@ -645,6 +648,13 @@ def setUp(self): ) self._ct_multiframe_mask_array[:, 100:200, 200:400] = True + @staticmethod + def remove_empty_frames(mask): + # Remove empty frames from an array + return np.stack([ + frame for frame in mask if np.sum(frame) > 0 + ]) + def test_construction(self): instance = Segmentation( [self._ct_image], @@ -946,12 +956,8 @@ def test_pixel_types(self): # Find the expected encodings for the masks if mask.ndim > 2: - expected_encoding = np.stack([ - frame for frame in mask if np.sum(frame) > 0 - ]) - expected_additional_encoding = np.stack([ - frame for frame in additional_mask if np.sum(frame) > 0 - ]) + expected_encoding = self.remove_empty_frames(mask) + expected_additional_encoding = self.remove_empty_frames(additional_mask) two_segment_expected_encoding = np.concatenate([expected_encoding, expected_additional_encoding], axis=0) expected_encoding = expected_encoding.squeeze() expected_additional_encoding = expected_additional_encoding.squeeze() @@ -1008,15 +1014,11 @@ def test_pixel_types(self): for source, mask in tests: additional_mask = (1 - mask) if mask.ndim > 2: - expected_encoding = np.stack([ - frame for frame in mask if np.sum(frame) > 0 - ]) - expected_additional_encoding = np.stack([ - frame for frame in additional_mask if np.sum(frame) > 0 - ]) + expected_encoding = self.remove_empty_frames(mask) + expected_additional_encoding = self.remove_empty_frames(additional_mask) two_segment_expected_encoding = np.concatenate([ expected_encoding, expected_additional_encoding - ] , axis=0) + ], axis=0) expected_encoding = expected_encoding.squeeze() expected_additional_encoding = expected_additional_encoding.squeeze() else: @@ -1068,6 +1070,66 @@ def test_pixel_types(self): # Ensure the recovered pixel array matches what is expected assert np.array_equal(instance_reread.pixel_array, two_segment_expected_encoding) + def test_odd_number_pixels(self): + # Test that an image with an odd number of pixels per frame is encoded properly + # Including when additional segments are subsequently added + + # Create an instance with an odd number of pixels in each frame + # Based on the single frame CT image + odd_instance = self._ct_image + r = 9 + c = 9 + odd_pixels = np.random.randint(256, size=(r, c), dtype=np.uint16) + + odd_instance.PixelData = odd_pixels.flatten().tobytes() + odd_instance.Rows = r + odd_instance.Columns = c + + odd_mask = np.random.randint(2, size=odd_pixels.shape, dtype=np.bool) + addtional_odd_mask = np.random.randint(2, size=odd_pixels.shape, dtype=np.bool) + + instance = Segmentation( + [odd_instance], + odd_mask, + SegmentationTypes.BINARY.value, + segment_descriptions=self._segment_descriptions, + series_instance_uid=self._series_instance_uid, + series_number=self._series_number, + sop_instance_uid=self._sop_instance_uid, + instance_number=self._instance_number, + manufacturer=self._manufacturer, + manufacturer_model_name=self._manufacturer_model_name, + software_versions=self._software_versions, + device_serial_number=self._device_serial_number + ) + + # Write to buffer and read in again + print(len(instance.PixelData)) + with BytesIO() as fp: + instance.save_as(fp) + fp.seek(0) + instance_reread = dcmread(fp) + print(len(instance_reread.PixelData)) + + assert np.array_equal(instance_reread.pixel_array, odd_mask) + + instance.add_segments( + addtional_odd_mask, + self._additional_segment_descriptions + ) + + # Write to buffer and read in again + print(len(instance.PixelData)) + with BytesIO() as fp: + instance.save_as(fp) + fp.seek(0) + instance_reread = dcmread(fp) + print(len(instance_reread.PixelData)) + + expected_two_segment_mask = np.stack([odd_mask, addtional_odd_mask], axis=0) + assert np.array_equal(instance_reread.pixel_array, expected_two_segment_mask) + + def test_multi_segments(self): # Test that the multi-segment encoding is behaving as expected From 07e303c8ba2a0243eae6877a498660a008542194 Mon Sep 17 00:00:00 2001 From: Christopher Bridge Date: Sat, 22 Feb 2020 08:41:47 -0500 Subject: [PATCH 4/7] Tests passing without warnings --- src/highdicom/seg/sop.py | 8 ++++---- tests/test_seg.py | 16 +++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/highdicom/seg/sop.py b/src/highdicom/seg/sop.py index 9b87d593..865691fd 100644 --- a/src/highdicom/seg/sop.py +++ b/src/highdicom/seg/sop.py @@ -712,10 +712,6 @@ def add_segments( for index in range(plane_position_values.shape[1]) ] - # Before adding new pixel data, remove the trailing null padding byte - if len(self.PixelData) == get_expected_length(self) + 1: - self.PixelData = self.PixelData[:-1] - # When using binary segmentation type, the previous frames may have been # padded to be a multiple of 8. In this case, we need to decode the # pixel data, add the new pixels and then re-encode. This process @@ -736,6 +732,10 @@ def add_segments( else: re_encode_pixel_data = False + # Before adding new pixel data, remove the trailing null padding byte + if len(self.PixelData) == get_expected_length(self) + 1: + self.PixelData = self.PixelData[:-1] + for i, segment_number in enumerate(described_segment_numbers): if pixel_array.dtype == np.float: # Floating-point numbers must be mapped to 8-bit integers in diff --git a/tests/test_seg.py b/tests/test_seg.py index 9df9ee4a..dcb3ae87 100644 --- a/tests/test_seg.py +++ b/tests/test_seg.py @@ -655,6 +655,16 @@ def remove_empty_frames(mask): frame for frame in mask if np.sum(frame) > 0 ]) + @staticmethod + def get_array_after_writing(instance): + # Write a DICOM object to a buffer, read it again and reconstruct the mask + with BytesIO() as fp: + instance.save_as(fp) + fp.seek(0) + instance_reread = dcmread(fp) + + return instance_reread + def test_construction(self): instance = Segmentation( [self._ct_image], @@ -987,7 +997,7 @@ def test_pixel_types(self): with BytesIO() as fp: instance.save_as(fp) fp.seek(0) - instance_reread = dcmread(fp) + instance_reread = self.get_array_after_writing(instance) # Ensure the recovered pixel array matches what is expected assert np.array_equal( @@ -1104,12 +1114,10 @@ def test_odd_number_pixels(self): ) # Write to buffer and read in again - print(len(instance.PixelData)) with BytesIO() as fp: instance.save_as(fp) fp.seek(0) instance_reread = dcmread(fp) - print(len(instance_reread.PixelData)) assert np.array_equal(instance_reread.pixel_array, odd_mask) @@ -1119,12 +1127,10 @@ def test_odd_number_pixels(self): ) # Write to buffer and read in again - print(len(instance.PixelData)) with BytesIO() as fp: instance.save_as(fp) fp.seek(0) instance_reread = dcmread(fp) - print(len(instance_reread.PixelData)) expected_two_segment_mask = np.stack([odd_mask, addtional_odd_mask], axis=0) assert np.array_equal(instance_reread.pixel_array, expected_two_segment_mask) From a344c7dcad18a96fb4734eda0119276595353ab4 Mon Sep 17 00:00:00 2001 From: Christopher Bridge Date: Sat, 22 Feb 2020 08:48:02 -0500 Subject: [PATCH 5/7] Refactored tests to improve conciseness --- tests/test_seg.py | 66 +++++++++++------------------------------------ 1 file changed, 15 insertions(+), 51 deletions(-) diff --git a/tests/test_seg.py b/tests/test_seg.py index dcb3ae87..9bb117a7 100644 --- a/tests/test_seg.py +++ b/tests/test_seg.py @@ -663,7 +663,7 @@ def get_array_after_writing(instance): fp.seek(0) instance_reread = dcmread(fp) - return instance_reread + return instance_reread.pixel_array def test_construction(self): instance = Segmentation( @@ -993,15 +993,9 @@ def test_pixel_types(self): max_fractional_value=1 ) - # Write to buffer and read in again - with BytesIO() as fp: - instance.save_as(fp) - fp.seek(0) - instance_reread = self.get_array_after_writing(instance) - # Ensure the recovered pixel array matches what is expected assert np.array_equal( - instance_reread.pixel_array, + self.get_array_after_writing(instance), expected_encoding ) @@ -1012,14 +1006,9 @@ def test_pixel_types(self): ) assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.UNDEFINED - # Write to buffer and read in again - with BytesIO() as fp: - instance.save_as(fp) - fp.seek(0) - instance_reread = dcmread(fp) - # Ensure the recovered pixel array matches what is expected - assert np.array_equal(instance_reread.pixel_array, two_segment_expected_encoding) + assert np.array_equal(self.get_array_after_writing(instance), + two_segment_expected_encoding) for source, mask in tests: additional_mask = (1 - mask) @@ -1052,15 +1041,9 @@ def test_pixel_types(self): max_fractional_value=1 ) - # Write to buffer and read in again - with BytesIO() as fp: - instance.save_as(fp) - fp.seek(0) - instance_reread = dcmread(fp) - # Ensure the recovered pixel array matches what is expected assert np.array_equal( - instance_reread.pixel_array, + self.get_array_after_writing(instance), expected_encoding ) @@ -1071,14 +1054,11 @@ def test_pixel_types(self): ) assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.UNDEFINED - # Write to buffer and read in again - with BytesIO() as fp: - instance.save_as(fp) - fp.seek(0) - instance_reread = dcmread(fp) - # Ensure the recovered pixel array matches what is expected - assert np.array_equal(instance_reread.pixel_array, two_segment_expected_encoding) + assert np.array_equal( + self.get_array_after_writing(instance), + two_segment_expected_encoding + ) def test_odd_number_pixels(self): # Test that an image with an odd number of pixels per frame is encoded properly @@ -1113,28 +1093,18 @@ def test_odd_number_pixels(self): device_serial_number=self._device_serial_number ) - # Write to buffer and read in again - with BytesIO() as fp: - instance.save_as(fp) - fp.seek(0) - instance_reread = dcmread(fp) - - assert np.array_equal(instance_reread.pixel_array, odd_mask) + assert np.array_equal(self.get_array_after_writing(instance), odd_mask) instance.add_segments( addtional_odd_mask, self._additional_segment_descriptions ) - # Write to buffer and read in again - with BytesIO() as fp: - instance.save_as(fp) - fp.seek(0) - instance_reread = dcmread(fp) - expected_two_segment_mask = np.stack([odd_mask, addtional_odd_mask], axis=0) - assert np.array_equal(instance_reread.pixel_array, expected_two_segment_mask) - + assert np.array_equal( + self.get_array_after_writing(instance), + expected_two_segment_mask + ) def test_multi_segments(self): # Test that the multi-segment encoding is behaving as expected @@ -1184,15 +1154,9 @@ def test_multi_segments(self): max_fractional_value=1 ) - # Write to buffer and read in again - with BytesIO() as fp: - instance.save_as(fp) - fp.seek(0) - instance_reread = dcmread(fp) - # Ensure the recovered pixel array matches what is expected assert np.array_equal( - instance_reread.pixel_array, + self.get_array_after_writing(instance), expected_encoding ) From db8e3b5fc987562fe58af836b42ef972c3e8b1ed Mon Sep 17 00:00:00 2001 From: Christopher Bridge Date: Sat, 22 Feb 2020 09:17:31 -0500 Subject: [PATCH 6/7] Removed redundant function in tests --- tests/test_seg.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/test_seg.py b/tests/test_seg.py index 79eec423..0ba266f0 100644 --- a/tests/test_seg.py +++ b/tests/test_seg.py @@ -29,16 +29,6 @@ from highdicom.seg.sop import Segmentation, SurfaceSegmentation -def interleave_frames(a1, a2): - # Interleave the frames (down the first dimension) coming from two arrays - assert a1.shape == a2.shape - out_shape = (a1.shape[0] + a2.shape[0], ) + a1.shape[1:] - out = np.empty(out_shape) - out[::2] = a1 - out[1::2] = a2 - return out - - class TestAlgorithmIdentificationSequence(unittest.TestCase): def setUp(self): From 3cb3d6b0465dc7f70b197336d47b551b29ec2dc7 Mon Sep 17 00:00:00 2001 From: hackermd Date: Wed, 26 Feb 2020 17:34:56 -0500 Subject: [PATCH 7/7] Rename enumerated values --- src/highdicom/base.py | 6 +- src/highdicom/content.py | 9 +- src/highdicom/enum.py | 16 +-- src/highdicom/sc/enum.py | 2 +- src/highdicom/sc/sop.py | 73 ++++++----- src/highdicom/seg/content.py | 9 +- src/highdicom/seg/enum.py | 12 +- src/highdicom/seg/sop.py | 48 ++++--- src/highdicom/sr/content.py | 78 ++++++------ src/highdicom/sr/enum.py | 12 +- src/highdicom/sr/templates.py | 114 ++++++++--------- src/highdicom/sr/value_types.py | 213 +++++++++++++++++++------------- tests/test_seg.py | 169 ++++++++++++++++--------- tests/test_sr.py | 22 ++-- 14 files changed, 440 insertions(+), 343 deletions(-) diff --git a/src/highdicom/base.py b/src/highdicom/base.py index 6d395c4c..b8a6f8a9 100644 --- a/src/highdicom/base.py +++ b/src/highdicom/base.py @@ -16,7 +16,7 @@ from pydicom.valuerep import DA, DT, TM from highdicom.sr.coding import CodingSchemeIdentificationItem -from highdicom.enum import ContentQualifications +from highdicom.enum import ContentQualificationValues from highdicom.version import __version__ from highdicom._iods import IOD_MODULE_MAP from highdicom._modules import MODULE_ATTRIBUTE_MAP @@ -174,7 +174,9 @@ def __init__( self.ContentDate = DA(datetime.now().date()) self.ContentTime = TM(datetime.now().time()) if content_qualification is not None: - content_qualification = ContentQualifications(content_qualification) + content_qualification = ContentQualificationValues( + content_qualification + ) self.ContentQualification = content_qualification.value if coding_schemes is not None: self.CodingSchemeIdentificationSequence = [] diff --git a/src/highdicom/content.py b/src/highdicom/content.py index 503bb085..fd4c1d07 100644 --- a/src/highdicom/content.py +++ b/src/highdicom/content.py @@ -10,9 +10,8 @@ from highdicom.enum import ( CoordinateSystemNames, - UniversalEntityIDTypes, + UniversalEntityIDTypeValues, ) -from highdicom.seg.enum import SegmentAlgorithmTypes from highdicom.sr.coding import CodedConcept from highdicom.sr.value_types import ( CodeContentItem, @@ -277,7 +276,7 @@ def __init__( self, issuer_of_identifier: str, issuer_of_identifier_type: Optional[ - Union[str, UniversalEntityIDTypes] + Union[str, UniversalEntityIDTypeValues] ] = None ): """ @@ -285,7 +284,7 @@ def __init__( ---------- issuer_of_identifier: str Identifier of the entity that created the examined specimen - issuer_of_identifier_type: Union[str, highdicom.enum.UniversalEntityIDTypes], optional + issuer_of_identifier_type: Union[str, highdicom.enum.UniversalEntityIDTypeValues], optional Type of identifier of the entity that created the examined specimen (required if `issuer_of_specimen_id` is a Unique Entity ID) @@ -295,7 +294,7 @@ def __init__( self.LocalNamespaceEntityID = issuer_of_identifier else: self.UniversalEntityID = issuer_of_identifier - issuer_of_identifier_type = UniversalEntityIDTypes( + issuer_of_identifier_type = UniversalEntityIDTypeValues( issuer_of_identifier_type ) self.UniversalEntityIDType = issuer_of_identifier_type.value diff --git a/src/highdicom/enum.py b/src/highdicom/enum.py index e3ad1baa..9f3f152f 100644 --- a/src/highdicom/enum.py +++ b/src/highdicom/enum.py @@ -10,7 +10,7 @@ class CoordinateSystemNames(Enum): SLIDE = 'SLIDE' -class ContentQualifications(Enum): +class ContentQualificationValues(Enum): """Enumerated values for Content Qualification attribute.""" @@ -19,7 +19,7 @@ class ContentQualifications(Enum): SERVICE = 'SERVICE' -class DimensionOrganizationTypes(Enum): +class DimensionOrganizationTypeValues(Enum): """Enumerated values for Dimension Organization Type attribute.""" @@ -29,7 +29,7 @@ class DimensionOrganizationTypes(Enum): TILED_SPARSE = 'TILED_SPARSE' -class PhotometricInterpretations(Enum): +class PhotometricInterpretationValues(Enum): """Enumerated values for Photometric Interpretation attribute.""" @@ -44,7 +44,7 @@ class PhotometricInterpretations(Enum): YBR_RCT = 'YBR_RCT' -class Lateralities(Enum): +class LateralityValues(Enum): """Enumerated values for Laterality attribute.""" @@ -52,7 +52,7 @@ class Lateralities(Enum): L = 'L' -class AnatomicalOrientationTypes(Enum): +class AnatomicalOrientationTypeValues(Enum): """Enumerated values for Anatomical Orientation Type attribute.""" @@ -60,7 +60,7 @@ class AnatomicalOrientationTypes(Enum): QUADRUPED = 'QUADRUPED' -class PatientOrientationsBiped(Enum): +class PatientOrientationValuesBiped(Enum): """Enumerated values for Patient Orientation attribute if Anatomical Orientation Type attribute has value ``"BIPED"``. @@ -74,7 +74,7 @@ class PatientOrientationsBiped(Enum): F = 'F' -class PatientOrientationsQuadruped(Enum): +class PatientOrientationValuesQuadruped(Enum): """Enumerated values for Patient Orientation attribute if Anatomical Orientation Type attribute has value ``"QUADRUPED"``. @@ -95,7 +95,7 @@ class PatientOrientationsQuadruped(Enum): PL = 'PL' -class UniversalEntityIDTypes(Enum): +class UniversalEntityIDTypeValues(Enum): """Enumerated values for Universal Entity ID Type attribute.""" diff --git a/src/highdicom/sc/enum.py b/src/highdicom/sc/enum.py index 6b084c80..f6875a03 100644 --- a/src/highdicom/sc/enum.py +++ b/src/highdicom/sc/enum.py @@ -2,7 +2,7 @@ from enum import Enum -class ConversionTypes(Enum): +class ConversionTypeValues(Enum): """Enumerated values for attribute Conversion Type.""" diff --git a/src/highdicom/sc/sop.py b/src/highdicom/sc/sop.py index f09da0e0..9d393c2d 100644 --- a/src/highdicom/sc/sop.py +++ b/src/highdicom/sc/sop.py @@ -15,14 +15,14 @@ SpecimenDescription, ) from highdicom.enum import ( - AnatomicalOrientationTypes, + AnatomicalOrientationTypeValues, CoordinateSystemNames, - Lateralities, - PhotometricInterpretations, - PatientOrientationsBiped, - PatientOrientationsQuadruped, + LateralityValues, + PhotometricInterpretationValues, + PatientOrientationValuesBiped, + PatientOrientationValuesQuadruped, ) -from highdicom.sc.enum import ConversionTypes +from highdicom.sc.enum import ConversionTypeValues from highdicom.sr.coding import CodedConcept @@ -38,7 +38,10 @@ class SCImage(SOPClass): def __init__( self, pixel_array: np.ndarray, - photometric_interpretation: Union[str, PhotometricInterpretations], + photometric_interpretation: Union[ + str, + PhotometricInterpretationValues + ], bits_allocated: int, coordinate_system: Union[str, CoordinateSystemNames], study_instance_uid: str, @@ -57,19 +60,22 @@ def __init__( study_time: Optional[Union[str, datetime.time]] = None, referring_physician_name: Optional[str] = None, pixel_spacing: Optional[Tuple[int, int]] = None, - laterality: Optional[Union[str, Lateralities]] = None, + laterality: Optional[Union[str, LateralityValues]] = None, patient_orientation: Optional[ Union[ Tuple[str, str], - Tuple[PatientOrientationsBiped, PatientOrientationsBiped], Tuple[ - PatientOrientationsQuadruped, - PatientOrientationsQuadruped, + PatientOrientationValuesBiped, + PatientOrientationValuesBiped, + ], + Tuple[ + PatientOrientationValuesQuadruped, + PatientOrientationValuesQuadruped, ] ] ] = None, anatomical_orientation_type: Optional[ - Union[str, AnatomicalOrientationTypes] + Union[str, AnatomicalOrientationTypeValues] ] = None, container_identifier: Optional[str] = None, issuer_of_container_identifier: Optional[IssuerOfIdentifier] = None, @@ -86,7 +92,7 @@ def __init__( Array of unsigned integer pixel values representing a single-frame image; either a 2D grayscale image or a 3D color image (RGB color space) - photometric_interpretation: Union[str, highdicom.enum.PhotometricInterpretations] + photometric_interpretation: Union[str, highdicom.enum.PhotometricInterpretationValues] Interpretation of pixel data; either ``"MONOCHROME1"`` or ``"MONOCHROME2"`` for 2D grayscale images or ``"RGB"`` or ``"YBR_FULL"`` for 3D color images @@ -130,14 +136,14 @@ def __init__( pixel_spacing: Tuple[int, int], optional Physical spacing in millimeter between pixels along the row and column dimension - laterality: Union[str, highdicom.enum.Lateralities], optional + laterality: Union[str, highdicom.enum.LateralityValues], optional Laterality of the examined body part (required if `coordinate_system` is ``"PATIENT"``) patient_orientation: - Union[Tuple[str, str], Tuple[highdicom.enum.PatientOrientationsBiped, highdicom.enum.PatientOrientationsBiped], Tuple[highdicom.enum.PatientOrientationsQuadruped, highdicom.enum.PatientOrientationsQuadruped]], optional + Union[Tuple[str, str], Tuple[highdicom.enum.PatientOrientationValuesBiped, highdicom.enum.PatientOrientationValuesBiped], Tuple[highdicom.enum.PatientOrientationValuesQuadruped, highdicom.enum.PatientOrientationValuesQuadruped]], optional Orientation of the patient along the row and column axes of the image (required if `coordinate_system` is ``"PATIENT"``) - anatomical_orientation_type: Union[str, highdicom.enum.AnatomicalOrientationTypes], optional + anatomical_orientation_type: Union[str, highdicom.enum.AnatomicalOrientationTypeValues], optional Type of anatomical orientation of patient relative to image (may be provide if `coordinate_system` is ``"PATIENT"`` and patient is an animal) @@ -190,28 +196,31 @@ def __init__( ) # General Series - laterality = Lateralities(laterality) + laterality = LateralityValues(laterality) self.Laterality = laterality.value # General Image if anatomical_orientation_type is not None: - anatomical_orientation_type = AnatomicalOrientationTypes( + anatomical_orientation_type = AnatomicalOrientationTypeValues( anatomical_orientation_type ) self.AnatomicalOrientationType = \ anatomical_orientation_type.value else: - anatomical_orientation_type = AnatomicalOrientationTypes.BIPED + anatomical_orientation_type = \ + AnatomicalOrientationTypeValues.BIPED - if anatomical_orientation_type == AnatomicalOrientationTypes.BIPED: + row_orientation, col_orientation = patient_orientation + if (anatomical_orientation_type == + AnatomicalOrientationTypeValues.BIPED): patient_orientation = [ - PatientOrientationsBiped(patient_orientation[0]).value, - PatientOrientationsBiped(patient_orientation[1]).value, + PatientOrientationValuesBiped(row_orientation).value, + PatientOrientationValuesBiped(col_orientation).value, ] else: patient_orientation = [ - PatientOrientationsQuadruped(patient_orientation[0]).value, - PatientOrientationsQuadruped(patient_orientation[1]).value, + PatientOrientationValuesQuadruped(row_orientation).value, + PatientOrientationValuesQuadruped(col_orientation).value, ] self.PatientOrientation = patient_orientation @@ -239,7 +248,7 @@ def __init__( self.SpecimenDescriptionSequence = specimen_descriptions # SC Equipment - self.ConversionType = ConversionTypes.DI.value + self.ConversionType = ConversionTypeValues.DI.value # SC Image now = datetime.datetime.now() @@ -263,15 +272,15 @@ def __init__( self.HighBit = self.BitsAllocated - 1 self.BitsStored = self.BitsAllocated self.PixelRepresentation = 0 - photometric_interpretation = PhotometricInterpretations( + photometric_interpretation = PhotometricInterpretationValues( photometric_interpretation ) if pixel_array.ndim == 3: accepted_interpretations = ( - PhotometricInterpretations.RGB.value, - PhotometricInterpretations.YBR_FULL.value, - PhotometricInterpretations.YBR_FULL_422.value, - PhotometricInterpretations.YBR_PARTIAL_420.value, + PhotometricInterpretationValues.RGB.value, + PhotometricInterpretationValues.YBR_FULL.value, + PhotometricInterpretationValues.YBR_FULL_422.value, + PhotometricInterpretationValues.YBR_PARTIAL_420.value, ) if photometric_interpretation.value not in accepted_interpretations: raise ValueError( @@ -293,8 +302,8 @@ def __init__( self.PlanarConfiguration = 0 elif pixel_array.ndim == 2: accepted_interpretations = ( - PhotometricInterpretations.MONOCHROME1.value, - PhotometricInterpretations.MONOCHROME2.value, + PhotometricInterpretationValues.MONOCHROME1.value, + PhotometricInterpretationValues.MONOCHROME2.value, ) if photometric_interpretation.value not in accepted_interpretations: raise ValueError( diff --git a/src/highdicom/seg/content.py b/src/highdicom/seg/content.py index 1f9f328f..d4a719f3 100644 --- a/src/highdicom/seg/content.py +++ b/src/highdicom/seg/content.py @@ -9,7 +9,7 @@ from highdicom.content import AlgorithmIdentificationSequence from highdicom.enum import CoordinateSystemNames -from highdicom.seg.enum import SegmentAlgorithmTypes +from highdicom.seg.enum import SegmentAlgorithmTypeValues from highdicom.sr.coding import CodedConcept @@ -23,7 +23,7 @@ def __init__( segment_label: str, segmented_property_category: Union[Code, CodedConcept], segmented_property_type: Union[Code, CodedConcept], - algorithm_type: Union[SegmentAlgorithmTypes, str], + algorithm_type: Union[SegmentAlgorithmTypeValues, str], algorithm_identification: AlgorithmIdentificationSequence, tracking_uid: Optional[str] = None, tracking_id: Optional[str] = None, @@ -49,7 +49,7 @@ def __init__( Property the segment represents, e.g. ``Code("108369006", "SCT", "Neoplasm")`` (see CID 7151 Segmentation Property Types) - algorithm_type: Union[str, highdicom.seg.enum.SegmentAlgorithmTypes] + algorithm_type: Union[str, highdicom.seg.enum.SegmentAlgorithmTypeValues] Type of algorithm algorithm_identification: AlgorithmIdentificationSequence, optional Information useful for identification of the algorithm, such @@ -87,7 +87,8 @@ def __init__( segmented_property_type.scheme_version ), ] - self.SegmentAlgorithmType = SegmentAlgorithmTypes(algorithm_type).value + algorithm_type = SegmentAlgorithmTypeValues(algorithm_type) + self.SegmentAlgorithmType = algorithm_type.value self.SegmentAlgorithmName = algorithm_identification[0].AlgorithmName self.SegmentationAlgorithmIdentificationSequence = \ algorithm_identification diff --git a/src/highdicom/seg/enum.py b/src/highdicom/seg/enum.py index afbf713d..0d3748ce 100644 --- a/src/highdicom/seg/enum.py +++ b/src/highdicom/seg/enum.py @@ -2,7 +2,7 @@ from enum import Enum -class SegmentAlgorithmTypes(Enum): +class SegmentAlgorithmTypeValues(Enum): """Enumerated values for attribute Segment Algorithm Type.""" @@ -11,7 +11,7 @@ class SegmentAlgorithmTypes(Enum): MANUAL = 'MANUAL' -class SegmentationTypes(Enum): +class SegmentationTypeValues(Enum): """Enumerated values for attribute Segmentation Type.""" @@ -19,7 +19,7 @@ class SegmentationTypes(Enum): FRACTIONAL = 'FRACTIONAL' -class SegmentationFractionalTypes(Enum): +class SegmentationFractionalTypeValues(Enum): """Enumerated values for attribute Segmentation Fractional Type.""" @@ -27,7 +27,7 @@ class SegmentationFractionalTypes(Enum): OCCUPANCY = 'OCCUPANCY' -class SpatialLocationsPreserved(Enum): +class SpatialLocationsPreservedValues(Enum): """Enumerated values for attribute Spatial Locations Preserved.""" @@ -36,9 +36,9 @@ class SpatialLocationsPreserved(Enum): REORIENTED_ONLY = 'REORIENTED_ONLY' -class SegmentsOverlap(Enum): +class SegmentsOverlapValues(Enum): - """Enumerated values for attribute Segments Overlap Attribute.""" + """Enumerated values for attribute Segments Overlap.""" YES = 'YES' UNDEFINED = 'UNDEFINED' diff --git a/src/highdicom/seg/sop.py b/src/highdicom/seg/sop.py index e5597815..1b12eb82 100644 --- a/src/highdicom/seg/sop.py +++ b/src/highdicom/seg/sop.py @@ -22,18 +22,16 @@ PlanePositionSequence, PixelMeasuresSequence ) -from highdicom.enum import ( - CoordinateSystemNames, -) +from highdicom.enum import CoordinateSystemNames from highdicom.seg.content import ( DimensionIndexSequence, SegmentDescription, Surface, ) from highdicom.seg.enum import ( - SegmentationFractionalTypes, - SegmentationTypes, - SegmentsOverlap, + SegmentationFractionalTypeValues, + SegmentationTypeValues, + SegmentsOverlapValues, ) from highdicom.sr.coding import CodedConcept from highdicom.utils import compute_plane_positions_tiled_full @@ -54,7 +52,7 @@ def __init__( self, source_images: Sequence[Dataset], pixel_array: np.ndarray, - segmentation_type: Union[str, SegmentationTypes], + segmentation_type: Union[str, SegmentationTypeValues], segment_descriptions: Sequence[SegmentDescription], series_instance_uid: str, series_number: int, @@ -65,8 +63,8 @@ def __init__( software_versions: Union[str, Tuple[str]], device_serial_number: str, fractional_type: Optional[ - Union[str, SegmentationFractionalTypes] - ] = SegmentationFractionalTypes.PROBABILITY, + Union[str, SegmentationFractionalTypeValues] + ] = SegmentationFractionalTypeValues.PROBABILITY, max_fractional_value: Optional[int] = 255, content_description: Optional[str] = None, content_creator_name: Optional[str] = None, @@ -114,7 +112,7 @@ 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.SegmentationTypes] + segmentation_type: Union[str, highdicom.seg.enum.SegmentationTypeValues] Type of segmentation, either ``"BINARY"`` or ``"FRACTIONAL"`` segment_descriptions: Sequence[highdicom.seg.content.SegmentDescription] Description of each segment encoded in `pixel_array`. In the case of @@ -136,7 +134,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.SegmentationFractionalTypes], optional + fractional_type: Union[str, highdicom.seg.enum.SegmentationFractionalTypeValues], optional Type of fractional segmentation that indicates how pixel data should be interpreted max_fractional_value: int, optional @@ -312,15 +310,15 @@ def __init__( self.ContentDescription = content_description self.ContentCreatorName = content_creator_name - segmentation_type = SegmentationTypes(segmentation_type) + segmentation_type = SegmentationTypeValues(segmentation_type) self.SegmentationType = segmentation_type.value - if self.SegmentationType == SegmentationTypes.BINARY.value: + if self.SegmentationType == SegmentationTypeValues.BINARY.value: self.BitsAllocated = 1 self.HighBit = 0 - elif self.SegmentationType == SegmentationTypes.FRACTIONAL.value: + elif self.SegmentationType == SegmentationTypeValues.FRACTIONAL.value: self.BitsAllocated = 8 self.HighBit = 7 - segmentation_fractional_type = SegmentationFractionalTypes( + segmentation_fractional_type = SegmentationFractionalTypeValues( fractional_type ) self.SegmentationFractionalType = segmentation_fractional_type.value @@ -543,7 +541,7 @@ def add_segments( 'When providing a float-valued pixel array, provide only ' 'a single segment description' ) - if self.SegmentationType == SegmentationTypes.BINARY.value: + if self.SegmentationType == SegmentationTypeValues.BINARY.value: non_boolean_values = np.logical_and( unique_values > 0.0, unique_values < 1.0 @@ -561,16 +559,16 @@ def add_segments( if len(set(described_segment_numbers) & self._segment_inventory) > 0: raise ValueError('Segment with given segment number already exists') - # Set the optional tag value SegmentsOverlap to NO to indicate that the - # segments do not overlap. We can know this for sure if it's the first - # segment (or set of segments) to be added because they are contained - # within a single pixel array. + # Set the optional tag value SegmentsOverlapValues to NO to indicate + # that the segments do not overlap. We can know this for sure if it's + # the first segment (or set of segments) to be added because they are + # contained within a single pixel array. if len(self._segment_inventory) == 0: - self.SegmentsOverlap = SegmentsOverlap.NO.value + self.SegmentsOverlap = SegmentsOverlapValues.NO.value else: # If this is not the first set of segments to be added, we cannot # be sure whether there is overlap with the existing segments - self.SegmentsOverlap = SegmentsOverlap.UNDEFINED.value + self.SegmentsOverlap = SegmentsOverlapValues.UNDEFINED.value src_img = self._source_images[0] is_multiframe = hasattr(src_img, 'NumberOfFrames') @@ -717,7 +715,7 @@ def add_segments( # pixel data, add the new pixels and then re-encode. This process # should be avoided if it is not necessary in order to improve # efficiency. - if (self.SegmentationType == SegmentationTypes.BINARY.value and + if (self.SegmentationType == SegmentationTypeValues.BINARY.value and ((self.Rows * self.Columns * self.SamplesPerPixel) % 8) > 0): re_encode_pixel_data = True logger.warning( @@ -732,7 +730,7 @@ def add_segments( else: re_encode_pixel_data = False - # Before adding new pixel data, remove the trailing null padding byte + # Before adding new pixel data, remove trailing null padding byte if len(self.PixelData) == get_expected_length(self) + 1: self.PixelData = self.PixelData[:-1] @@ -905,7 +903,7 @@ def _encode_pixels(self, planes: np.ndarray) -> bytes: """ # TODO: compress depending on transfer syntax UID - if self.SegmentationType == SegmentationTypes.BINARY.value: + if self.SegmentationType == SegmentationTypeValues.BINARY.value: return pack_bits(planes.flatten()) else: return planes.flatten().tobytes() diff --git a/src/highdicom/sr/content.py b/src/highdicom/sr/content.py index 189b5c54..4d532de1 100644 --- a/src/highdicom/sr/content.py +++ b/src/highdicom/sr/content.py @@ -5,10 +5,10 @@ from pydicom.sr.coding import Code from highdicom.sr.coding import CodedConcept from highdicom.sr.enum import ( - GraphicTypes, - GraphicTypes3D, - PixelOriginInterpretations, - RelationshipTypes, + GraphicTypeValues, + GraphicTypeValues3D, + PixelOriginInterpretationValues, + RelationshipTypeValues, ) from highdicom.sr.value_types import ( CodeContentItem, @@ -53,7 +53,7 @@ def __init__( ), value=value, unit=unit, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) event_type_item = CodeContentItem( name=CodedConcept( @@ -62,7 +62,7 @@ def __init__( scheme_designator='DCM' ), value=event_type, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) self.ContentSequence = ContentSequence([event_type_item]) @@ -100,7 +100,7 @@ def __init__( referenced_sop_class_uid=referenced_sop_class_uid, referenced_sop_instance_uid=referenced_sop_instance_uid, referenced_frame_numbers=referenced_frame_numbers, - relationship_type=RelationshipTypes.SELECTED_FROM + relationship_type=RelationshipTypeValues.SELECTED_FROM ) @@ -137,7 +137,7 @@ def __init__( referenced_sop_class_uid=referenced_sop_class_uid, referenced_sop_instance_uid=referenced_sop_instance_uid, referenced_frame_numbers=referenced_frame_numbers, - relationship_type=RelationshipTypes.SELECTED_FROM + relationship_type=RelationshipTypeValues.SELECTED_FROM ) @@ -174,7 +174,7 @@ def __init__( referenced_sop_class_uid=referenced_sop_class_uid, referenced_sop_instance_uid=referenced_sop_instance_uid, referenced_frame_numbers=referenced_frame_numbers, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) @@ -199,7 +199,7 @@ def __init__(self, referenced_series_instance_uid: str): scheme_designator='DCM' ), value=referenced_series_instance_uid, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) @@ -211,35 +211,35 @@ class ImageRegion(ScoordContentItem): def __init__( self, - graphic_type: Union[GraphicTypes, str], + graphic_type: Union[GraphicTypeValues, str], graphic_data: np.ndarray, source_image: SourceImageForRegion, pixel_origin_interpretation: Optional[ - Union[PixelOriginInterpretations, str] + Union[PixelOriginInterpretationValues, str] ] = None ) -> None: """ Parameters ---------- - graphic_type: Union[highdicom.sr.enum.GraphicTypes, str] + graphic_type: Union[highdicom.sr.enum.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: pydicom.sr.template.SourceImageForRegion source image to which `graphic_data` relates - pixel_origin_interpretation: Union[highdicom.sr.enum.PixelOriginInterpretations, str], optional + pixel_origin_interpretation: Union[highdicom.sr.enum.PixelOriginInterpretationValues, str], optional whether pixel coordinates specified by `graphic_data` are defined relative to the total pixel matrix - (``highdicom.sr.enum.PixelOriginInterpretations.VOLUME``) or + (``highdicom.sr.enum.PixelOriginInterpretationValues.VOLUME``) or relative to an individual frame - (``highdicom.sr.enum.PixelOriginInterpretations.FRAME``) + (``highdicom.sr.enum.PixelOriginInterpretationValues.FRAME``) of the source image - (default: ``highdicom.sr.enum.PixelOriginInterpretations.VOLUME``) + (default: ``highdicom.sr.enum.PixelOriginInterpretationValues.VOLUME``) """ # noqa - graphic_type = GraphicTypes(graphic_type) - if graphic_type == GraphicTypes.MULTIPOINT: + graphic_type = GraphicTypeValues(graphic_type) + if graphic_type == GraphicTypeValues.MULTIPOINT: raise ValueError( 'Graphic type "MULTIPOINT" is not valid for region.' ) @@ -248,8 +248,8 @@ def __init__( 'Argument "source_image" must have type SourceImageForRegion.' ) if pixel_origin_interpretation is None: - pixel_origin_interpretation = PixelOriginInterpretations.VOLUME - if pixel_origin_interpretation == PixelOriginInterpretations.FRAME: + pixel_origin_interpretation = PixelOriginInterpretationValues.VOLUME + if pixel_origin_interpretation == PixelOriginInterpretationValues.FRAME: if (not hasattr(source_image, 'ReferencedFrameNumber') or source_image.ReferencedFrameNumber is None): raise ValueError( @@ -265,7 +265,7 @@ def __init__( graphic_type=graphic_type, graphic_data=graphic_data, pixel_origin_interpretation=pixel_origin_interpretation, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) self.ContentSequence = [source_image] @@ -278,14 +278,14 @@ class ImageRegion3D(Scoord3DContentItem): def __init__( self, - graphic_type: Union[GraphicTypes3D, str], + graphic_type: Union[GraphicTypeValues3D, str], graphic_data: np.ndarray, frame_of_reference_uid: str ) -> None: """ Parameters ---------- - graphic_type: Union[highdicom.sr.enum.GraphicTypes3D, str] + graphic_type: Union[highdicom.sr.enum.GraphicTypeValues3D, str] name of the graphic type graphic_data: numpy.ndarray array of ordered spatial coordinates, where each row of the array @@ -294,12 +294,12 @@ def __init__( UID of the frame of reference """ # noqa - graphic_type = GraphicTypes3D(graphic_type) - if graphic_type == GraphicTypes3D.MULTIPOINT: + graphic_type = GraphicTypeValues3D(graphic_type) + if graphic_type == GraphicTypeValues3D.MULTIPOINT: raise ValueError( 'Graphic type "MULTIPOINT" is not valid for region.' ) - if graphic_type == GraphicTypes3D.ELLIPSOID: + if graphic_type == GraphicTypeValues3D.ELLIPSOID: raise ValueError( 'Graphic type "ELLIPSOID" is not valid for region.' ) @@ -312,7 +312,7 @@ def __init__( graphic_type=graphic_type, graphic_data=graphic_data, frame_of_reference_uid=frame_of_reference_uid, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) @@ -324,7 +324,7 @@ class VolumeSurface(Scoord3DContentItem): def __init__( self, - graphic_type: Union[GraphicTypes3D, str], + graphic_type: Union[GraphicTypeValues3D, str], graphic_data: np.ndarray, frame_of_reference_uid: str, source_images: Optional[ @@ -335,7 +335,7 @@ def __init__( """ Parameters ---------- - graphic_type: Union[highdicom.sr.enum.GraphicTypes, str] + graphic_type: Union[highdicom.sr.enum.GraphicTypeValues3D, str] name of the graphic type graphic_data: Sequence[Sequence[int]] ordered set of (row, column, frame) coordinate pairs @@ -352,8 +352,8 @@ def __init__( Either one or more source images or one source series must be provided. """ # noqa - graphic_type = GraphicTypes3D(graphic_type) - if graphic_type != GraphicTypes3D.ELLIPSOID: + graphic_type = GraphicTypeValues3D(graphic_type) + if graphic_type != GraphicTypeValues3D.ELLIPSOID: raise ValueError( 'Graphic type for volume surface must be "ELLIPSOID".' ) @@ -366,7 +366,7 @@ def __init__( frame_of_reference_uid=frame_of_reference_uid, graphic_type=graphic_type, graphic_data=graphic_data, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) self.ContentSequence = ContentSequence() if source_images is not None: @@ -411,7 +411,7 @@ def __init__(self, referenced_sop_instance_uid: str): ), referenced_sop_class_uid='1.2.840.10008.5.1.4.1.1.67', referenced_sop_instance_uid=referenced_sop_instance_uid, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) @@ -444,7 +444,7 @@ def __init__( scheme_designator='SCT' ), value=anatomic_location, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) self.ContentSequence = ContentSequence() if laterality is not None: @@ -455,7 +455,7 @@ def __init__( scheme_designator='SCT' ), value=laterality, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) self.ContentSequence.append(laterality_item) if topographical_modifier is not None: @@ -466,7 +466,7 @@ def __init__( scheme_designator='SCT' ), value=topographical_modifier, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) self.ContentSequence.append(modifier_item) @@ -512,7 +512,7 @@ def __init__( referenced_sop_instance_uid=sop_instance_uid, referenced_frame_numbers=frame_number, referenced_segment_numbers=segment_number, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) self.append(segmentation_item) if not isinstance(source_image, SourceImageForSegmentation): @@ -574,7 +574,7 @@ def __init__( referenced_sop_instance_uid=sop_instance_uid, referenced_frame_numbers=frame_numbers, referenced_segment_numbers=segment_number, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) self.append(segment_item) if source_images is not None: diff --git a/src/highdicom/sr/enum.py b/src/highdicom/sr/enum.py index a3838384..7cf8e0c9 100644 --- a/src/highdicom/sr/enum.py +++ b/src/highdicom/sr/enum.py @@ -2,7 +2,7 @@ from enum import Enum -class ValueTypes(Enum): +class ValueTypeValues(Enum): """Enumerated values for attribute Value Type.""" @@ -23,7 +23,7 @@ class ValueTypes(Enum): WAVEFORM = 'WAVEFORM' -class GraphicTypes(Enum): +class GraphicTypeValues(Enum): """Enumerated values for attribute Graphic Type.""" @@ -35,7 +35,7 @@ class GraphicTypes(Enum): POLYLINE = 'POLYLINE' -class GraphicTypes3D(Enum): +class GraphicTypeValues3D(Enum): """Enumerated values for attribute Graphic Type 3D.""" @@ -47,7 +47,7 @@ class GraphicTypes3D(Enum): POLYGON = 'POLYGON' -class TemporalRangeTypes(Enum): +class TemporalRangeTypeValues(Enum): """Enumerated values for attribute Temporal Range Type.""" @@ -59,7 +59,7 @@ class TemporalRangeTypes(Enum): SEGMENT = 'SEGMENT' -class RelationshipTypes(Enum): +class RelationshipTypeValues(Enum): """Enumerated values for attribute Relationship Type.""" @@ -72,7 +72,7 @@ class RelationshipTypes(Enum): SELECTED_FROM = 'SELECTED FROM' -class PixelOriginInterpretations(Enum): +class PixelOriginInterpretationValues(Enum): """Enumerated values for attribute Pixel Origin Interpretation.""" diff --git a/src/highdicom/sr/templates.py b/src/highdicom/sr/templates.py index 58340659..12b37c15 100644 --- a/src/highdicom/sr/templates.py +++ b/src/highdicom/sr/templates.py @@ -17,13 +17,13 @@ ReferencedSegmentationFrame, SourceImageForMeasurement, ) +from highdicom.sr.enum import RelationshipTypeValues from highdicom.sr.value_types import ( CodeContentItem, ContainerContentItem, ContentItem, ContentSequence, NumContentItem, - RelationshipTypes, TextContentItem, UIDRefContentItem, ) @@ -80,7 +80,7 @@ def __init__( scheme_designator='DCM' ), value=name, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) self.append(name_item) version_item = TextContentItem( @@ -90,7 +90,7 @@ def __init__( scheme_designator='DCM' ), value=version, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) self.append(version_item) if parameters is not None: @@ -102,7 +102,7 @@ def __init__( scheme_designator='DCM' ), value=param, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) self.append(parameter_item) @@ -130,7 +130,7 @@ def __init__(self, uid: str, identifier: Optional[str] = None): scheme_designator='DCM' ), value=identifier, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(tracking_identifier_item) tracking_uid_item = UIDRefContentItem( @@ -140,7 +140,7 @@ def __init__(self, uid: str, identifier: Optional[str] = None): scheme_designator='DCM' ), value=uid, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(tracking_uid_item) @@ -198,7 +198,7 @@ def __init__( scheme_designator='UMLS' ), value=time_point, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(time_point_item) if time_point_type is not None: @@ -209,7 +209,7 @@ def __init__( scheme_designator='DCM' ), value=time_point_type, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(time_point_type_item) if time_point_order is not None: @@ -220,7 +220,7 @@ def __init__( scheme_designator='DCM' ), value=time_point_order, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(time_point_order_item) if subject_time_point_identifier is not None: @@ -231,7 +231,7 @@ def __init__( scheme_designator='DCM' ), value=subject_time_point_identifier, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(subject_time_point_identifier_item) if protocol_time_point_identifier is not None: @@ -242,7 +242,7 @@ def __init__( scheme_designator='DCM' ), value=protocol_time_point_identifier, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(protocol_time_point_identifier_item) if temporal_offset_from_event is not None: @@ -298,7 +298,7 @@ def __init__( scheme_designator='DCM' ), value=authority, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(description_item) if authority is not None: @@ -309,7 +309,7 @@ def __init__( scheme_designator='DCM' ), value=authority, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(authority_item) @@ -351,14 +351,14 @@ def __init__( description_item = TextContentItem( name=codes.DCM.NormalRangeDescription, value=description, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(description_item) if authority is not None: authority_item = TextContentItem( name=codes.DCM.NormalRangeAuthority, value=authority, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(authority_item) @@ -413,7 +413,7 @@ def __init__( scheme_designator='DCM' ), value=normality, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(normality_item) if measurement_statistical_properties is not None: @@ -440,7 +440,7 @@ def __init__( scheme_designator='DCM' ), value=level_of_significance, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(level_of_significance_item) if selection_status is not None: @@ -451,7 +451,7 @@ def __init__( scheme_designator='DCM' ), value=selection_status, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(selection_status_item) if upper_measurement_uncertainty is not None: @@ -462,7 +462,7 @@ def __init__( scheme_designator='SCT' ), value=upper_measurement_uncertainty, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(upper_measurement_uncertainty_item) if lower_measurement_uncertainty is not None: @@ -473,7 +473,7 @@ def __init__( scheme_designator='SCT' ), value=lower_measurement_uncertainty, - relationship_type=RelationshipTypes.HAS_PROPERTIES + relationship_type=RelationshipTypeValues.HAS_PROPERTIES ) self.append(lower_measurement_uncertainty_item) @@ -513,7 +513,7 @@ def __init__( scheme_designator='DCM', ), value=name, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(name_item) if login_name is not None: @@ -524,7 +524,7 @@ def __init__( scheme_designator='DCM', ), value=login_name, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(login_name_item) if organization_name is not None: @@ -535,7 +535,7 @@ def __init__( scheme_designator='DCM', ), value=organization_name, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(organization_name_item) if role_in_organization is not None: @@ -546,7 +546,7 @@ def __init__( scheme_designator='DCM', ), value=role_in_organization, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(role_in_organization_item) if role_in_procedure is not None: @@ -557,7 +557,7 @@ def __init__( scheme_designator='DCM', ), value=role_in_procedure, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(role_in_procedure_item) @@ -603,7 +603,7 @@ def __init__( scheme_designator='DCM', ), value=uid, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(device_observer_item) if manufacturer_name is not None: @@ -614,7 +614,7 @@ def __init__( scheme_designator='DCM', ), value=manufacturer_name, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(manufacturer_name_item) if model_name is not None: @@ -625,7 +625,7 @@ def __init__( scheme_designator='DCM', ), value=model_name, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(model_name_item) if serial_number is not None: @@ -636,21 +636,21 @@ def __init__( scheme_designator='DCM', ), value=serial_number, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(serial_number_item) if physical_location is not None: physical_location_item = TextContentItem( name=codes.DCM.DeviceObserverPhysicalLocationDuringObservation, value=physical_location, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(physical_location_item) if role_in_procedure is not None: role_in_procedure_item = CodeContentItem( name=codes.DCM.DeviceRoleInProcedure, value=role_in_procedure, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(role_in_procedure_item) @@ -685,7 +685,7 @@ def __init__( scheme_designator='DCM', ), value=observer_type, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(observer_type_item) if observer_type == codes.cid270.Person: @@ -735,7 +735,7 @@ def __init__(self, subject_id: str): scheme_designator='DCM' ), value=subject_id, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(subject_id_item) @@ -775,7 +775,7 @@ def __init__( scheme_designator='DCM' ), value=uid, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(specimen_uid_item) if identifier is not None: @@ -786,7 +786,7 @@ def __init__( scheme_designator='DCM' ), value=identifier, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(specimen_identifier_item) if container_identifier is not None: @@ -797,7 +797,7 @@ def __init__( scheme_designator='DCM' ), value=container_identifier, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(container_identifier_item) if specimen_type is not None: @@ -808,7 +808,7 @@ def __init__( scheme_designator='DCM' ), value=specimen_type, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(specimen_type_item) @@ -847,14 +847,14 @@ def __init__( device_name_item = TextContentItem( name=codes.DCM.DeviceSubjectName, value=name, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(device_name_item) if uid is not None: device_uid_item = UIDRefContentItem( name=codes.DCM.DeviceSubjectUID, value=uid, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(device_uid_item) if manufacturer_name is not None: @@ -865,7 +865,7 @@ def __init__( scheme_designator='DCM', ), value=manufacturer_name, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(manufacturer_name_item) if model_name is not None: @@ -876,7 +876,7 @@ def __init__( scheme_designator='DCM', ), value=model_name, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(model_name_item) if serial_number is not None: @@ -887,14 +887,14 @@ def __init__( scheme_designator='DCM', ), value=serial_number, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(serial_number_item) if physical_location is not None: physical_location_item = TextContentItem( name=codes.DCM.DeviceSubjectPhysicalLocationDuringObservation, value=physical_location, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(physical_location_item) @@ -932,7 +932,7 @@ def __init__( scheme_designator='DCM' ), value=subject_class, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) self.append(subject_class_item) self.extend(subject_class_specific_context) @@ -1012,7 +1012,7 @@ def __init__(self, language: CodedConcept): scheme_designator='DCM', ), value=language, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) self.append(language_item) @@ -1084,7 +1084,7 @@ def __init__( value=value, unit=unit, qualifier=qualifier, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) value_item.ContentSequence = ContentSequence() if tracking_identifier is not None: @@ -1102,7 +1102,7 @@ def __init__( scheme_designator='SCT' ), value=method, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) value_item.ContentSequence.append(method_item) if derivation is not None: @@ -1113,7 +1113,7 @@ def __init__( scheme_designator='DCM' ), value=derivation, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) value_item.ContentSequence.append(derivation_item) if finding_sites is not None: @@ -1214,7 +1214,7 @@ def __init__( meaning='Measurement Group', scheme_designator='DCM' ), - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) group_item.ContentSequence = ContentSequence() if not isinstance(tracking_identifier, TrackingIdentifier): @@ -1237,7 +1237,7 @@ def __init__( scheme_designator='NCIt' ), value=session, - relationship_type=RelationshipTypes.HAS_OBS_CONTEXT + relationship_type=RelationshipTypeValues.HAS_OBS_CONTEXT ) group_item.ContentSequence.append(session_item) if finding_type is not None: @@ -1248,7 +1248,7 @@ def __init__( scheme_designator='DCM' ), value=finding_type, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) group_item.ContentSequence.append(finding_type_item) if method is not None: @@ -1259,7 +1259,7 @@ def __init__( scheme_designator='SCT' ), value=method, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) group_item.ContentSequence.append(method_item) if finding_sites is not None: @@ -1651,7 +1651,7 @@ def __init__( """ # noqa value_item = NumContentItem( name=derivation, - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) value_item.ContentSequence = ContentSequence() for group in measurement_groups: @@ -1761,7 +1761,7 @@ def __init__( scheme_designator='DCM', ), value=procedure, - relationship_type=RelationshipTypes.HAS_CONCEPT_MOD + relationship_type=RelationshipTypeValues.HAS_CONCEPT_MOD ) item.ContentSequence.append(procedure_item) image_library_item = ImageLibrary() @@ -1788,7 +1788,7 @@ def __init__( meaning='Imaging Measurements', scheme_designator='DCM' ), - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) container_item.ContentSequence = [] for measurements in imaging_measurements: @@ -1815,7 +1815,7 @@ def __init__( meaning='Derived Imaging Measurements', scheme_designator='DCM' ), - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) container_item.ContentSequence = [] for measurements in derived_imaging_measurements: @@ -1860,6 +1860,6 @@ def __init__(self): meaning='Image Library', scheme_designator='DCM' ), - relationship_type=RelationshipTypes.CONTAINS + relationship_type=RelationshipTypeValues.CONTAINS ) self.append(library_item) diff --git a/src/highdicom/sr/value_types.py b/src/highdicom/sr/value_types.py index 585e3934..c02dfc6e 100644 --- a/src/highdicom/sr/value_types.py +++ b/src/highdicom/sr/value_types.py @@ -12,12 +12,12 @@ from highdicom.sr.coding import CodedConcept from highdicom.sr.enum import ( - GraphicTypes, - GraphicTypes3D, - PixelOriginInterpretations, - RelationshipTypes, - TemporalRangeTypes, - ValueTypes, + GraphicTypeValues, + GraphicTypeValues3D, + PixelOriginInterpretationValues, + RelationshipTypeValues, + TemporalRangeTypeValues, + ValueTypeValues, ) @@ -28,23 +28,25 @@ class ContentItem(Dataset): def __init__( self, - value_type: Union[str, ValueTypes], + value_type: Union[str, ValueTypeValues], name: Union[Code, CodedConcept], - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters ---------- - value_type: Union[str, highdicom.sr.enum.ValueTypes] + value_type: Union[str, highdicom.sr.enum.ValueTypeValues] type of value encoded in a content item name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] coded name or an enumerated item representing a coded name - relationship_type: Union[str, highdicom.sr.enum.RelationshipTypes], optional + relationship_type: Union[str, highdicom.sr.enum.RelationshipTypeValues], optional type of relationship with parent content item """ # noqa super(ContentItem, self).__init__() - value_type = ValueTypes(value_type) + value_type = ValueTypeValues(value_type) self.ValueType = value_type.value if not isinstance(name, (CodedConcept, Code, )): raise TypeError( @@ -54,7 +56,7 @@ def __init__( name = CodedConcept(*name) self.ConceptNameCodeSequence = [name] if relationship_type is not None: - relationship_type = RelationshipTypes(relationship_type) + relationship_type = RelationshipTypeValues(relationship_type) self.RelationshipType = relationship_type.value def __setattr__(self, name: str, value: Any): @@ -71,7 +73,7 @@ def name(self): @property def value_type(self): """str: type of the content item - (see `highdicom.sr.value_types.ValueTypes`) + (see `highdicom.sr.value_types.ValueTypeValues`) """ return self.ValueType @@ -79,7 +81,7 @@ def value_type(self): @property def relationship_type(self): """str: type of relationship the content item has with its parent - (see `highdicom.sr.enum.RelationshipTypes`) + (see `highdicom.sr.enum.RelationshipTypeValues`) """ return getattr(self, 'RelationshipType', None) @@ -87,8 +89,10 @@ def relationship_type(self): def get_content_items( self, name: Optional[Union[Code, CodedConcept]] = None, - value_type: Optional[Union[ValueTypes, str]] = None, - relationship_type: Optional[Union[ValueTypes, str]] = None + value_type: Optional[Union[ValueTypeValues, str]] = None, + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ) -> 'ContentSequence': """Gets content items, i.e. items contained in the content sequence, optionally filtering them based on specified criteria. @@ -97,12 +101,12 @@ def get_content_items( ---------- name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional coded name that items should have - value_type: Union[highdicom.sr.value_types.ValueTypes, str], optional + value_type: Union[highdicom.sr.value_types.ValueTypeValues, str], optional type of value that items should have - (e.g. ``highdicom.sr.value_types.ValueTypes.CONTAINER``) - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + (e.g. ``highdicom.sr.value_types.ValueTypeValues.CONTAINER``) + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship that items should have with its parent - (e.g. ``highdicom.sr.enum.RelationshipTypes.CONTAINS``) + (e.g. ``highdicom.sr.enum.RelationshipTypeValues.CONTAINS``) Returns ------- @@ -168,8 +172,10 @@ def get_nodes(self) -> 'ContentSequence': def filter( self, name: Optional[Union[Code, CodedConcept]] = None, - value_type: Optional[Union[ValueTypes, str]] = None, - relationship_type: Optional[Union[ValueTypes, str]] = None + value_type: Optional[Union[ValueTypeValues, str]] = None, + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ) -> 'ContentSequence': """Filters content items. @@ -177,12 +183,12 @@ def filter( ---------- name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code], optional coded name that items should have - value_type: Union[highdicom.sr.value_types.ValueTypes, str], optional + value_type: Union[highdicom.sr.value_types.ValueTypeValues, str], optional type of value that items should have - (e.g. ``highdicom.sr.value_types.ValueTypes.CONTAINER``) - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + (e.g. ``highdicom.sr.value_types.ValueTypeValues.CONTAINER``) + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship that items should have with its parent - (e.g. ``highdicom.sr.enum.RelationshipTypes.CONTAINS``) + (e.g. ``highdicom.sr.enum.RelationshipTypeValues.CONTAINS``) Returns ------- @@ -268,7 +274,9 @@ def __init__( self, name: Union[Code, CodedConcept], value: Union[Code, CodedConcept], - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -277,12 +285,12 @@ def __init__( concept name value: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] coded value or an enumerated item representing a coded value - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(CodeContentItem, self).__init__( - ValueTypes.CODE, name, relationship_type + ValueTypeValues.CODE, name, relationship_type ) if not isinstance(value, (CodedConcept, Code, )): raise TypeError( @@ -301,7 +309,9 @@ def __init__( self, name: Union[Code, CodedConcept], value: Union[str, PersonName], - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -310,12 +320,12 @@ def __init__( concept name value: Union[str, pydicom.valuerep.PersonName] name of the person - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(PnameContentItem, self).__init__( - ValueTypes.PNAME, name, relationship_type + ValueTypeValues.PNAME, name, relationship_type ) self.PersonName = PersonName(value) @@ -328,7 +338,9 @@ def __init__( self, name: Union[Code, CodedConcept], value: str, - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -337,12 +349,12 @@ def __init__( concept name value: str description of the concept in free text - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(TextContentItem, self).__init__( - ValueTypes.TEXT, name, relationship_type + ValueTypeValues.TEXT, name, relationship_type ) self.TextValue = str(value) @@ -355,7 +367,9 @@ def __init__( self, name: Union[Code, CodedConcept], value: Union[str, datetime.time, TM], - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -364,12 +378,12 @@ def __init__( concept name value: Union[str, datetime.time, pydicom.valuerep.TM] time - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(TimeContentItem, self).__init__( - ValueTypes.TIME, name, relationship_type + ValueTypeValues.TIME, name, relationship_type ) self.Time = TM(value) @@ -382,7 +396,9 @@ def __init__( self, name: Union[Code, CodedConcept], value: Union[str, datetime.date, DA], - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -391,12 +407,12 @@ def __init__( concept name value: Union[str, datetime.date, pydicom.valuerep.DA] date - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(DateContentItem, self).__init__( - ValueTypes.DATE, name, relationship_type + ValueTypeValues.DATE, name, relationship_type ) self.Date = DA(value) @@ -409,7 +425,9 @@ def __init__( self, name: Union[Code, CodedConcept], value: Union[str, datetime.datetime, DT], - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -418,12 +436,12 @@ def __init__( concept name value: Union[str, datetime.datetime, pydicom.valuerep.DT] datetime - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(DateTimeContentItem, self).__init__( - ValueTypes.DATETIME, name, relationship_type + ValueTypeValues.DATETIME, name, relationship_type ) self.DateTime = DT(value) @@ -436,7 +454,9 @@ def __init__( self, name: Union[Code, CodedConcept], value: Union[str, UID], - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -445,12 +465,12 @@ def __init__( concept name value: Union[pydicom.uid.UID, str] unique identifier - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(UIDRefContentItem, self).__init__( - ValueTypes.UIDREF, name, relationship_type + ValueTypeValues.UIDREF, name, relationship_type ) self.UID = UID(value) @@ -465,7 +485,9 @@ def __init__( value: Optional[Union[int, float]] = None, unit: Optional[Union[Code, CodedConcept]] = None, qualifier: Optional[Union[Code, CodedConcept]] = None, - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -481,7 +503,7 @@ def __init__( 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.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item Note @@ -490,7 +512,7 @@ def __init__( """ # noqa super(NumContentItem, self).__init__( - ValueTypes.NUM, name, relationship_type + ValueTypeValues.NUM, name, relationship_type ) if value is not None: self.MeasuredValueSequence = [] @@ -535,7 +557,9 @@ def __init__( name: Union[Code, CodedConcept], is_content_continuous: bool = True, template_id: Optional[str] = None, - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -552,7 +576,7 @@ def __init__( """ super(ContainerContentItem, self).__init__( - ValueTypes.CONTAINER, name, relationship_type + ValueTypeValues.CONTAINER, name, relationship_type ) if is_content_continuous: self.ContinuityOfContent = 'CONTINUOUS' @@ -574,7 +598,9 @@ def __init__( name: Union[Code, CodedConcept], referenced_sop_class_uid: Union[str, UID], referenced_sop_instance_uid: Union[str, UID], - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -585,12 +611,12 @@ def __init__( 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.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(CompositeContentItem, self).__init__( - ValueTypes.COMPOSITE, name, relationship_type + ValueTypeValues.COMPOSITE, name, relationship_type ) item = Dataset() item.ReferencedSOPClassUID = str(referenced_sop_class_uid) @@ -613,7 +639,9 @@ def __init__( referenced_segment_numbers: Optional[ Union[int, Sequence[int]] ] = None, - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters @@ -630,12 +658,12 @@ 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.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(ImageContentItem, self).__init__( - ValueTypes.IMAGE, name, relationship_type + ValueTypeValues.IMAGE, name, relationship_type ) item = Dataset() item.ReferencedSOPClassUID = str(referenced_sop_class_uid) @@ -660,65 +688,70 @@ class ScoordContentItem(ContentItem): def __init__( self, name: Union[Code, CodedConcept], - graphic_type: Union[str, GraphicTypes], + graphic_type: Union[str, GraphicTypeValues], graphic_data: np.ndarray, - pixel_origin_interpretation: Union[str, PixelOriginInterpretations], + pixel_origin_interpretation: Union[ + str, + PixelOriginInterpretationValues + ], fiducial_uid: Optional[Union[str, UID]] = None, - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters ---------- name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] concept name - graphic_type: Union[highdicom.sr.enum.GraphicTypes, str] + graphic_type: Union[highdicom.sr.enum.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.value_types.PixelOriginInterpretations, str] + pixel_origin_interpretation: Union[highdicom.sr.value_types.PixelOriginInterpretationValues, str] whether pixel coordinates specified by `graphic_data` are defined relative to the total pixel matrix - (``highdicom.sr.value_types.PixelOriginInterpretations.VOLUME``) or + (``highdicom.sr.value_types.PixelOriginInterpretationValues.VOLUME``) or relative to an individual frame - (``highdicom.sr.value_types.PixelOriginInterpretations.FRAME``) + (``highdicom.sr.value_types.PixelOriginInterpretationValues.FRAME``) fiducial_uid: Union[pydicom.uid.UID, str, None], optional unique identifier for the content item - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(ScoordContentItem, self).__init__( - ValueTypes.SCOORD, name, relationship_type + ValueTypeValues.SCOORD, name, relationship_type ) - graphic_type = GraphicTypes(graphic_type) - pixel_origin_interpretation = PixelOriginInterpretations( + graphic_type = GraphicTypeValues(graphic_type) + pixel_origin_interpretation = PixelOriginInterpretationValues( pixel_origin_interpretation ) self.GraphicType = graphic_type.value - if graphic_type == GraphicTypes.POINT: + if graphic_type == GraphicTypeValues.POINT: if graphic_data.shape[0] != 1 or not graphic_data.shape[1] == 2: raise ValueError( 'Graphic data of a scoord of graphic type "POINT" ' 'must be a single (column row) pair in two-dimensional ' 'image coordinate space.' ) - elif graphic_type == GraphicTypes.CIRCLE: + elif graphic_type == GraphicTypeValues.CIRCLE: if graphic_data.shape[0] != 2 or not graphic_data.shape[1] == 2: raise ValueError( 'Graphic data of a scoord of graphic type "CIRCLE" ' 'must be two (column, row) pairs in two-dimensional ' 'image coordinate space.' ) - elif graphic_type == GraphicTypes.ELLIPSE: + elif graphic_type == GraphicTypeValues.ELLIPSE: if graphic_data.shape[0] != 4 or not graphic_data.shape[1] == 2: raise ValueError( 'Graphic data of a scoord of graphic type "ELLIPSE" ' 'must be four (column, row) pairs in two-dimensional ' 'image coordinate space.' ) - elif graphic_type == GraphicTypes.ELLIPSOID: + elif graphic_type == GraphicTypeValues.ELLIPSOID: if graphic_data.shape[0] != 6 or not graphic_data.shape[1] == 2: raise ValueError( 'Graphic data of a scoord of graphic type "ELLIPSOID" ' @@ -753,18 +786,20 @@ class Scoord3DContentItem(ContentItem): def __init__( self, name: Union[Code, CodedConcept], - graphic_type: Union[str, GraphicTypes], + graphic_type: Union[str, GraphicTypeValues], graphic_data: np.ndarray, frame_of_reference_uid: Union[str, UID], fiducial_uid: Optional[Union[str, UID]] = None, - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters ---------- name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] concept name - graphic_type: Union[highdicom.sr.enum.GraphicTypes, str] + graphic_type: Union[highdicom.sr.enum.GraphicTypeValues, str] name of the graphic type graphic_data: numpy.ndarray[numpy.float] array of spatial coordinates, where each row of the array @@ -774,31 +809,31 @@ def __init__( coordinates are defined fiducial_uid: str, optional unique identifier for the content item - relationship_type: Union[highdicom.sr.enum.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(Scoord3DContentItem, self).__init__( - ValueTypes.SCOORD3D, name, relationship_type + ValueTypeValues.SCOORD3D, name, relationship_type ) - graphic_type = GraphicTypes3D(graphic_type) + graphic_type = GraphicTypeValues3D(graphic_type) self.GraphicType = graphic_type.value - if graphic_type == GraphicTypes3D.POINT: + if graphic_type == GraphicTypeValues3D.POINT: if graphic_data.shape[0] != 1 or not graphic_data.shape[1] == 3: raise ValueError( 'Graphic data of a scoord 3D of graphic type "POINT" ' 'must be a single point in three-dimensional patient or ' 'slide coordinate space in form of a (x, y, z) triplet.' ) - elif graphic_type == GraphicTypes3D.ELLIPSE: + elif graphic_type == GraphicTypeValues3D.ELLIPSE: if graphic_data.shape[0] != 4 or not graphic_data.shape[1] == 3: raise ValueError( 'Graphic data of a 3D scoord of graphic type "ELLIPSE" ' 'must be four (x, y, z) triplets in three-dimensional ' 'patient or slide coordinate space.' ) - elif graphic_type == GraphicTypes3D.ELLIPSOID: + elif graphic_type == GraphicTypeValues3D.ELLIPSOID: if graphic_data.shape[0] != 6 or not graphic_data.shape[1] == 3: raise ValueError( 'Graphic data of a 3D scoord of graphic type ' @@ -826,18 +861,20 @@ class TcoordContentItem(ContentItem): def __init__( self, name: Union[Code, CodedConcept], - temporal_range_type: Union[str, TemporalRangeTypes], + temporal_range_type: Union[str, TemporalRangeTypeValues], referenced_sample_positions: Optional[Sequence[int]] = None, referenced_time_offsets: Optional[Sequence[float]] = None, referenced_date_time: Optional[Sequence[datetime.datetime]] = None, - relationship_type: Optional[Union[str, RelationshipTypes]] = None + relationship_type: Optional[ + Union[str, RelationshipTypeValues] + ] = None ): """ Parameters ---------- name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] concept name - temporal_range_type: Union[highdicom.sr.enum.TemporalRangeTypes, str] + temporal_range_type: Union[highdicom.sr.enum.TemporalRangeTypeValues, str] name of the temporal range type referenced_sample_positions: Sequence[int], optional one-based relative sample position of acquired time points @@ -846,14 +883,14 @@ 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.RelationshipTypes, str], optional + relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(TcoordContentItem, self).__init__( - ValueTypes.TSCOORD, name, relationship_type + ValueTypeValues.TSCOORD, name, relationship_type ) - temporal_range_type = TemporalRangeTypes(temporal_range_type) + temporal_range_type = TemporalRangeTypeValues(temporal_range_type) self.TemporalRangeType = temporal_range_type.value if referenced_sample_positions is not None: self.ReferencedSamplePositions = [ diff --git a/tests/test_seg.py b/tests/test_seg.py index 0ba266f0..7c7fe90a 100644 --- a/tests/test_seg.py +++ b/tests/test_seg.py @@ -25,7 +25,11 @@ SegmentDescription, Surface, ) -from highdicom.seg.enum import SegmentAlgorithmTypes, SegmentsOverlap, SegmentationTypes +from highdicom.seg.enum import ( + SegmentAlgorithmTypeValues, + SegmentsOverlapValues, + SegmentationTypeValues, +) from highdicom.seg.sop import Segmentation, SurfaceSegmentation @@ -116,7 +120,8 @@ def setUp(self): self._segmented_property_category = \ codes.SCT.MorphologicallyAbnormalStructure self._segmented_property_type = codes.SCT.Neoplasm - self._segment_algorithm_type = SegmentAlgorithmTypes.AUTOMATIC.value + self._segment_algorithm_type = \ + SegmentAlgorithmTypeValues.AUTOMATIC.value self._algorithm_identification = AlgorithmIdentificationSequence( name='bla', family=codes.DCM.ArtificialIntelligence, @@ -557,7 +562,7 @@ def setUp(self): segment_label='Segment #1', segmented_property_category=self._segmented_property_category, segmented_property_type=self._segmented_property_type, - algorithm_type=SegmentAlgorithmTypes.AUTOMATIC.value, + algorithm_type=SegmentAlgorithmTypeValues.AUTOMATIC.value, algorithm_identification=AlgorithmIdentificationSequence( name='bla', family=codes.DCM.ArtificialIntelligence, @@ -571,7 +576,7 @@ def setUp(self): segment_label='Segment #2', segmented_property_category=self._segmented_property_category, segmented_property_type=self._segmented_property_type, - algorithm_type=SegmentAlgorithmTypes.AUTOMATIC.value, + algorithm_type=SegmentAlgorithmTypeValues.AUTOMATIC.value, algorithm_identification=AlgorithmIdentificationSequence( name='foo', family=codes.DCM.ArtificialIntelligence, @@ -604,8 +609,8 @@ def setUp(self): self._sm_image = dcmread( os.path.join(data_dir, 'test_files', 'sm_image.dcm') ) - # Override te existing ImageOrientationSlide to make the frame ordering simpler - # for the tests + # Override te existing ImageOrientationSlide to make the frame ordering + # simpler for the tests self._sm_image.ImageOrientationSlide = [0.0, 1.0, 0.0, 1.0, 0.0, 0.0] self._sm_pixel_array = np.zeros( self._sm_image.pixel_array.shape[:3], # remove colour channel axis @@ -647,7 +652,7 @@ def remove_empty_frames(mask): @staticmethod def get_array_after_writing(instance): - # Write a DICOM object to a buffer, read it again and reconstruct the mask + # Write DICOM object to buffer, read it again and reconstruct the mask with BytesIO() as fp: instance.save_as(fp) fp.seek(0) @@ -659,7 +664,7 @@ def test_construction(self): instance = Segmentation( [self._ct_image], self._ct_pixel_array, - SegmentationTypes.FRACTIONAL.value, + SegmentationTypeValues.FRACTIONAL.value, self._segment_descriptions, self._series_instance_uid, self._series_number, @@ -729,7 +734,8 @@ def test_construction(self): assert len(frame_content_item.DimensionIndexValues) == 2 for derivation_image_item in frame_item.DerivationImageSequence: assert len(derivation_image_item.SourceImageSequence) == 1 - assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.NO + assert SegmentsOverlapValues[instance.SegmentsOverlap] == \ + SegmentsOverlapValues.NO with pytest.raises(AttributeError): frame_item.PlanePositionSlideSequence @@ -737,7 +743,7 @@ def test_construction_2(self): instance = Segmentation( [self._sm_image], self._sm_pixel_array, - SegmentationTypes.FRACTIONAL.value, + SegmentationTypeValues.FRACTIONAL.value, self._segment_descriptions, self._series_instance_uid, self._series_number, @@ -792,7 +798,8 @@ def test_construction_2(self): assert len(derivation_image_item.SourceImageSequence) == 1 source_image_item = derivation_image_item.SourceImageSequence[0] assert hasattr(source_image_item, 'ReferencedFrameNumber') - assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.NO + assert SegmentsOverlapValues[instance.SegmentsOverlap] == \ + SegmentsOverlapValues.NO with pytest.raises(AttributeError): frame_item.PlanePositionSequence @@ -801,7 +808,7 @@ def test_construction_3(self): instance = Segmentation( self._ct_series, self._ct_series_mask_array, - SegmentationTypes.FRACTIONAL.value, + SegmentationTypeValues.FRACTIONAL.value, self._segment_descriptions, self._series_instance_uid, self._series_number, @@ -860,7 +867,8 @@ def test_construction_3(self): if dcm.SOPInstanceUID in uid_to_plane_position } assert source_uid_to_plane_position == uid_to_plane_position - assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.NO + assert SegmentsOverlapValues[instance.SegmentsOverlap] == \ + SegmentsOverlapValues.NO with pytest.raises(AttributeError): frame_item.PlanePositionSlideSequence @@ -869,7 +877,7 @@ def test_construction_4(self): instance = Segmentation( [self._ct_multiframe], self._ct_multiframe_mask_array, - SegmentationTypes.FRACTIONAL.value, + SegmentationTypeValues.FRACTIONAL.value, self._segment_descriptions, self._series_instance_uid, self._series_number, @@ -936,7 +944,8 @@ def test_construction_4(self): assert source_image_item.ReferencedFrameNumber == i + 1 assert source_image_item.ReferencedSOPInstanceUID == \ self._ct_multiframe.SOPInstanceUID - assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.NO + assert SegmentsOverlapValues[instance.SegmentsOverlap] == \ + SegmentsOverlapValues.NO with pytest.raises(AttributeError): frame_item.PlanePositionSlideSequence @@ -951,26 +960,35 @@ def test_pixel_types(self): for source, mask in tests: - # Create a mask for an additional segment as the complement of the original mask + # Create a mask for an additional segment as the complement of the + # original mask additional_mask = (1 - mask) # Find the expected encodings for the masks if mask.ndim > 2: expected_encoding = self.remove_empty_frames(mask) - expected_additional_encoding = self.remove_empty_frames(additional_mask) - two_segment_expected_encoding = np.concatenate([expected_encoding, expected_additional_encoding], axis=0) + expected_additional_encoding = self.remove_empty_frames( + additional_mask + ) + two_segment_expected_encoding = np.concatenate( + [expected_encoding, expected_additional_encoding], + axis=0 + ) expected_encoding = expected_encoding.squeeze() - expected_additional_encoding = expected_additional_encoding.squeeze() + expected_additional_encoding = \ + expected_additional_encoding.squeeze() else: expected_encoding = mask - two_segment_expected_encoding = np.stack([mask, additional_mask]) + two_segment_expected_encoding = np.stack( + [mask, additional_mask] + ) # Test instance creation for different pixel types for pix_type in [np.bool, np.uint8, np.uint16, np.float]: instance = Segmentation( source, mask.astype(pix_type), - SegmentationTypes.FRACTIONAL.value, + SegmentationTypeValues.FRACTIONAL.value, self._segment_descriptions, self._series_instance_uid, self._series_number, @@ -994,7 +1012,8 @@ def test_pixel_types(self): additional_mask.astype(pix_type), self._additional_segment_descriptions ) - assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.UNDEFINED + assert SegmentsOverlapValues[instance.SegmentsOverlap] == \ + SegmentsOverlapValues.UNDEFINED # Ensure the recovered pixel array matches what is expected assert np.array_equal(self.get_array_after_writing(instance), @@ -1004,21 +1023,28 @@ def test_pixel_types(self): additional_mask = (1 - mask) if mask.ndim > 2: expected_encoding = self.remove_empty_frames(mask) - expected_additional_encoding = self.remove_empty_frames(additional_mask) - two_segment_expected_encoding = np.concatenate([ - expected_encoding, expected_additional_encoding - ], axis=0) + expected_additional_encoding = \ + self.remove_empty_frames(additional_mask) + two_segment_expected_encoding = np.concatenate( + [expected_encoding, expected_additional_encoding], + axis=0 + ) expected_encoding = expected_encoding.squeeze() - expected_additional_encoding = expected_additional_encoding.squeeze() + expected_additional_encoding = \ + expected_additional_encoding.squeeze() else: expected_encoding = (mask > 0).astype(mask.dtype) - expected_additional_encoding = (additional_mask > 0).astype(mask.dtype) - two_segment_expected_encoding = np.stack([expected_encoding, expected_additional_encoding]) + expected_additional_encoding = (additional_mask > 0).astype( + mask.dtype + ) + two_segment_expected_encoding = np.stack( + [expected_encoding, expected_additional_encoding] + ) for pix_type in [np.bool, np.uint8, np.uint16, np.float]: instance = Segmentation( source, mask.astype(pix_type), - SegmentationTypes.BINARY.value, + SegmentationTypeValues.BINARY.value, self._segment_descriptions, self._series_instance_uid, self._series_number, @@ -1042,7 +1068,8 @@ def test_pixel_types(self): additional_mask.astype(pix_type), self._additional_segment_descriptions ) - assert SegmentsOverlap[instance.SegmentsOverlap] == SegmentsOverlap.UNDEFINED + assert SegmentsOverlapValues(instance.SegmentsOverlap) == \ + SegmentsOverlapValues.UNDEFINED # Ensure the recovered pixel array matches what is expected assert np.array_equal( @@ -1051,27 +1078,39 @@ def test_pixel_types(self): ) def test_odd_number_pixels(self): - # Test that an image with an odd number of pixels per frame is encoded properly - # Including when additional segments are subsequently added + # Test that an image with an odd number of pixels per frame is encoded + # properly Including when additional segments are subsequently added # Create an instance with an odd number of pixels in each frame # Based on the single frame CT image odd_instance = self._ct_image r = 9 c = 9 - odd_pixels = np.random.randint(256, size=(r, c), dtype=np.uint16) + odd_pixels = np.random.randint( + 256, + size=(r, c), + dtype=np.uint16 + ) odd_instance.PixelData = odd_pixels.flatten().tobytes() odd_instance.Rows = r odd_instance.Columns = c - odd_mask = np.random.randint(2, size=odd_pixels.shape, dtype=np.bool) - addtional_odd_mask = np.random.randint(2, size=odd_pixels.shape, dtype=np.bool) + odd_mask = np.random.randint( + 2, + size=odd_pixels.shape, + dtype=np.bool + ) + addtional_odd_mask = np.random.randint( + 2, + size=odd_pixels.shape, + dtype=np.bool + ) instance = Segmentation( [odd_instance], odd_mask, - SegmentationTypes.BINARY.value, + SegmentationTypeValues.BINARY.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1090,7 +1129,10 @@ def test_odd_number_pixels(self): self._additional_segment_descriptions ) - expected_two_segment_mask = np.stack([odd_mask, addtional_odd_mask], axis=0) + expected_two_segment_mask = np.stack( + [odd_mask, addtional_odd_mask], + axis=0 + ) assert np.array_equal( self.get_array_after_writing(instance), expected_two_segment_mask @@ -1131,7 +1173,7 @@ def test_multi_segments(self): instance = Segmentation( [self._ct_image], mask, - SegmentationTypes.BINARY.value, + SegmentationTypeValues.BINARY.value, all_segment_descriptions, self._series_instance_uid, self._series_number, @@ -1155,8 +1197,11 @@ def test_construction_wrong_segment_order(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, - segment_descriptions=self._additional_segment_descriptions + self._segment_descriptions, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, + segment_descriptions=( + self._additional_segment_descriptions + + self._segment_descriptions + ), series_instance_uid=self._series_instance_uid, series_number=self._series_number, sop_instance_uid=self._sop_instance_uid, @@ -1172,8 +1217,11 @@ def test_construction_duplicate_segment_number(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, - segment_descriptions=self._segment_descriptions + self._segment_descriptions, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, + segment_descriptions=( + self._segment_descriptions + + self._segment_descriptions + ), series_instance_uid=self._series_instance_uid, series_number=self._series_number, sop_instance_uid=self._sop_instance_uid, @@ -1189,8 +1237,11 @@ def test_construction_non_described_segment(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array * 3, - segmentation_type=SegmentationTypes.FRACTIONAL.value, - segment_descriptions=self._segment_descriptions + self._segment_descriptions, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, + segment_descriptions=( + self._segment_descriptions + + self._segment_descriptions + ), series_instance_uid=self._series_instance_uid, series_number=self._series_number, sop_instance_uid=self._sop_instance_uid, @@ -1205,7 +1256,7 @@ def test_construction_missing_required_attribute(self): with pytest.raises(TypeError): Segmentation( pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1221,7 +1272,7 @@ def test_construction_missing_required_attribute_2(self): with pytest.raises(TypeError): Segmentation( source_images=[self._ct_image], - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1238,7 +1289,7 @@ def test_construction_missing_required_attribute_3(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, series_instance_uid=self._series_instance_uid, series_number=self._series_number, sop_instance_uid=self._sop_instance_uid, @@ -1254,7 +1305,7 @@ def test_construction_missing_required_attribute_4(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_number=self._series_number, sop_instance_uid=self._sop_instance_uid, @@ -1270,7 +1321,7 @@ def test_construction_missing_required_attribute_5(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, sop_instance_uid=self._sop_instance_uid, @@ -1286,7 +1337,7 @@ def test_construction_missing_required_attribute_6(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1302,7 +1353,7 @@ def test_construction_missing_required_attribute_7(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1318,7 +1369,7 @@ def test_construction_missing_required_attribute_8(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1334,7 +1385,7 @@ def test_construction_missing_required_attribute_9(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1350,7 +1401,7 @@ def test_construction_missing_required_attribute_10(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1366,7 +1417,7 @@ def test_construction_missing_required_attribute_11(self): Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1385,7 +1436,7 @@ def test_construction_optional_arguments(self): instance = Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1427,7 +1478,7 @@ def test_construction_optional_arguments_2(self): instance = Segmentation( source_images=[self._ct_image], pixel_array=self._ct_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, @@ -1473,7 +1524,7 @@ def test_construction_optional_arguments_3(self): instance = Segmentation( source_images=[self._sm_image], pixel_array=self._sm_pixel_array, - segmentation_type=SegmentationTypes.FRACTIONAL.value, + segmentation_type=SegmentationTypeValues.FRACTIONAL.value, segment_descriptions=self._segment_descriptions, series_instance_uid=self._series_instance_uid, series_number=self._series_number, diff --git a/tests/test_sr.py b/tests/test_sr.py index 90cfa37f..de439f05 100644 --- a/tests/test_sr.py +++ b/tests/test_sr.py @@ -22,8 +22,8 @@ SourceSeriesForSegmentation ) from highdicom.sr.enum import ( - GraphicTypes, - GraphicTypes3D, + GraphicTypeValues, + GraphicTypeValues3D, ) from highdicom.sr.value_types import ( CodeContentItem, @@ -480,7 +480,7 @@ def test_image_item_construction_single_segment_number(self): def test_scoord_item_construction_point(self): name = codes.DCM.ImageRegion - graphic_type = GraphicTypes.POINT + graphic_type = GraphicTypeValues.POINT graphic_data = np.array([[1.0, 1.0]]) pixel_origin_interpretation = 'FRAME' i = ScoordContentItem( @@ -499,7 +499,7 @@ def test_scoord_item_construction_point(self): def test_scoord_item_construction_circle(self): name = codes.DCM.ImageRegion - graphic_type = GraphicTypes.CIRCLE + graphic_type = GraphicTypeValues.CIRCLE graphic_data = np.array([[1.0, 1.0], [2.0, 2.0]]) pixel_origin_interpretation = 'VOLUME' i = ScoordContentItem( @@ -519,7 +519,7 @@ def test_scoord_item_construction_circle(self): def test_scoord3d_item_construction_point(self): name = codes.DCM.ImageRegion - graphic_type = GraphicTypes3D.POINT + graphic_type = GraphicTypeValues3D.POINT graphic_data = np.array([[1.0, 1.0, 1.0]]) frame_of_reference_uid = '1.2.3' i = Scoord3DContentItem( @@ -538,7 +538,7 @@ def test_scoord3d_item_construction_point(self): def test_scoord3d_item_construction_polygon(self): name = codes.DCM.ImageRegion - graphic_type = GraphicTypes3D.POLYGON + graphic_type = GraphicTypeValues3D.POLYGON graphic_data = np.array([ [1.0, 1.0, 1.0], [2.0, 2.0, 1.0], [1.0, 1.0, 1.0] ]) @@ -778,7 +778,7 @@ def setUp(self): referenced_sop_instance_uid=generate_uid() ) self._region = ImageRegion( - graphic_type=GraphicTypes.POINT, + graphic_type=GraphicTypeValues.POINT, graphic_data=np.array([[1.0, 1.0]]), source_image=self._image ) @@ -881,7 +881,7 @@ def setUp(self): referenced_sop_instance_uid=generate_uid() ) self._region = ImageRegion( - graphic_type=GraphicTypes.CIRCLE, + graphic_type=GraphicTypeValues.CIRCLE, graphic_data=np.array([[1.0, 1.0], [2.0, 2.0]]), source_image=self._image ) @@ -932,7 +932,7 @@ def setUp(self): ] self._regions = [ ImageRegion( - graphic_type=GraphicTypes.POLYLINE, + graphic_type=GraphicTypeValues.POLYLINE, graphic_data=np.array([ [1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [1.0, 1.0] ]), @@ -951,7 +951,7 @@ def test_constructed_with_volume(self): referenced_sop_instance_uid=generate_uid() ) volume = VolumeSurface( - graphic_type=GraphicTypes3D.ELLIPSOID, + graphic_type=GraphicTypeValues3D.ELLIPSOID, graphic_data=np.array([ [1.0, 2.0, 2.0], [3.0, 2.0, 2.0], [2.0, 1.0, 2.0], [2.0, 3.0, 2.0], @@ -1012,7 +1012,7 @@ def setUp(self): referenced_sop_instance_uid=generate_uid() ) self._region = ImageRegion( - graphic_type=GraphicTypes.CIRCLE, + graphic_type=GraphicTypeValues.CIRCLE, graphic_data=np.array([[1.0, 1.0], [2.0, 2.0]]), source_image=self._image )