def __init__( self, name: Union[Code, CodedConcept], value: Union[str, datetime.date, DA], relationship_type: Union[str, RelationshipTypeValues, None] = None ) -> None: """ Parameters ---------- name: Union[highdicom.sr.CodedConcept, pydicom.sr.coding.Code] concept name value: Union[str, datetime.date, pydicom.valuerep.DA] date relationship_type: Union[highdicom.sr.RelationshipTypeValues, str] type of relationship with parent content item """ # noqa if relationship_type is None: warnings.warn( 'A future release will require that relationship types be ' f'provided for items of type {self.__class__.__name__}.', DeprecationWarning) super(DateContentItem, self).__init__(ValueTypeValues.DATE, name, relationship_type) self.Date = DA(value)
def test_multivalue_DA(self): """Write DA/DT/TM data elements..........""" multi_DA_expected = (date(1961, 8, 4), date(1963, 11, 22)) DA_expected = date(1961, 8, 4) tzinfo = tzoffset('-0600', -21600) multi_DT_expected = (datetime(1961, 8, 4), datetime(1963, 11, 22, 12, 30, 0, 0, tzoffset('-0600', -21600))) multi_TM_expected = (time(1, 23, 45), time(11, 11, 11)) TM_expected = time(11, 11, 11, 1) ds = read_file(datetime_name) # Add date/time data elements ds.CalibrationDate = MultiValue(DA, multi_DA_expected) ds.DateOfLastCalibration = DA(DA_expected) ds.ReferencedDateTime = MultiValue(DT, multi_DT_expected) ds.CalibrationTime = MultiValue(TM, multi_TM_expected) ds.TimeOfLastCalibration = TM(TM_expected) ds.save_as(datetime_out) # Now read it back in and check the values are as expected ds = read_file(datetime_out) self.assertSequenceEqual( multi_DA_expected, ds.CalibrationDate, "Multiple dates not written correctly (VR=DA)") self.assertEqual(DA_expected, ds.DateOfLastCalibration, "Date not written correctly (VR=DA)") self.assertSequenceEqual( multi_DT_expected, ds.ReferencedDateTime, "Multiple datetimes not written correctly (VR=DT)") self.assertSequenceEqual( multi_TM_expected, ds.CalibrationTime, "Multiple times not written correctly (VR=TM)") self.assertEqual(TM_expected, ds.TimeOfLastCalibration, "Time not written correctly (VR=DA)") if os.path.exists(datetime_out): os.remove(datetime_out) # get rid of the file
def test_date_item_construction_from_time(self): name = codes.DCM.StudyTime value = datetime.now().date() i = DateContentItem(name=name, value=value) assert i.ValueType == 'DATE' assert i.ConceptNameCodeSequence[0] == name assert i.Date == DA(value) with pytest.raises(AttributeError): assert i.RelationshipType
def convert_DA_string(byte_string, is_little_endian, struct_format=None): """Read and return a DA value""" if datetime_conversion: if not in_py2: byte_string = byte_string.decode(encoding) length = len(byte_string) if length != 8: logger.warn("Expected length to be 8, got length %d", length) return DA(byte_string) else: return convert_string(byte_string, is_little_endian, struct_format)
def __init__( self, name: Union[Code, CodedConcept], value: Union[str, datetime.date, DA], relationship_type: Optional[Union[str, RelationshipTypeValues]] = None ) -> None: """ Parameters ---------- name: Union[highdicom.sr.coding.CodedConcept, pydicom.sr.coding.Code] concept name value: Union[str, datetime.date, pydicom.valuerep.DA] date relationship_type: Union[highdicom.sr.enum.RelationshipTypeValues, str], optional type of relationship with parent content item """ # noqa super(DateContentItem, self).__init__(ValueTypeValues.DATE, name, relationship_type) self.Date = DA(value)
def _DA_from_byte_string(byte_string): return DA(byte_string.rstrip())
def _DA_from_byte_string(byte_string): byte_string = byte_string.rstrip() length = len(byte_string) if length != 8 and length != 0: logger.warn("Expected length to be 8, got length %d", length) return DA(byte_string)
def _DA_from_str(value: str) -> DA: return DA(value.rstrip())
def __init__(self, study_instance_uid: str, series_instance_uid: str, series_number: int, sop_instance_uid: str, sop_class_uid: str, instance_number: int, modality: str, manufacturer: Optional[str] = None, transfer_syntax_uid: Optional[str] = None, patient_id: Optional[str] = None, patient_name: Optional[str] = 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[str] = None, content_qualification: Optional[Union[ str, ContentQualificationValues]] = None, coding_schemes: Optional[ Sequence[CodingSchemeIdentificationItem]] = None, series_description: Optional[str] = None): """ Parameters ---------- study_instance_uid: str UID of the study series_instance_uid: str UID of the series series_number: Union[int, None] Number of the series within the study sop_instance_uid: str UID that should be assigned to the instance instance_number: int Number that should be assigned to the instance manufacturer: str Name of the manufacturer (developer) of the device (software) that creates the instance modality: str Name of the modality transfer_syntax_uid: str, optional UID of transfer syntax that should be used for encoding of data elements. Defaults to Implicit VR Little Endian (UID ``"1.2.840.10008.1.2"``) patient_id: str, optional ID of the patient (medical record number) patient_name: str, 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: str, optional Name of the referring physician content_qualification: Union[str, highdicom.enum.ContentQualificationValues], optional Indicator of content qualification coding_schemes: Sequence[highdicom.sr.coding.CodingSchemeIdentificationItem], optional private or public coding schemes that are not part of the DICOM standard series_description: str, optional Human readable description of the series Note ---- The constructor only provides attributes that are required by the standard (type 1 and 2) as part of the Patient, General Study, Patient Study, General Series, General Equipment and SOP Common modules. Derived classes are responsible for providing additional attributes required by the corresponding Information Object Definition (IOD). Additional optional attributes can subsequently be added to the dataset. """ # noqa super().__init__() if transfer_syntax_uid is None: transfer_syntax_uid = ImplicitVRLittleEndian if transfer_syntax_uid == ExplicitVRBigEndian: self.is_little_endian = False else: self.is_little_endian = True if transfer_syntax_uid == ImplicitVRLittleEndian: self.is_implicit_VR = True else: self.is_implicit_VR = False # Include all File Meta Information required for writing SOP instance # to a file in PS3.10 format. self.preamble = b'\x00' * 128 self.file_meta = Dataset() self.file_meta.DICOMPrefix = 'DICM' self.file_meta.FilePreamble = self.preamble self.file_meta.TransferSyntaxUID = transfer_syntax_uid self.file_meta.MediaStorageSOPClassUID = str(sop_class_uid) self.file_meta.MediaStorageSOPInstanceUID = str(sop_instance_uid) self.file_meta.FileMetaInformationVersion = b'\x00\x01' self.file_meta.ImplementationClassUID = '1.2.826.0.1.3680043.9.7433.1.1' self.file_meta.ImplementationVersionName = '{} v{}'.format( __name__.split('.')[0], __version__) self.fix_meta_info(enforce_standard=True) with BytesIO() as fp: write_file_meta_info(fp, self.file_meta, enforce_standard=True) self.file_meta.FileMetaInformationGroupLength = len(fp.getvalue()) # Patient self.PatientID = patient_id self.PatientName = patient_name self.PatientBirthDate = patient_birth_date self.PatientSex = patient_sex # Study self.StudyInstanceUID = str(study_instance_uid) self.AccessionNumber = accession_number self.StudyID = study_id self.StudyDate = DA(study_date) if study_date is not None else None self.StudyTime = TM(study_time) if study_time is not None else None self.ReferringPhysicianName = referring_physician_name # Series self.SeriesInstanceUID = str(series_instance_uid) self.SeriesNumber = series_number self.Modality = modality if series_description is not None: self.SeriesDescription = series_description # Equipment self.Manufacturer = manufacturer # Instance self.SOPInstanceUID = str(sop_instance_uid) self.SOPClassUID = str(sop_class_uid) self.InstanceNumber = instance_number self.ContentDate = DA(datetime.datetime.now().date()) self.ContentTime = TM(datetime.datetime.now().time()) if content_qualification is not None: content_qualification = ContentQualificationValues( content_qualification) self.ContentQualification = content_qualification.value if coding_schemes is not None: self.CodingSchemeIdentificationSequence: List[Dataset] = [] for item in coding_schemes: if not isinstance(item, CodingSchemeIdentificationItem): raise TypeError( 'Coding scheme identification item must have type ' '"CodingSchemeIdentificationItem".') self.CodingSchemeIdentificationSequence.append(item)
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