def test_write_be_us(self): """Test DicomIO.write_beUS indirectly""" fp = DicomBytesIO() fp.is_little_endian = False assert fp.getvalue() == b'' fp.write_beUS(0) assert fp.getvalue() == b'\x00\x00' fp.write_beUS(255) assert fp.getvalue() == b'\x00\x00\x00\xFF' fp.write_beUS(65534) assert fp.getvalue() == b'\x00\x00\x00\xFF\xFF\xFE'
def test_write_be_ul(self): """Test DicomIO.write_beUL indirectly""" fp = DicomBytesIO() fp.is_little_endian = False assert fp.getvalue() == b'' fp.write_beUL(0) assert fp.getvalue() == b'\x00\x00\x00\x00' fp.write_beUL(65535) assert fp.getvalue() == b'\x00\x00\x00\x00\x00\x00\xFF\xFF' fp.write_beUL(4294967294) assert fp.getvalue() == ( b'\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFE')
def test_write_tag(self): """Test DicomIO.write_tag indirectly""" tag = Tag(0x01020304) # Little endian fp = DicomBytesIO() fp.is_little_endian = True fp.write_tag(tag) assert fp.getvalue() == b'\x02\x01\x04\x03' # Big endian fp = DicomBytesIO() fp.is_little_endian = False fp.write_tag(tag) assert fp.getvalue() == b'\x01\x02\x03\x04'
def decode(bytestring, is_implicit_vr, is_little_endian, deflated=False): """Decode `bytestring` to a *pydicom* :class:`~pydicom.dataset.Dataset`. .. versionchanged:: 1.5 Added `deflated` keyword parameter Parameters ---------- byestring : io.BytesIO The encoded dataset in the DIMSE Message sent from the peer AE. is_implicit_vr : bool The dataset is encoded as implicit (``True``) or explicit VR (``False``). is_little_endian : bool The byte ordering of the encoded dataset, ``True`` for little endian, ``False`` for big endian. deflated : bool, optional ``True`` if the dataset has been encoded using *Deflated Explicit VR Little Endian* transfer syntax (default ``False``). Returns ------- pydicom.dataset.Dataset The decoded dataset. """ ## Logging transfer_syntax = '' if deflated: transfer_syntax = "Deflated " transfer_syntax += "Little Endian" if is_little_endian else "Big Endian" if is_implicit_vr: transfer_syntax += " Implicit" else: transfer_syntax += " Explicit" LOGGER.debug('pydicom.read_dataset() TransferSyntax="%s"', transfer_syntax) # Rewind to the start of the stream bytestring.seek(0) if deflated: # Decompress the dataset bytestring = DicomBytesIO( zlib.decompress(bytestring.getvalue(), -zlib.MAX_WBITS)) bytestring.is_implicit_VR = is_implicit_vr bytestring.is_little_endian = is_little_endian # Decode the dataset return read_dataset(bytestring, is_implicit_vr, is_little_endian)
def test_getvalue(self): """Test DicomBytesIO.getvalue""" fp = DicomBytesIO(b'\x00\x01\x00\x02') assert fp.getvalue() == b'\x00\x01\x00\x02'
def write_data_element(fp, data_element, encodings=None): """Write the data_element to file fp according to dicom media storage rules. """ # Write element's tag fp.write_tag(data_element.tag) # If explicit VR, write the VR VR = data_element.VR if not fp.is_implicit_VR: if len(VR) != 2: msg = ("Cannot write ambiguous VR of '{}' for data element with " "tag {}.\nSet the correct VR before writing, or use an " "implicit VR transfer syntax".format( VR, repr(data_element.tag))) raise ValueError(msg) if not in_py2: fp.write(bytes(VR, default_encoding)) else: fp.write(VR) if VR in extra_length_VRs: fp.write_US(0) # reserved 2 bytes # write into a buffer to avoid seeking back which can be expansive buffer = DicomBytesIO() buffer.is_little_endian = fp.is_little_endian buffer.is_implicit_VR = fp.is_implicit_VR if data_element.is_raw: # raw data element values can be written as they are buffer.write(data_element.value) is_undefined_length = data_element.length == 0xFFFFFFFF else: if VR not in writers: raise NotImplementedError( "write_data_element: unknown Value Representation " "'{0}'".format(VR)) encodings = encodings or [default_encoding] encodings = convert_encodings(encodings) writer_function, writer_param = writers[VR] is_undefined_length = data_element.is_undefined_length if VR in text_VRs or VR in ('PN', 'SQ'): writer_function(buffer, data_element, encodings=encodings) else: # Many numeric types use the same writer but with numeric format # parameter if writer_param is not None: writer_function(buffer, data_element, writer_param) else: writer_function(buffer, data_element) # valid pixel data with undefined length shall contain encapsulated # data, e.g. sequence items - raise ValueError otherwise (see #238) if is_undefined_length and data_element.tag == 0x7fe00010: val = data_element.value if (fp.is_little_endian and not val.startswith(b'\xfe\xff\x00\xe0') or not fp.is_little_endian and not val.startswith(b'\xff\xfe\xe0\x00')): raise ValueError('Pixel Data with undefined length must ' 'start with an item tag') value_length = buffer.tell() if (not fp.is_implicit_VR and VR not in extra_length_VRs and not is_undefined_length): fp.write_US(value_length) # Explicit VR length field is only 2 bytes else: # write the proper length of the data_element in the length slot, # unless is SQ with undefined length. fp.write_UL(0xFFFFFFFF if is_undefined_length else value_length) fp.write(buffer.getvalue()) if is_undefined_length: fp.write_tag(SequenceDelimiterTag) fp.write_UL(0) # 4-byte 'length' of delimiter data item
def write_data_element(fp, data_element, encodings=None): """Write the data_element to file fp according to dicom media storage rules. """ # Write element's tag fp.write_tag(data_element.tag) # If explicit VR, write the VR VR = data_element.VR if not fp.is_implicit_VR: if len(VR) != 2: msg = ("Cannot write ambiguous VR of '{}' for data element with " "tag {}.\nSet the correct VR before writing, or use an " "implicit VR transfer syntax".format( VR, repr(data_element.tag))) raise ValueError(msg) if not in_py2: fp.write(bytes(VR, default_encoding)) else: fp.write(VR) if VR in extra_length_VRs: fp.write_US(0) # reserved 2 bytes # write into a buffer to avoid seeking back which can be expansive buffer = DicomBytesIO() buffer.is_little_endian = fp.is_little_endian buffer.is_implicit_VR = fp.is_implicit_VR if data_element.is_raw: # raw data element values can be written as they are buffer.write(data_element.value) is_undefined_length = data_element.length == 0xFFFFFFFF else: if VR not in writers: raise NotImplementedError( "write_data_element: unknown Value Representation " "'{0}'".format(VR)) encodings = encodings or [default_encoding] encodings = convert_encodings(encodings) writer_function, writer_param = writers[VR] is_undefined_length = data_element.is_undefined_length if VR in text_VRs or VR in ('PN', 'SQ'): writer_function(buffer, data_element, encodings=encodings) else: # Many numeric types use the same writer but with numeric format # parameter if writer_param is not None: writer_function(buffer, data_element, writer_param) else: writer_function(buffer, data_element) # valid pixel data with undefined length shall contain encapsulated # data, e.g. sequence items - raise ValueError otherwise (see #238) if is_undefined_length and data_element.tag == 0x7fe00010: val = data_element.value if (fp.is_little_endian and not val.startswith(b'\xfe\xff\x00\xe0') or not fp.is_little_endian and not val.startswith(b'\xff\xfe\xe0\x00')): raise ValueError('Pixel Data with undefined length must ' 'start with an item tag') value_length = buffer.tell() if (not fp.is_implicit_VR and VR not in extra_length_VRs and not is_undefined_length): try: fp.write_US(value_length) # Explicit VR length field is 2 bytes except struct.error: msg = ('The value for the data element {} exceeds the size ' 'of 64 kByte and cannot be written in an explicit transfer ' 'syntax. You can save it using Implicit Little Endian ' 'transfer syntax, or you have to truncate the value to not ' 'exceed the maximum size of 64 kByte.' .format(data_element.tag)) raise ValueError(msg) else: # write the proper length of the data_element in the length slot, # unless is SQ with undefined length. fp.write_UL(0xFFFFFFFF if is_undefined_length else value_length) fp.write(buffer.getvalue()) if is_undefined_length: fp.write_tag(SequenceDelimiterTag) fp.write_UL(0) # 4-byte 'length' of delimiter data item
class WriteDataElementTests(unittest.TestCase): """Attempt to write data elements has the expected behaviour""" def setUp(self): # Create a dummy (in memory) file to write to self.f1 = DicomBytesIO() self.f1.is_little_endian = True self.f1.is_implicit_VR = True @staticmethod def encode_element(elem, is_implicit_VR=True, is_little_endian=True): """Return the encoded `elem`. Parameters ---------- elem : pydicom.dataelem.DataElement The element to encode is_implicit_VR : bool Encode using implicit VR, default True is_little_endian : bool Encode using little endian, default True Returns ------- str or bytes The encoded element as str (python2) or bytes (python3) """ with DicomBytesIO() as fp: fp.is_implicit_VR = is_implicit_VR fp.is_little_endian = is_little_endian write_data_element(fp, elem) return fp.parent.getvalue() def test_empty_AT(self): """Write empty AT correctly..........""" # Was issue 74 data_elem = DataElement(0x00280009, "AT", []) expected = hex2bytes(( " 28 00 09 00" # (0028,0009) Frame Increment Pointer " 00 00 00 00" # length 0 )) write_data_element(self.f1, data_elem) got = self.f1.getvalue() msg = ("Did not write zero-length AT value correctly. " "Expected %r, got %r") % (bytes2hex(expected), bytes2hex(got)) msg = "%r %r" % (type(expected), type(got)) msg = "'%r' '%r'" % (expected, got) self.assertEqual(expected, got, msg) def test_write_OD_implicit_little(self): """Test writing elements with VR of OD works correctly.""" # VolumetricCurvePoints bytestring = b'\x00\x01\x02\x03\x04\x05\x06\x07' \ b'\x01\x01\x02\x03\x04\x05\x06\x07' elem = DataElement(0x0070150d, 'OD', bytestring) encoded_elem = self.encode_element(elem) # Tag pair (0070, 150d): 70 00 0d 15 # Length (16): 10 00 00 00 # | Tag | Length | Value -> ref_bytes = b'\x70\x00\x0d\x15\x10\x00\x00\x00' + bytestring self.assertEqual(encoded_elem, ref_bytes) # Empty data elem.value = b'' encoded_elem = self.encode_element(elem) ref_bytes = b'\x70\x00\x0d\x15\x00\x00\x00\x00' self.assertEqual(encoded_elem, ref_bytes) def test_write_OD_explicit_little(self): """Test writing elements with VR of OD works correctly. Elements with a VR of 'OD' use the newer explicit VR encoding (see PS3.5 Section 7.1.2). """ # VolumetricCurvePoints bytestring = b'\x00\x01\x02\x03\x04\x05\x06\x07' \ b'\x01\x01\x02\x03\x04\x05\x06\x07' elem = DataElement(0x0070150d, 'OD', bytestring) encoded_elem = self.encode_element(elem, False, True) # Tag pair (0070, 150d): 70 00 0d 15 # VR (OD): \x4f\x44 # Reserved: \x00\x00 # Length (16): \x10\x00\x00\x00 # | Tag | VR | Rsrvd | Length | Value -> ref_bytes = b'\x70\x00\x0d\x15\x4f\x44\x00\x00\x10\x00\x00\x00' + bytestring self.assertEqual(encoded_elem, ref_bytes) # Empty data elem.value = b'' encoded_elem = self.encode_element(elem, False, True) ref_bytes = b'\x70\x00\x0d\x15\x4f\x44\x00\x00\x00\x00\x00\x00' self.assertEqual(encoded_elem, ref_bytes) def test_write_OL_implicit_little(self): """Test writing elements with VR of OL works correctly.""" # TrackPointIndexList bytestring = b'\x00\x01\x02\x03\x04\x05\x06\x07' \ b'\x01\x01\x02\x03' elem = DataElement(0x00660129, 'OL', bytestring) encoded_elem = self.encode_element(elem) # Tag pair (0066, 0129): 66 00 29 01 # Length (12): 0c 00 00 00 # | Tag | Length | Value -> ref_bytes = b'\x66\x00\x29\x01\x0c\x00\x00\x00' + bytestring self.assertEqual(encoded_elem, ref_bytes) # Empty data elem.value = b'' encoded_elem = self.encode_element(elem) ref_bytes = b'\x66\x00\x29\x01\x00\x00\x00\x00' self.assertEqual(encoded_elem, ref_bytes) def test_write_OL_explicit_little(self): """Test writing elements with VR of OL works correctly. Elements with a VR of 'OL' use the newer explicit VR encoding (see PS3.5 Section 7.1.2). """ # TrackPointIndexList bytestring = b'\x00\x01\x02\x03\x04\x05\x06\x07' \ b'\x01\x01\x02\x03' elem = DataElement(0x00660129, 'OL', bytestring) encoded_elem = self.encode_element(elem, False, True) # Tag pair (0066, 0129): 66 00 29 01 # VR (OL): \x4f\x4c # Reserved: \x00\x00 # Length (12): 0c 00 00 00 # | Tag | VR | Rsrvd | Length | Value -> ref_bytes = b'\x66\x00\x29\x01\x4f\x4c\x00\x00\x0c\x00\x00\x00' + bytestring self.assertEqual(encoded_elem, ref_bytes) # Empty data elem.value = b'' encoded_elem = self.encode_element(elem, False, True) ref_bytes = b'\x66\x00\x29\x01\x4f\x4c\x00\x00\x00\x00\x00\x00' self.assertEqual(encoded_elem, ref_bytes) def test_write_UC_implicit_little(self): """Test writing elements with VR of UC works correctly.""" # VM 1, even data elem = DataElement(0x00189908, 'UC', 'Test') encoded_elem = self.encode_element(elem) # Tag pair (0018, 9908): 08 00 20 01 # Length (4): 04 00 00 00 # Value: \x54\x65\x73\x74 ref_bytes = b'\x18\x00\x08\x99\x04\x00\x00\x00\x54\x65\x73\x74' self.assertEqual(encoded_elem, ref_bytes) # VM 1, odd data - padded to even length elem.value = 'Test.' encoded_elem = self.encode_element(elem) ref_bytes = b'\x18\x00\x08\x99\x06\x00\x00\x00\x54\x65\x73\x74\x2e\x20' self.assertEqual(encoded_elem, ref_bytes) # VM 3, even data elem.value = ['Aa', 'B', 'C'] encoded_elem = self.encode_element(elem) ref_bytes = b'\x18\x00\x08\x99\x06\x00\x00\x00\x41\x61\x5c\x42\x5c\x43' self.assertEqual(encoded_elem, ref_bytes) # VM 3, odd data - padded to even length elem.value = ['A', 'B', 'C'] encoded_elem = self.encode_element(elem) ref_bytes = b'\x18\x00\x08\x99\x06\x00\x00\x00\x41\x5c\x42\x5c\x43\x20' self.assertEqual(encoded_elem, ref_bytes) # Empty data elem.value = '' encoded_elem = self.encode_element(elem) ref_bytes = b'\x18\x00\x08\x99\x00\x00\x00\x00' self.assertEqual(encoded_elem, ref_bytes) def test_write_UC_explicit_little(self): """Test writing elements with VR of UC works correctly. Elements with a VR of 'UC' use the newer explicit VR encoding (see PS3.5 Section 7.1.2). """ # VM 1, even data elem = DataElement(0x00189908, 'UC', 'Test') encoded_elem = self.encode_element(elem, False, True) # Tag pair (0018, 9908): 08 00 20 01 # VR (UC): \x55\x43 # Reserved: \x00\x00 # Length (4): \x04\x00\x00\x00 # Value: \x54\x65\x73\x74 ref_bytes = b'\x18\x00\x08\x99\x55\x43\x00\x00\x04\x00\x00\x00' \ b'\x54\x65\x73\x74' self.assertEqual(encoded_elem, ref_bytes) # VM 1, odd data - padded to even length elem.value = 'Test.' encoded_elem = self.encode_element(elem, False, True) ref_bytes = b'\x18\x00\x08\x99\x55\x43\x00\x00\x06\x00\x00\x00' \ b'\x54\x65\x73\x74\x2e\x20' self.assertEqual(encoded_elem, ref_bytes) # VM 3, even data elem.value = ['Aa', 'B', 'C'] encoded_elem = self.encode_element(elem, False, True) ref_bytes = b'\x18\x00\x08\x99\x55\x43\x00\x00\x06\x00\x00\x00' \ b'\x41\x61\x5c\x42\x5c\x43' self.assertEqual(encoded_elem, ref_bytes) # VM 3, odd data - padded to even length elem.value = ['A', 'B', 'C'] encoded_elem = self.encode_element(elem, False, True) ref_bytes = b'\x18\x00\x08\x99\x55\x43\x00\x00\x06\x00\x00\x00' \ b'\x41\x5c\x42\x5c\x43\x20' self.assertEqual(encoded_elem, ref_bytes) # Empty data elem.value = '' encoded_elem = self.encode_element(elem, False, True) ref_bytes = b'\x18\x00\x08\x99\x55\x43\x00\x00\x00\x00\x00\x00' self.assertEqual(encoded_elem, ref_bytes) def test_write_UR_implicit_little(self): """Test writing elements with VR of UR works correctly.""" # Even length URL elem = DataElement(0x00080120, 'UR', 'http://github.com/darcymason/pydicom') encoded_elem = self.encode_element(elem) # Tag pair (0008, 2001): 08 00 20 01 # Length (36): 24 00 00 00 # Value: 68 to 6d ref_bytes = b'\x08\x00\x20\x01\x24\x00\x00\x00\x68\x74' \ b'\x74\x70\x3a\x2f\x2f\x67\x69\x74\x68\x75' \ b'\x62\x2e\x63\x6f\x6d\x2f\x64\x61\x72\x63' \ b'\x79\x6d\x61\x73\x6f\x6e\x2f\x70\x79\x64' \ b'\x69\x63\x6f\x6d' self.assertEqual(encoded_elem, ref_bytes) # Odd length URL has trailing \x20 (SPACE) padding elem.value = '../test/test.py' encoded_elem = self.encode_element(elem) # Tag pair (0008, 2001): 08 00 20 01 # Length (16): 10 00 00 00 # Value: 2e to 20 ref_bytes = b'\x08\x00\x20\x01\x10\x00\x00\x00\x2e\x2e' \ b'\x2f\x74\x65\x73\x74\x2f\x74\x65\x73\x74' \ b'\x2e\x70\x79\x20' self.assertEqual(encoded_elem, ref_bytes) # Empty value elem.value = '' encoded_elem = self.encode_element(elem) self.assertEqual(encoded_elem, b'\x08\x00\x20\x01\x00\x00\x00\x00') def test_write_UR_explicit_little(self): """Test writing elements with VR of UR works correctly. Elements with a VR of 'UR' use the newer explicit VR encoded (see PS3.5 Section 7.1.2). """ # Even length URL elem = DataElement(0x00080120, 'UR', 'ftp://bits') encoded_elem = self.encode_element(elem, False, True) # Tag pair (0008, 2001): 08 00 20 01 # VR (UR): \x55\x52 # Reserved: \x00\x00 # Length (4): \x0a\x00\x00\x00 # Value: \x66\x74\x70\x3a\x2f\x2f\x62\x69\x74\x73 ref_bytes = b'\x08\x00\x20\x01\x55\x52\x00\x00\x0a\x00\x00\x00' \ b'\x66\x74\x70\x3a\x2f\x2f\x62\x69\x74\x73' self.assertEqual(encoded_elem, ref_bytes) # Odd length URL has trailing \x20 (SPACE) padding elem.value = 'ftp://bit' encoded_elem = self.encode_element(elem, False, True) ref_bytes = b'\x08\x00\x20\x01\x55\x52\x00\x00\x0a\x00\x00\x00' \ b'\x66\x74\x70\x3a\x2f\x2f\x62\x69\x74\x20' self.assertEqual(encoded_elem, ref_bytes) # Empty value elem.value = '' encoded_elem = self.encode_element(elem, False, True) ref_bytes = b'\x08\x00\x20\x01\x55\x52\x00\x00\x00\x00\x00\x00' self.assertEqual(encoded_elem, ref_bytes)
def write_file_meta_info(fp, file_meta, enforce_standard=True): """Write the File Meta Information elements in `file_meta` to `fp`. If `enforce_standard` is True then the file-like `fp` should be positioned past the 128 byte preamble + 4 byte prefix (which should already have been written). DICOM File Meta Information Group Elements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From the DICOM standard, Part 10 Section 7.1, any DICOM file shall contain a 128-byte preamble, a 4-byte DICOM prefix 'DICM' and (at a minimum) the following Type 1 DICOM Elements (from Table 7.1-1): * (0002,0000) FileMetaInformationGroupLength, UL, 4 * (0002,0001) FileMetaInformationVersion, OB, 2 * (0002,0002) MediaStorageSOPClassUID, UI, N * (0002,0003) MediaStorageSOPInstanceUID, UI, N * (0002,0010) TransferSyntaxUID, UI, N * (0002,0012) ImplementationClassUID, UI, N If `enforce_standard` is True then (0002,0000) will be added/updated, (0002,0001) and (0002,0012) will be added if not already present and the other required elements will be checked to see if they exist. If `enforce_standard` is False then `file_meta` will be written as is after minimal validation checking. The following Type 3/1C Elements may also be present: * (0002,0013) ImplementationVersionName, SH, N * (0002,0016) SourceApplicationEntityTitle, AE, N * (0002,0017) SendingApplicationEntityTitle, AE, N * (0002,0018) ReceivingApplicationEntityTitle, AE, N * (0002,0100) PrivateInformationCreatorUID, UI, N * (0002,0102) PrivateInformation, OB, N If `enforce_standard` is True then (0002,0013) will be added/updated. Encoding ~~~~~~~~ The encoding of the File Meta Information shall be Explicit VR Little Endian Parameters ---------- fp : file-like The file-like to write the File Meta Information to. file_meta : pydicom.dataset.Dataset The File Meta Information DataElements. enforce_standard : bool If False, then only the File Meta Information elements already in `file_meta` will be written to `fp`. If True (default) then a DICOM Standards conformant File Meta will be written to `fp`. Raises ------ ValueError If `enforce_standard` is True and any of the required File Meta Information elements are missing from `file_meta`, with the exception of (0002,0000), (0002,0001) and (0002,0012). ValueError If any non-Group 2 Elements are present in `file_meta`. """ validate_file_meta(file_meta, enforce_standard) if enforce_standard and 'FileMetaInformationGroupLength' not in file_meta: # Will be updated with the actual length later file_meta.FileMetaInformationGroupLength = 0 # Write the File Meta Information Group elements # first write into a buffer to avoid seeking back, that can be # expansive and is not allowed if writing into a zip file buffer = DicomBytesIO() buffer.is_little_endian = True buffer.is_implicit_VR = False write_dataset(buffer, file_meta) # If FileMetaInformationGroupLength is present it will be the first written # element and we must update its value to the correct length. if 'FileMetaInformationGroupLength' in file_meta: # Update the FileMetaInformationGroupLength value, which is the number # of bytes from the end of the FileMetaInformationGroupLength element # to the end of all the File Meta Information elements. # FileMetaInformationGroupLength has a VR of 'UL' and so has a value # that is 4 bytes fixed. The total length of when encoded as # Explicit VR must therefore be 12 bytes. file_meta.FileMetaInformationGroupLength = buffer.tell() - 12 buffer.seek(0) write_data_element(buffer, file_meta[0x00020000]) fp.write(buffer.getvalue())
def write_data_element(fp, data_element, encodings=None): """Write the data_element to file fp according to dicom media storage rules. """ # Write element's tag fp.write_tag(data_element.tag) # write into a buffer to avoid seeking back which can be expansive buffer = DicomBytesIO() buffer.is_little_endian = fp.is_little_endian buffer.is_implicit_VR = fp.is_implicit_VR VR = data_element.VR if not fp.is_implicit_VR and len(VR) != 2: msg = ("Cannot write ambiguous VR of '{}' for data element with " "tag {}.\nSet the correct VR before writing, or use an " "implicit VR transfer syntax".format(VR, repr(data_element.tag))) raise ValueError(msg) if data_element.is_raw: # raw data element values can be written as they are buffer.write(data_element.value) is_undefined_length = data_element.length == 0xFFFFFFFF else: if VR not in writers: raise NotImplementedError( "write_data_element: unknown Value Representation " "'{0}'".format(VR)) encodings = encodings or [default_encoding] encodings = convert_encodings(encodings) writer_function, writer_param = writers[VR] is_undefined_length = data_element.is_undefined_length if not data_element.is_empty: if VR in text_VRs or VR in ('PN', 'SQ'): writer_function(buffer, data_element, encodings=encodings) else: # Many numeric types use the same writer but with # numeric format parameter if writer_param is not None: writer_function(buffer, data_element, writer_param) else: writer_function(buffer, data_element) # valid pixel data with undefined length shall contain encapsulated # data, e.g. sequence items - raise ValueError otherwise (see #238) if is_undefined_length and data_element.tag == 0x7fe00010: encap_item = b'\xfe\xff\x00\xe0' if not fp.is_little_endian: # Non-conformant endianness encap_item = b'\xff\xfe\xe0\x00' if not data_element.value.startswith(encap_item): raise ValueError( "(7FE0,0010) Pixel Data has an undefined length indicating " "that it's compressed, but the data isn't encapsulated as " "required. See pydicom.encaps.encapsulate() for more " "information") value_length = buffer.tell() if (not fp.is_implicit_VR and VR not in extra_length_VRs and not is_undefined_length and value_length > 0xffff): # see PS 3.5, section 6.2.2 for handling of this case msg = ('The value for the data element {} exceeds the size ' 'of 64 kByte and cannot be written in an explicit transfer ' 'syntax. The data element VR is changed from "{}" to "UN" ' 'to allow saving the data.'.format(data_element.tag, VR)) warnings.warn(msg) VR = 'UN' # write the VR for explicit transfer syntax if not fp.is_implicit_VR: if not in_py2: fp.write(bytes(VR, default_encoding)) else: fp.write(VR) if VR in extra_length_VRs: fp.write_US(0) # reserved 2 bytes if (not fp.is_implicit_VR and VR not in extra_length_VRs and not is_undefined_length): fp.write_US(value_length) # Explicit VR length field is 2 bytes else: # write the proper length of the data_element in the length slot, # unless is SQ with undefined length. fp.write_UL(0xFFFFFFFF if is_undefined_length else value_length) fp.write(buffer.getvalue()) if is_undefined_length: fp.write_tag(SequenceDelimiterTag) fp.write_UL(0) # 4-byte 'length' of delimiter data item
def write_data_element( fp: DicomIO, elem: Union[DataElement, RawDataElement], encodings: Optional[Union[str, List[str]]] = None) -> None: """Write the data_element to file fp according to dicom media storage rules. """ # Write element's tag fp.write_tag(elem.tag) # write into a buffer to avoid seeking back which can be expansive buffer = DicomBytesIO() buffer.is_little_endian = fp.is_little_endian buffer.is_implicit_VR = fp.is_implicit_VR VR: Optional[str] = elem.VR if not fp.is_implicit_VR and VR and len(VR) != 2: msg = (f"Cannot write ambiguous VR of '{VR}' for data element with " f"tag {repr(elem.tag)}.\nSet the correct VR before " f"writing, or use an implicit VR transfer syntax") raise ValueError(msg) if elem.is_raw: elem = cast(RawDataElement, elem) # raw data element values can be written as they are buffer.write(cast(bytes, elem.value)) is_undefined_length = elem.length == 0xFFFFFFFF else: elem = cast(DataElement, elem) if VR not in writers: raise NotImplementedError( f"write_data_element: unknown Value Representation '{VR}'") encodings = encodings or [default_encoding] encodings = convert_encodings(encodings) fn, param = writers[VR] is_undefined_length = elem.is_undefined_length if not elem.is_empty: if VR in text_VRs or VR in ('PN', 'SQ'): fn(buffer, elem, encodings=encodings) # type: ignore[operator] else: # Many numeric types use the same writer but with # numeric format parameter if param is not None: fn(buffer, elem, param) # type: ignore[operator] else: fn(buffer, elem) # type: ignore[operator] # valid pixel data with undefined length shall contain encapsulated # data, e.g. sequence items - raise ValueError otherwise (see #238) if is_undefined_length and elem.tag == 0x7fe00010: encap_item = b'\xfe\xff\x00\xe0' if not fp.is_little_endian: # Non-conformant endianness encap_item = b'\xff\xfe\xe0\x00' if not cast(bytes, elem.value).startswith(encap_item): raise ValueError( "(7FE0,0010) Pixel Data has an undefined length indicating " "that it's compressed, but the data isn't encapsulated as " "required. See pydicom.encaps.encapsulate() for more " "information") value_length = buffer.tell() if (not fp.is_implicit_VR and VR not in extra_length_VRs and not is_undefined_length and value_length > 0xffff): # see PS 3.5, section 6.2.2 for handling of this case msg = ( f"The value for the data element {elem.tag} exceeds the " f"size of 64 kByte and cannot be written in an explicit transfer " f"syntax. The data element VR is changed from '{VR}' to 'UN' " f"to allow saving the data.") warnings.warn(msg) VR = 'UN' # write the VR for explicit transfer syntax if not fp.is_implicit_VR: VR = cast(str, VR) fp.write(bytes(VR, default_encoding)) if VR in extra_length_VRs: fp.write_US(0) # reserved 2 bytes if (not fp.is_implicit_VR and VR not in extra_length_VRs and not is_undefined_length): fp.write_US(value_length) # Explicit VR length field is 2 bytes else: # write the proper length of the data_element in the length slot, # unless is SQ with undefined length. fp.write_UL(0xFFFFFFFF if is_undefined_length else value_length) fp.write(buffer.getvalue()) if is_undefined_length: fp.write_tag(SequenceDelimiterTag) fp.write_UL(0) # 4-byte 'length' of delimiter data item
def write_file_meta_info(fp, file_meta, enforce_standard=True): """Write the File Meta Information elements in `file_meta` to `fp`. If `enforce_standard` is ``True`` then the file-like `fp` should be positioned past the 128 byte preamble + 4 byte prefix (which should already have been written). **DICOM File Meta Information Group Elements** From the DICOM standard, Part 10, :dcm:`Section 7.1<part10/chapter_7.html#sect_7.1>`, any DICOM file shall contain a 128-byte preamble, a 4-byte DICOM prefix 'DICM' and (at a minimum) the following Type 1 DICOM Elements (from :dcm:`Table 7.1-1<part10/chapter_7.html#table_7.1-1>`): * (0002,0000) *File Meta Information Group Length*, UL, 4 * (0002,0001) *File Meta Information Version*, OB, 2 * (0002,0002) *Media Storage SOP Class UID*, UI, N * (0002,0003) *Media Storage SOP Instance UID*, UI, N * (0002,0010) *Transfer Syntax UID*, UI, N * (0002,0012) *Implementation Class UID*, UI, N If `enforce_standard` is ``True`` then (0002,0000) will be added/updated, (0002,0001) and (0002,0012) will be added if not already present and the other required elements will be checked to see if they exist. If `enforce_standard` is ``False`` then `file_meta` will be written as is after minimal validation checking. The following Type 3/1C Elements may also be present: * (0002,0013) *Implementation Version Name*, SH, N * (0002,0016) *Source Application Entity Title*, AE, N * (0002,0017) *Sending Application Entity Title*, AE, N * (0002,0018) *Receiving Application Entity Title*, AE, N * (0002,0102) *Private Information*, OB, N * (0002,0100) *Private Information Creator UID*, UI, N If `enforce_standard` is ``True`` then (0002,0013) will be added/updated. *Encoding* The encoding of the *File Meta Information* shall be *Explicit VR Little Endian*. Parameters ---------- fp : file-like The file-like to write the File Meta Information to. file_meta : pydicom.dataset.Dataset The File Meta Information elements. enforce_standard : bool If ``False``, then only the *File Meta Information* elements already in `file_meta` will be written to `fp`. If ``True`` (default) then a DICOM Standards conformant File Meta will be written to `fp`. Raises ------ ValueError If `enforce_standard` is ``True`` and any of the required *File Meta Information* elements are missing from `file_meta`, with the exception of (0002,0000), (0002,0001) and (0002,0012). ValueError If any non-Group 2 Elements are present in `file_meta`. """ validate_file_meta(file_meta, enforce_standard) if enforce_standard and 'FileMetaInformationGroupLength' not in file_meta: # Will be updated with the actual length later file_meta.FileMetaInformationGroupLength = 0 # Write the File Meta Information Group elements # first write into a buffer to avoid seeking back, that can be # expansive and is not allowed if writing into a zip file buffer = DicomBytesIO() buffer.is_little_endian = True buffer.is_implicit_VR = False write_dataset(buffer, file_meta) # If FileMetaInformationGroupLength is present it will be the first written # element and we must update its value to the correct length. if 'FileMetaInformationGroupLength' in file_meta: # Update the FileMetaInformationGroupLength value, which is the number # of bytes from the end of the FileMetaInformationGroupLength element # to the end of all the File Meta Information elements. # FileMetaInformationGroupLength has a VR of 'UL' and so has a value # that is 4 bytes fixed. The total length of when encoded as # Explicit VR must therefore be 12 bytes. file_meta.FileMetaInformationGroupLength = buffer.tell() - 12 buffer.seek(0) write_data_element(buffer, file_meta[0x00020000]) fp.write(buffer.getvalue())