def test_jpeg_rgb_wrong_planar_configuration(self): frame = np.ones((16, 32, 3), dtype=np.uint8) with pytest.raises(ValueError): encode_frame(frame, transfer_syntax_uid=JPEGBaseline, bits_allocated=8, bits_stored=8, photometric_interpretation='YBR_FULL_422', pixel_representation=0, planar_configuration=1)
def test_jpeg2000_monochrome_wrong_pixel_representation(self): frame = np.zeros((16, 32), dtype=np.uint16) with pytest.raises(ValueError): encode_frame( frame, transfer_syntax_uid=JPEG2000Lossless, bits_allocated=16, bits_stored=16, photometric_interpretation='MONOCHROME2', pixel_representation=1, )
def test_jpeg_monochrome(self): bits_allocated = 8 frame = np.zeros((16, 32), dtype=np.dtype(f'uint{bits_allocated}')) compressed_frame = encode_frame( frame, transfer_syntax_uid=JPEGBaseline, bits_allocated=bits_allocated, bits_stored=bits_allocated, photometric_interpretation='MONOCHROME1', pixel_representation=0) assert compressed_frame.startswith(b'\xFF\xD8') assert compressed_frame.endswith(b'\xFF\xD9')
def test_jpeg_rgb(self): bits_allocated = 8 frame = np.ones((16, 32, 3), dtype=np.dtype(f'uint{bits_allocated}')) frame *= 255 compressed_frame = encode_frame( frame, transfer_syntax_uid=JPEGBaseline, bits_allocated=bits_allocated, bits_stored=bits_allocated, photometric_interpretation='YBR_FULL_422', pixel_representation=0, planar_configuration=0) assert compressed_frame.startswith(b'\xFF\xD8') assert compressed_frame.endswith(b'\xFF\xD9')
def _encode_pixels(self, planes: np.ndarray) -> bytes: """Encodes pixel planes. Parameters ---------- planes: numpy.ndarray Array representing one or more segmentation image planes. For encapsulated transfer syntaxes, only a single frame may be processed. For other transfer syntaxes, multiple planes in a 3D array may be processed. Returns ------- bytes Encoded pixels Raises ------ ValueError If multiple frames are passed when using an encapsulated transfer syntax. """ if self.file_meta.TransferSyntaxUID.is_encapsulated: # Check that only a single plane was passed if planes.ndim == 3: if planes.shape[0] == 1: planes = planes[0, ...] else: raise ValueError( 'Only single frame can be encoded at at time ' 'in case of encapsulated format encoding.') return encode_frame( planes, transfer_syntax_uid=self.file_meta.TransferSyntaxUID, bits_allocated=self.BitsAllocated, bits_stored=self.BitsStored, photometric_interpretation=self.PhotometricInterpretation, pixel_representation=self.PixelRepresentation) else: # The array may represent more than one frame item. if self.SegmentationType == SegmentationTypeValues.BINARY.value: return pack_bits(planes.flatten()) else: return planes.flatten().tobytes()
def test_jpeg2000_monochrome(self): bits_allocated = 16 frame = np.zeros((16, 32), dtype=np.dtype(f'uint{bits_allocated}')) compressed_frame = encode_frame( frame, transfer_syntax_uid=JPEG2000Lossless, bits_allocated=bits_allocated, bits_stored=bits_allocated, photometric_interpretation='MONOCHROME1', pixel_representation=0, ) assert compressed_frame.startswith(b'\x00\x00\x00\x0C\x6A\x50\x20') assert compressed_frame.endswith(b'\xFF\xD9') decoded_frame = decode_frame(value=compressed_frame, transfer_syntax_uid=JPEG2000Lossless, rows=frame.shape[0], columns=frame.shape[1], samples_per_pixel=1, bits_allocated=bits_allocated, bits_stored=bits_allocated, photometric_interpretation='MONOCHROME1', pixel_representation=0, planar_configuration=0) np.testing.assert_array_equal(frame, decoded_frame)
def test_jpeg2000_rgb(self): bits_allocated = 8 frame = np.ones((16, 32, 3), dtype=np.dtype(f'uint{bits_allocated}')) frame *= 255 compressed_frame = encode_frame(frame, transfer_syntax_uid=JPEG2000Lossless, bits_allocated=bits_allocated, bits_stored=bits_allocated, photometric_interpretation='YBR_FULL', pixel_representation=0, planar_configuration=0) assert compressed_frame.startswith(b'\x00\x00\x00\x0C\x6A\x50\x20') assert compressed_frame.endswith(b'\xFF\xD9') decoded_frame = decode_frame(value=compressed_frame, transfer_syntax_uid=JPEG2000Lossless, rows=frame.shape[0], columns=frame.shape[1], samples_per_pixel=frame.shape[2], bits_allocated=bits_allocated, bits_stored=bits_allocated, photometric_interpretation='YBR_FULL', pixel_representation=0, planar_configuration=0) np.testing.assert_array_equal(frame, decoded_frame)
def __init__( self, pixel_array: np.ndarray, photometric_interpretation: Union[str, PhotometricInterpretationValues], bits_allocated: int, coordinate_system: Union[str, CoordinateSystemNames], study_instance_uid: str, series_instance_uid: str, series_number: int, sop_instance_uid: str, instance_number: int, manufacturer: str, patient_id: Optional[str] = None, patient_name: Optional[Union[str, PersonName]] = None, patient_birth_date: Optional[str] = None, patient_sex: Optional[str] = None, accession_number: Optional[str] = None, study_id: str = None, study_date: Optional[Union[str, datetime.date]] = None, study_time: Optional[Union[str, datetime.time]] = None, referring_physician_name: Optional[Union[str, PersonName]] = None, pixel_spacing: Optional[Tuple[int, int]] = None, laterality: Optional[Union[str, LateralityValues]] = None, patient_orientation: Optional[ Union[Tuple[str, str], Tuple[PatientOrientationValuesBiped, PatientOrientationValuesBiped, ], Tuple[PatientOrientationValuesQuadruped, PatientOrientationValuesQuadruped, ]]] = None, anatomical_orientation_type: Optional[Union[ str, AnatomicalOrientationTypeValues]] = None, container_identifier: Optional[str] = None, issuer_of_container_identifier: Optional[ IssuerOfIdentifier] = None, specimen_descriptions: Optional[ Sequence[SpecimenDescription]] = None, transfer_syntax_uid: str = ImplicitVRLittleEndian, **kwargs: Any): """ Parameters ---------- pixel_array: numpy.ndarray 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.PhotometricInterpretationValues] Interpretation of pixel data; either ``"MONOCHROME1"`` or ``"MONOCHROME2"`` for 2D grayscale images or ``"RGB"`` or ``"YBR_FULL"`` for 3D color images bits_allocated: int Number of bits that should be allocated per pixel value coordinate_system: Union[str, highdicom.enum.CoordinateSystemNames] Subject (``"PATIENT"`` or ``"SLIDE"``) that was the target of imaging study_instance_uid: str Study Instance UID series_instance_uid: str Series Instance UID of the SC image series series_number: Union[int, None] Series Number of the SC image series sop_instance_uid: str SOP instance UID that should be assigned to the SC image instance instance_number: int Number that should be assigned to this SC image instance manufacturer: str Name of the manufacturer of the device that creates the SC image instance (in a research setting this is typically the same as `institution_name`) patient_id: str, optional ID of the patient (medical record number) patient_name: Optional[Union[str, PersonName]], optional Name of the patient patient_birth_date: str, optional Patient's birth date patient_sex: str, optional Patient's sex study_id: str, optional ID of the study accession_number: str, optional Accession number of the study study_date: Union[str, datetime.date], optional Date of study creation study_time: Union[str, datetime.time], optional Time of study creation referring_physician_name: Optional[Union[str, PersonName]], optional Name of the referring physician pixel_spacing: Tuple[int, int], optional Physical spacing in millimeter between pixels along the row and column dimension laterality: Union[str, highdicom.enum.LateralityValues], optional Laterality of the examined body part patient_orientation: 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.AnatomicalOrientationTypeValues], optional Type of anatomical orientation of patient relative to image (may be provide if `coordinate_system` is ``"PATIENT"`` and patient is an animal) container_identifier: str, optional Identifier of the container holding the specimen (required if `coordinate_system` is ``"SLIDE"``) issuer_of_container_identifier: highdicom.IssuerOfIdentifier, optional Issuer of `container_identifier` specimen_descriptions: Sequence[highdicom.SpecimenDescriptions], optional Description of each examined specimen (required if `coordinate_system` is ``"SLIDE"``) transfer_syntax_uid: str, optional UID of transfer syntax that should be used for encoding of data elements. The following lossless compressed transfer syntaxes are supported: RLE Lossless (``"1.2.840.10008.1.2.5"``). **kwargs: Any, optional Additional keyword arguments that will be passed to the constructor of `highdicom.base.SOPClass` """ # noqa supported_transfer_syntaxes = { ImplicitVRLittleEndian, ExplicitVRLittleEndian, RLELossless, } if transfer_syntax_uid not in supported_transfer_syntaxes: raise ValueError( f'Transfer syntax "{transfer_syntax_uid}" is not supported') # Check names if patient_name is not None: check_person_name(patient_name) if referring_physician_name is not None: check_person_name(referring_physician_name) super().__init__(study_instance_uid=study_instance_uid, series_instance_uid=series_instance_uid, series_number=series_number, sop_instance_uid=sop_instance_uid, sop_class_uid=SecondaryCaptureImageStorage, instance_number=instance_number, manufacturer=manufacturer, modality='OT', transfer_syntax_uid=transfer_syntax_uid, patient_id=patient_id, patient_name=patient_name, patient_birth_date=patient_birth_date, patient_sex=patient_sex, accession_number=accession_number, study_id=study_id, study_date=study_date, study_time=study_time, referring_physician_name=referring_physician_name, **kwargs) coordinate_system = CoordinateSystemNames(coordinate_system) if coordinate_system == CoordinateSystemNames.PATIENT: if patient_orientation is None: raise TypeError( 'Patient orientation is required if coordinate system ' 'is "PATIENT".') # General Series if laterality is not None: laterality = LateralityValues(laterality) self.Laterality = laterality.value # General Image if anatomical_orientation_type is not None: anatomical_orientation_type = AnatomicalOrientationTypeValues( anatomical_orientation_type) self.AnatomicalOrientationType = \ anatomical_orientation_type.value else: anatomical_orientation_type = \ AnatomicalOrientationTypeValues.BIPED row_orientation, col_orientation = patient_orientation if (anatomical_orientation_type == AnatomicalOrientationTypeValues.BIPED): patient_orientation = ( PatientOrientationValuesBiped(row_orientation).value, PatientOrientationValuesBiped(col_orientation).value, ) else: patient_orientation = ( PatientOrientationValuesQuadruped(row_orientation).value, PatientOrientationValuesQuadruped(col_orientation).value, ) self.PatientOrientation = list(patient_orientation) elif coordinate_system == CoordinateSystemNames.SLIDE: if container_identifier is None: raise TypeError( 'Container identifier is required if coordinate system ' 'is "SLIDE".') if specimen_descriptions is None: raise TypeError( 'Specimen descriptions are required if coordinate system ' 'is "SLIDE".') # Specimen self.ContainerIdentifier = container_identifier self.IssuerOfTheContainerIdentifierSequence: List[Dataset] = [] if issuer_of_container_identifier is not None: self.IssuerOftheContainerIdentifierSequence.append( issuer_of_container_identifier) container_type_item = CodedConcept(*codes.SCT.MicroscopeSlide) self.ContainerTypeCodeSequence = [container_type_item] self.SpecimenDescriptionSequence = specimen_descriptions # SC Equipment self.ConversionType = ConversionTypeValues.DI.value # SC Image now = datetime.datetime.now() self.DateOfSecondaryCapture = DA(now.date()) self.TimeOfSecondaryCapture = TM(now.time()) # Image Pixel self.ImageType = ['DERIVED', 'SECONDARY', 'OTHER'] self.Rows = pixel_array.shape[0] self.Columns = pixel_array.shape[1] allowed_types = [np.bool_, np.uint8, np.uint16] if not any(pixel_array.dtype == t for t in allowed_types): raise TypeError( 'Pixel array must be of type np.bool_, np.uint8 or np.uint16. ' f'Found {pixel_array.dtype}.') wrong_bit_depth_assignment = ( pixel_array.dtype == np.bool_ and bits_allocated != 1, pixel_array.dtype == np.uint8 and bits_allocated != 8, pixel_array.dtype == np.uint16 and bits_allocated not in (12, 16), ) if any(wrong_bit_depth_assignment): raise ValueError('Pixel array has an unexpected bit depth.') if bits_allocated not in (1, 8, 12, 16): raise ValueError('Unexpected number of bits allocated.') if transfer_syntax_uid == RLELossless and bits_allocated % 8 != 0: raise ValueError( 'When using run length encoding, bits allocated must be a ' 'multiple of 8') self.BitsAllocated = bits_allocated self.HighBit = self.BitsAllocated - 1 self.BitsStored = self.BitsAllocated self.PixelRepresentation = 0 photometric_interpretation = PhotometricInterpretationValues( photometric_interpretation) if pixel_array.ndim == 3: accepted_interpretations = { 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( 'Pixel array has an unexpected photometric interpretation.' ) if pixel_array.shape[-1] != 3: raise ValueError( 'Pixel array has an unexpected number of color channels.') if bits_allocated != 8: raise ValueError('Color images must be 8-bit.') if pixel_array.dtype != np.uint8: raise TypeError( 'Pixel array must have 8-bit unsigned integer data type ' 'in case of a color image.') self.PhotometricInterpretation = photometric_interpretation.value self.SamplesPerPixel = 3 self.PlanarConfiguration = 0 elif pixel_array.ndim == 2: accepted_interpretations = { PhotometricInterpretationValues.MONOCHROME1.value, PhotometricInterpretationValues.MONOCHROME2.value, } if photometric_interpretation.value not in accepted_interpretations: raise ValueError( 'Pixel array has an unexpected photometric interpretation.' ) self.PhotometricInterpretation = photometric_interpretation.value self.SamplesPerPixel = 1 else: raise ValueError( 'Pixel array has an unexpected number of dimensions.') if pixel_spacing is not None: self.PixelSpacing = pixel_spacing encoded_frame = encode_frame( pixel_array, transfer_syntax_uid=self.file_meta.TransferSyntaxUID, bits_allocated=self.BitsAllocated, bits_stored=self.BitsStored, photometric_interpretation=self.PhotometricInterpretation, pixel_representation=self.PixelRepresentation, planar_configuration=getattr(self, 'PlanarConfiguration', None)) if self.file_meta.TransferSyntaxUID.is_encapsulated: self.PixelData = encapsulate([encoded_frame]) else: self.PixelData = encoded_frame