def test_charset_patient_names(self, filename, patient_name): """Test patient names are correctly decoded and encoded.""" # check that patient names are correctly read file_path = get_charset_files(filename + '.dcm')[0] ds = dcmread(file_path) ds.decode() assert patient_name == ds.PatientName # check that patient names are correctly written back fp = DicomBytesIO() fp.is_implicit_VR = False fp.is_little_endian = True ds.save_as(fp, write_like_original=False) fp.seek(0) ds = dcmread(fp) assert patient_name == ds.PatientName # check that patient names are correctly written back # without original byte string (PersonName3 only) if hasattr(ds.PatientName, 'original_string'): ds.PatientName.original_string = None fp = DicomBytesIO() fp.is_implicit_VR = False fp.is_little_endian = True ds.save_as(fp, write_like_original=False) fp.seek(0) ds = dcmread(fp) assert patient_name == ds.PatientName
def test_seq_item_looks_like_explicit_VR(self): # For issue 999. # Set up an implicit VR dataset with a "normal" group 8 tag, # followed by a sequence with an item (dataset) having # a data element length that looks like a potential valid VR ds = Dataset() ds.file_meta = Dataset() ds.file_meta.MediaStorageSOPClassUID = "1.1.1" ds.file_meta.MediaStorageSOPInstanceUID = "2.2.2" ds.is_implicit_VR = True ds.is_little_endian = True ds.SOPClassUID = '9.9.9' # First item group 8 in top-level dataset seq = Sequence() seq_ds = Dataset() seq_ds.BadPixelImage = b"\3" * 0x5244 # length looks like "DR" seq.append(seq_ds) ds.ReferencedImageSequence = seq dbio = DicomBytesIO() ds.save_as(dbio, write_like_original=False) # Now read the constructed dataset back in # In original issue, shows warning that has detected what appears # to be Explicit VR, then throws NotImplemented for the unknown VR dbio.seek(0) ds = dcmread(dbio) ds.remove_private_tags() # forces it to actually parse SQ
def test_japanese_multi_byte_personname(self): """Test japanese person name which has multi byte strings are correctly encoded.""" file_path = get_charset_files('chrH32.dcm')[0] ds = dcmread(file_path) ds.decode() if hasattr(ds.PatientName, 'original_string'): original_string = ds.PatientName.original_string ds.PatientName.original_string = None fp = DicomBytesIO() fp.is_implicit_VR = False fp.is_little_endian = True ds.save_as(fp, write_like_original=False) fp.seek(0) ds_out = dcmread(fp) assert original_string == ds_out.PatientName.original_string japanese_pn = PersonName(u"Mori^Ogai=森^鷗外=もり^おうがい") pyencs = pydicom.charset.convert_encodings( ["ISO 2022 IR 6", "ISO 2022 IR 87", "ISO 2022 IR 159"]) actual_encoded = bytes(japanese_pn.encode(pyencs)) expect_encoded = ( b"\x4d\x6f\x72\x69\x5e\x4f\x67\x61\x69\x3d\x1b\x24\x42\x3f" b"\x39\x1b\x28\x42\x5e\x1b\x24\x28\x44\x6c\x3f\x1b\x24\x42" b"\x33\x30\x1b\x28\x42\x3d\x1b\x24\x42\x24\x62\x24\x6a\x1b" b"\x28\x42\x5e\x1b\x24\x42\x24\x2a\x24\x26\x24\x2c\x24\x24" b"\x1b\x28\x42") assert expect_encoded == actual_encoded
def test_changed_character_set(self): # Regression test for #629 multiPN_name = get_charset_files("chrFrenMulti.dcm")[0] ds = dcmread(multiPN_name) # is Latin-1 ds.SpecificCharacterSet = 'ISO_IR 192' from pydicom.filebase import DicomBytesIO fp = DicomBytesIO() ds.save_as(fp, write_like_original=False) fp.seek(0) ds_out = dcmread(fp) # we expect UTF-8 encoding here assert b'Buc^J\xc3\xa9r\xc3\xb4me' == ds_out.get_item(0x00100010).value
def test_read_file_does_not_raise(self): """Test that reading from DicomBytesIO does not raise on EOF. Regression test for #358.""" ds = dcmread(mr_name) fp = DicomBytesIO() ds.save_as(fp) fp.seek(0) de_gen = data_element_generator(fp, False, True) try: while True: next(de_gen) except StopIteration: pass except EOFError: self.fail('Unexpected EOFError raised')
def test_dcmread_does_not_raise(self): """Test that reading from DicomBytesIO does not raise on EOF. Regression test for #358.""" ds = dcmread(mr_name) fp = DicomBytesIO() ds.save_as(fp, write_like_original=True) fp.seek(0) de_gen = data_element_generator(fp, False, True) try: while True: next(de_gen) except StopIteration: pass except EOFError: self.fail('Unexpected EOFError raised')
def test_japanese_multi_byte_personname(self): """Test japanese person name which has multi byte strings are correctly encoded.""" file_path = get_charset_files('chrH32.dcm')[0] ds = dcmread(file_path) ds.decode() if hasattr(ds.PatientName, 'original_string'): original_string = ds.PatientName.original_string ds.PatientName.original_string = None fp = DicomBytesIO() fp.is_implicit_VR = False fp.is_little_endian = True ds.save_as(fp, write_like_original=False) fp.seek(0) ds_out = dcmread(fp) assert original_string == ds_out.PatientName.original_string
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())
class TestWriteFileMetaInfo(unittest.TestCase): """Unit tests for _write_file_meta_info.""" def setUp(self): """Create an empty file-like for use in testing.""" self.fp = DicomBytesIO() def test_bad_elements(self): """Test that non-group 2 elements aren't written to the file meta.""" meta = Dataset() meta.PatientID = '12345678' meta.MediaStorageSOPClassUID = '1.1' meta.MediaStorageSOPInstanceUID = '1.2' meta.TransferSyntaxUID = '1.3' meta.ImplementationClassUID = '1.4' self.assertRaises(ValueError, _write_file_meta_info, self.fp, meta) def test_missing_elements(self): """Test that missing required elements raises ValueError.""" meta = Dataset() self.assertRaises(ValueError, _write_file_meta_info, self.fp, meta) meta.MediaStorageSOPClassUID = '1.1' self.assertRaises(ValueError, _write_file_meta_info, self.fp, meta) meta.MediaStorageSOPInstanceUID = '1.2' self.assertRaises(ValueError, _write_file_meta_info, self.fp, meta) meta.TransferSyntaxUID = '1.3' self.assertRaises(ValueError, _write_file_meta_info, self.fp, meta) meta.ImplementationClassUID = '1.4' _write_file_meta_info(self.fp, meta) def test_prefix(self): """Test that the 'DICM' prefix is present.""" meta = Dataset() meta.MediaStorageSOPClassUID = '1.1' meta.MediaStorageSOPInstanceUID = '1.2' meta.TransferSyntaxUID = '1.3' meta.ImplementationClassUID = '1.4' _write_file_meta_info(self.fp, meta) self.fp.seek(0) prefix = self.fp.read(4) self.assertEqual(prefix, b'DICM') def test_group_length(self): """Test that the value for FileMetaInformationGroupLength is OK.""" meta = Dataset() meta.MediaStorageSOPClassUID = '1.1' meta.MediaStorageSOPInstanceUID = '1.2' meta.TransferSyntaxUID = '1.3' meta.ImplementationClassUID = '1.4' _write_file_meta_info(self.fp, meta) # 78 in total, - 4 for prefix, - 12 for group length = 62 self.fp.seek(12) self.assertEqual(self.fp.read(4), b'\x3E\x00\x00\x00') def test_group_length_updated(self): """Test that FileMetaInformationGroupLength gets updated if present.""" meta = Dataset() meta.FileMetaInformationGroupLength = 100 # Actual length meta.MediaStorageSOPClassUID = '1.1' meta.MediaStorageSOPInstanceUID = '1.2' meta.TransferSyntaxUID = '1.3' meta.ImplementationClassUID = '1.4' _write_file_meta_info(self.fp, meta) self.fp.seek(12) self.assertEqual(self.fp.read(4), b'\x3E\x00\x00\x00') # Check original file meta is unchanged/updated self.assertEqual(meta.FileMetaInformationGroupLength, 62) self.assertEqual(meta.FileMetaInformationVersion, b'\x00\x01') self.assertEqual(meta.MediaStorageSOPClassUID, '1.1') self.assertEqual(meta.MediaStorageSOPInstanceUID, '1.2') self.assertEqual(meta.TransferSyntaxUID, '1.3') self.assertEqual(meta.ImplementationClassUID, '1.4') def test_version(self): """Test that the value for FileMetaInformationVersion is OK.""" meta = Dataset() meta.MediaStorageSOPClassUID = '1.1' meta.MediaStorageSOPInstanceUID = '1.2' meta.TransferSyntaxUID = '1.3' meta.ImplementationClassUID = '1.4' _write_file_meta_info(self.fp, meta) self.fp.seek(16 + 12) self.assertEqual(self.fp.read(2), b'\x00\x01') def test_filelike_position(self): """Test that the file-like's ending position is OK.""" # 4 bytes prefix # 8 + 4 bytes FileMetaInformationGroupLength # 12 + 2 bytes FileMetaInformationVersion # 8 + 4 bytes MediaStorageSOPClassUID # 8 + 4 bytes MediaStorageSOPInstanceUID # 8 + 4 bytes TransferSyntaxUID # 8 + 4 bytes ImplementationClassUID # 78 bytes total meta = Dataset() meta.MediaStorageSOPClassUID = '1.1' meta.MediaStorageSOPInstanceUID = '1.2' meta.TransferSyntaxUID = '1.3' meta.ImplementationClassUID = '1.4' _write_file_meta_info(self.fp, meta) self.assertEqual(self.fp.tell(), 78) # 8 + 6 bytes ImplementationClassUID # 80 bytes total, group length 64 self.fp.seek(0) meta.ImplementationClassUID = '1.4.1' _write_file_meta_info(self.fp, meta) # Check File Meta length self.assertEqual(self.fp.tell(), 80) # Check Group Length self.fp.seek(12) self.assertEqual(self.fp.read(4), b'\x40\x00\x00\x00') _write_file_meta_info(self.fp, meta)
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())