def _read_file_meta_info(fp): """Return the file meta information. fp must be set after the 128 byte preamble and 'DICM' marker """ # File meta info is always LittleEndian, Explicit VR. After will change these # to the transfer syntax values set in the meta info # Get group length data element, whose value is the length of the meta_info fp_save = fp.tell() # in case need to rewind debugging = dicom.debugging if debugging: logger.debug("Try to read group length info...") bytes_read = fp.read(8) group, elem, VR, length = unpack("<HH2sH", bytes_read) if debugging: debug_msg = "%08x: %s" % (fp.tell()-8, bytes2hex(bytes_read)) if VR in ('OB','OW','OF','SQ','UN', 'UT'): bytes_read = fp.read(4) length = unpack("<L", bytes_read)[0] if debugging: debug_msg += " " + bytes2hex(bytes_read) if debugging: debug_msg = "%-47s (%04x, %04x) %2s Length: %d" % (debug_msg, group, elem, VR, length) logger.debug(debug_msg) # If required meta group length exists, store it, and then read until not group 2 if group == 2 and elem == 0: bytes_read = fp.read(length) if debugging: logger.debug("%08x: %s" % (fp.tell()-length, bytes2hex(bytes_read))) group_length = unpack("<L", bytes_read)[0] expected_ds_start = fp.tell() + group_length if debugging: msg = "value (group length) = %d" % group_length msg += " regular dataset should start at %08x" % (expected_ds_start) logger.debug(" "*10 + msg) else: expected_ds_start = None if debugging: logger.debug(" "*10 + "(0002,0000) Group length not found.") # Changed in pydicom 0.9.7 -- don't trust the group length, just read # until no longer group 2 data elements. But check the length and # give a warning if group 2 ends at different location. # Rewind to read the first data element as part of the file_meta dataset if debugging: logger.debug("Rewinding and reading whole dataset including this first data element") fp.seek(fp_save) file_meta = read_dataset(fp, is_implicit_VR=False, is_little_endian=True, stop_when=not_group2) fp_now = fp.tell() if expected_ds_start and fp_now != expected_ds_start: logger.info("*** Group length for file meta dataset did not match end of group 2 data ***") else: if debugging: logger.debug("--- End of file meta data found as expected ---------") return file_meta
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.parent.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 read_sequence_item(fp, is_implicit_VR, is_little_endian): """Read and return a single sequence item, i.e. a Dataset""" if is_little_endian: tag_length_format = "<HHL" else: tag_length_format = ">HHL" try: bytes_read = fp.read(8) group, element, length = unpack(tag_length_format, bytes_read) except: raise IOError("No tag to read at file position " "{0:05x}".format(fp.tell())) tag = (group, element) if tag == SequenceDelimiterTag: # No more items, time to stop reading data_element = DataElement(tag, None, None, fp.tell()-4) logger.debug("{0:08x}: {1}".format(fp.tell()-8, "End of Sequence")) if length != 0: logger.warning("Expected 0x00000000 after delimiter, found 0x%x," " at position 0x%x" % (length, fp.tell()-4)) return None if tag != ItemTag: logger.warning("Expected sequence item with tag %s at file position " "0x%x" % (ItemTag, fp.tell()-4)) else: logger.debug("{0:08x}: {1} Found Item tag (start of item)".format( fp.tell()-4, bytes2hex(bytes_read))) is_undefined_length = False if length == 0xFFFFFFFFL: ds = read_dataset(fp, is_implicit_VR, is_little_endian, bytelength=None) ds.is_undefined_length_sequence_item = True else: ds = read_dataset(fp, is_implicit_VR, is_little_endian, length) logger.debug("%08x: Finished sequence item" % fp.tell()) return ds
def read_sequence_item(fp, is_implicit_VR, is_little_endian): """Read and return a single sequence item, i.e. a Dataset""" if is_little_endian: tag_length_format = "<HHL" else: tag_length_format = ">HHL" try: bytes_read = fp.read(8) group, element, length = unpack(tag_length_format, bytes_read) except: raise IOError, "No tag to read at file position %05x" % fp.tell() tag = (group, element) if tag == SequenceDelimiterTag: # No more items, time to stop reading data_element = DataElement(tag, None, None, fp.tell()-4) logger.debug("%08x: %s" % (fp.tell()-8, "End of Sequence")) if length != 0: logger.warning("Expected 0x00000000 after delimiter, found 0x%x, at position 0x%x" % (length, fp.tell()-4)) return None if tag != ItemTag: logger.warning("Expected sequence item with tag %s at file position 0x%x" % (ItemTag, fp.tell()-4)) else: logger.debug("%08x: %s Found Item tag (start of item)" % (fp.tell()-4, bytes2hex(bytes_read))) is_undefined_length = False if length == 0xFFFFFFFFL: ds = read_dataset(fp, is_implicit_VR, is_little_endian, bytelength=None) ds.is_undefined_length_sequence_item = True else: ds = read_dataset(fp, is_implicit_VR, is_little_endian, length) logger.debug("%08x: Finished sequence item" % fp.tell()) return ds
def read_preamble(fp, force): """Read and return the DICOM preamble and read past the 'DICM' marker. If 'DICM' does not exist, assume no preamble, return None, and rewind file to the beginning.. """ logger.debug("Reading preamble...") preamble = fp.read(0x80) if dicom.debugging: sample_bytes = bytes2hex(preamble[:8]) + "..." + bytes2hex(preamble[-8:]) logger.debug("%08x: %s" % (fp.tell()-0x80, sample_bytes)) magic = fp.read(4) if magic != "DICM": if force: logger.info("File is not a standard DICOM file; 'DICM' header is missing. Assuming no header and continuing") preamble = None fp.seek(0) else: raise InvalidDicomError("File is missing 'DICM' marker. Use force=True to force reading") else: logger.debug("%08x: 'DICM' marker found" % (fp.tell()-4)) return preamble
def read_sequence_item(fp, is_implicit_VR, is_little_endian, encoding, offset=0): """Read and return a single sequence item, i.e. a Dataset""" seq_item_tell = fp.tell() + offset if is_little_endian: tag_length_format = "<HHL" else: tag_length_format = ">HHL" try: bytes_read = fp.read(8) group, element, length = unpack(tag_length_format, bytes_read) except: raise IOError("No tag to read at file position " "{0:05x}".format(fp.tell() + offset)) tag = (group, element) if tag == SequenceDelimiterTag: # No more items, time to stop reading logger.debug("{0:08x}: {1}".format(fp.tell() - 8 + offset, "End of Sequence")) if length != 0: logger.warning("Expected 0x00000000 after delimiter, found 0x%x, " "at position 0x%x" % (length, fp.tell() - 4 + offset)) return None if tag != ItemTag: logger.warning("Expected sequence item with tag %s at file position " "0x%x" % (ItemTag, fp.tell() - 4 + offset)) else: logger.debug("{0:08x}: {1} Found Item tag (start of item)".format( fp.tell() - 4 + offset, bytes2hex(bytes_read))) if length == 0xFFFFFFFF: ds = read_dataset(fp, is_implicit_VR, is_little_endian, bytelength=None, parent_encoding=encoding) ds.is_undefined_length_sequence_item = True else: ds = read_dataset(fp, is_implicit_VR, is_little_endian, length, parent_encoding=encoding) ds.is_undefined_length_sequence_item = False logger.debug("%08x: Finished sequence item" % (fp.tell() + offset, )) ds.seq_item_tell = seq_item_tell return ds
def _read_file_meta_info(fp): """Return the file meta information. fp must be set after the 128 byte preamble and 'DICM' marker """ # File meta info always LittleEndian, Explicit VR. After will change these # to the transfer syntax values set in the meta info # Get group length data element, whose value is the length of the meta_info fp_save = fp.tell() # in case need to rewind debugging = dicom.debugging if debugging: logger.debug("Try to read group length info...") bytes_read = fp.read(8) group, elem, VR, length = unpack("<HH2sH", bytes_read) if debugging: debug_msg = "{0:08x}: {1}".format(fp.tell() - 8, bytes2hex(bytes_read)) if in_py3: VR = VR.decode(default_encoding) if VR in extra_length_VRs: bytes_read = fp.read(4) length = unpack("<L", bytes_read)[0] if debugging: debug_msg += " " + bytes2hex(bytes_read) if debugging: debug_msg = "{0:<47s} ({1:04x}, {2:04x}) {3:2s} Length: {4:d}".format( debug_msg, group, elem, VR, length) logger.debug(debug_msg) # Store meta group length if it exists, then read until not group 2 if group == 2 and elem == 0: bytes_read = fp.read(length) if debugging: logger.debug("{0:08x}: {1}".format(fp.tell() - length, bytes2hex(bytes_read))) group_length = unpack("<L", bytes_read)[0] expected_ds_start = fp.tell() + group_length if debugging: msg = "value (group length) = {0:d}".format(group_length) msg += " regular dataset should start at {0:08x}".format( expected_ds_start) logger.debug(" " * 10 + msg) else: expected_ds_start = None if debugging: logger.debug(" " * 10 + "(0002,0000) Group length not found.") # Changed in pydicom 0.9.7 -- don't trust the group length, just read # until no longer group 2 data elements. But check the length and # give a warning if group 2 ends at different location. # Rewind to read the first data element as part of the file_meta dataset if debugging: logger.debug("Rewinding and reading whole dataset " "including this first data element") fp.seek(fp_save) file_meta = read_dataset(fp, is_implicit_VR=False, is_little_endian=True, stop_when=not_group2) fp_now = fp.tell() if expected_ds_start and fp_now != expected_ds_start: logger.info("*** Group length for file meta dataset " "did not match end of group 2 data ***") else: if debugging: logger.debug("--- End of file meta data found " "as expected ---------") return file_meta
def data_element_generator(fp, is_implicit_VR, is_little_endian, stop_when=None, defer_size=None, encoding=default_encoding): """Create a generator to efficiently return the raw data elements Returns (VR, length, raw_bytes, value_tell, is_little_endian), where: VR -- None if implicit VR, otherwise the VR read from the file length -- the length as in the DICOM data element (could be DICOM "undefined length" 0xffffffffL), value_bytes -- the raw bytes from the DICOM file (not parsed into python types) is_little_endian -- True if transfer syntax is little endian; else False """ # Summary of DICOM standard PS3.5-2008 chapter 7: # If Implicit VR, data element is: # tag, 4-byte length, value. # The 4-byte length can be FFFFFFFF (undefined length)* # If Explicit VR: # if OB, OW, OF, SQ, UN, or UT: # tag, VR, 2-bytes reserved (both zero), 4-byte length, value # For all but UT, the length can be FFFFFFFF (undefined length)* # else: (any other VR) # tag, VR, (2 byte length), value # * for undefined length, a Sequence Delimitation Item marks the end # of the Value Field. # Note, except for the special_VRs, both impl and expl VR use 8 bytes; # the special VRs follow the 8 bytes with a 4-byte length # With a generator, state is stored, so we can break down # into the individual cases, and not have to check them again for each # data element if is_little_endian: endian_chr = "<" else: endian_chr = ">" if is_implicit_VR: element_struct = Struct(endian_chr + "HHL") else: # Explicit VR # tag, VR, 2-byte length (or 0 if special VRs) element_struct = Struct(endian_chr + "HH2sH") extra_length_struct = Struct(endian_chr + "L") # for special VRs extra_length_unpack = extra_length_struct.unpack # for lookup speed # Make local variables so have faster lookup fp_read = fp.read fp_tell = fp.tell logger_debug = logger.debug debugging = dicom.debugging element_struct_unpack = element_struct.unpack while True: # Read tag, VR, length, get ready to read value bytes_read = fp_read(8) if len(bytes_read) < 8: raise StopIteration # at end of file if debugging: debug_msg = "{0:08x}: {1}".format(fp.tell() - 8, bytes2hex(bytes_read)) if is_implicit_VR: # must reset VR each time; could have set last iteration (e.g. SQ) VR = None group, elem, length = element_struct_unpack(bytes_read) else: # explicit VR group, elem, VR, length = element_struct_unpack(bytes_read) if in_py3: VR = VR.decode(default_encoding) if VR in extra_length_VRs: bytes_read = fp_read(4) length = extra_length_unpack(bytes_read)[0] if debugging: debug_msg += " " + bytes2hex(bytes_read) if debugging: debug_msg = "%-47s (%04x, %04x)" % (debug_msg, group, elem) if not is_implicit_VR: debug_msg += " %s " % VR if length != 0xFFFFFFFFL: debug_msg += "Length: %d" % length else: debug_msg += "Length: Undefined length (FFFFFFFF)" logger_debug(debug_msg) # Positioned to read the value, but may not want to -- check stop_when value_tell = fp_tell() tag = TupleTag((group, elem)) if stop_when is not None: # XXX VR may be None here!! Should stop_when just take tag? if stop_when(tag, VR, length): if debugging: logger_debug("Reading ended by stop_when callback. " "Rewinding to start of data element.") rewind_length = 8 if not is_implicit_VR and VR in extra_length_VRs: rewind_length += 4 fp.seek(value_tell - rewind_length) raise StopIteration # Reading the value # First case (most common): reading a value with a defined length if length != 0xFFFFFFFFL: if defer_size is not None and length > defer_size: # Flag as deferred by setting value to None, and skip bytes value = None logger_debug("Defer size exceeded." "Skipping forward to next data element.") fp.seek(fp_tell() + length) else: value = fp_read(length) if debugging: dotdot = " " if length > 12: dotdot = "..." logger_debug("%08x: %-34s %s %r %s" % (value_tell, bytes2hex(value[:12]), dotdot, value[:12], dotdot)) # If the tag is (0008,0005) Specific Character Set, then store it if tag == (0x08, 0x05): from dicom.values import convert_string encoding = convert_string(value, is_little_endian, encoding=default_encoding) # Store the encoding value in the generator for use with future elements (SQs) encoding = convert_encodings(encoding) yield RawDataElement(tag, VR, length, value, value_tell, is_implicit_VR, is_little_endian) # Second case: undefined length - must seek to delimiter, # unless is SQ type, in which case is easier to parse it, because # undefined length SQs and items of undefined lengths can be nested # and it would be error-prone to read to the correct outer delimiter else: # Try to look up type to see if is a SQ # if private tag, won't be able to look it up in dictionary, # in which case just ignore it and read the bytes unless it is # identified as a Sequence if VR is None: try: VR = dictionaryVR(tag) except KeyError: # Look ahead to see if it consists of items and is thus a SQ next_tag = TupleTag(unpack(endian_chr + "HH", fp_read(4))) # Rewind the file fp.seek(fp_tell() - 4) if next_tag == ItemTag: VR = 'SQ' if VR == 'SQ': if debugging: msg = "{0:08x}: Reading/parsing undefined length sequence" logger_debug(msg.format(fp_tell())) seq = read_sequence(fp, is_implicit_VR, is_little_endian, length, encoding) yield DataElement(tag, VR, seq, value_tell, is_undefined_length=True) else: delimiter = SequenceDelimiterTag if debugging: logger_debug("Reading undefined length data element") value = read_undefined_length_value(fp, is_little_endian, delimiter, defer_size) # If the tag is (0008,0005) Specific Character Set, then store it if tag == (0x08, 0x05): from dicom.values import convert_string encoding = convert_string(value, is_little_endian, encoding=default_encoding) # Store the encoding value in the generator for use with future elements (SQs) encoding = convert_encodings(encoding) yield RawDataElement(tag, VR, length, value, value_tell, is_implicit_VR, is_little_endian)
def data_element_generator(fp, is_implicit_VR, is_little_endian, stop_when=None, defer_size=None): """Create a generator to efficiently return the raw data elements Specifically, returns (VR, length, raw_bytes, value_tell, is_little_endian), where: VR -- None if implicit VR, otherwise the VR read from the file length -- the length as in the DICOM data element (could be DICOM "undefined length" 0xffffffffL), value_bytes -- the raw bytes from the DICOM file (not parsed into python types) is_little_endian -- True if transfer syntax is little endian; else False """ # Summary of DICOM standard PS3.5-2008 chapter 7: # If Implicit VR, data element is: # tag, 4-byte length, value. # The 4-byte length can be FFFFFFFF (undefined length)* # If Explicit VR: # if OB, OW, OF, SQ, UN, or UT: # tag, VR, 2-bytes reserved (both zero), 4-byte length, value # For all but UT, the length can be FFFFFFFF (undefined length)* # else: (any other VR) # tag, VR, (2 byte length), value # * for undefined length, a Sequence Delimitation Item marks the end # of the Value Field. # Note, except for the special_VRs, both impl and expl VR use 8 bytes; # the special VRs follow the 8 bytes with a 4-byte length # With a generator, state is stored, so we can break down # into the individual cases, and not have to check them again for each # data element # Make local variables so have faster lookup fp_read = fp.read fp_tell = fp.tell logger_debug = logger.debug debugging = dicom.debugging if is_little_endian: endian_chr = "<" else: endian_chr = ">" if is_implicit_VR: unpack_format = endian_chr + "HHL" # XXX in python >=2.5, can do struct.Struct to save time else: # Explicit VR unpack_format = endian_chr + "HH2sH" # tag, VR, 2-byte length (or 0 if special VRs) extra_length_format = endian_chr + "L" # for special VRs while True: # Read tag, VR, length, get ready to read value bytes_read = fp_read(8) if len(bytes_read) < 8: raise StopIteration # at end of file if debugging: debug_msg = "%08x: %s" % (fp.tell()-8, bytes2hex(bytes_read)) if is_implicit_VR: VR = None # must reset each time -- may have looked up on last iteration (e.g. SQ) group, elem, length = unpack(unpack_format, bytes_read) else: # explicit VR group, elem, VR, length = unpack(unpack_format, bytes_read) if VR in ('OB','OW','OF','SQ','UN', 'UT'): bytes_read = fp_read(4) length = unpack(extra_length_format, bytes_read)[0] if debugging: debug_msg += " " + bytes2hex(bytes_read) if debugging: debug_msg = "%-47s (%04x, %04x)" % (debug_msg, group, elem) if not is_implicit_VR: debug_msg += " %s " % VR if length != 0xFFFFFFFFL: debug_msg += "Length: %d" % length else: debug_msg += "Length: Undefined length (FFFFFFFF)" logger_debug(debug_msg) # Now are positioned to read the value, but may not want to -- check stop_when value_tell = fp_tell() # logger.debug("%08x: start of value of length %d" % (value_tell, length)) tag = TupleTag((group, elem)) if stop_when is not None: if stop_when(tag, VR, length): # XXX VR may be None here!! Should stop_when just take tag? if debugging: logger_debug("Reading ended by stop_when callback. Rewinding to start of data element.") rewind_length = 8 if not is_implicit_VR and VR in ('OB','OW','OF','SQ','UN', 'UT'): rewind_length += 4 fp.seek(value_tell-rewind_length) raise StopIteration # Reading the value # First case (most common): reading a value with a defined length if length != 0xFFFFFFFFL: if defer_size is not None and length > defer_size: # Flag as deferred read by setting value to None, and skip bytes value = None logger_debug("Defer size exceeded. Skipping forward to next data element.") fp.seek(fp_tell()+length) else: value = fp_read(length) if debugging: dotdot = " " if length > 12: dotdot = "..." logger_debug("%08x: %-34s %s %r %s" % (value_tell, bytes2hex(value[:12]), dotdot, value[:12], dotdot)) yield RawDataElement(tag, VR, length, value, value_tell, is_implicit_VR, is_little_endian) # Second case: undefined length - must seek to delimiter, # ... unless is SQ type, in which case is easier to parse it, because # undefined length SQs and items of undefined lengths can be nested # and it would be error-prone to read to the correct outer delimiter else: # Try to look up type to see if is a SQ # if private tag, won't be able to look it up in dictionary, # in which case just ignore it and read the bytes if VR is None: try: VR = dictionaryVR(tag) except KeyError: pass if VR == 'SQ': if debugging: logger_debug("%08x: Reading and parsing undefined length sequence" % fp_tell()) seq = read_sequence(fp, is_implicit_VR, is_little_endian, length) yield DataElement(tag, VR, seq, value_tell, is_undefined_length=True) else: delimiter = SequenceDelimiterTag if debugging: logger_debug("Reading undefined length data element") value = read_undefined_length_value(fp, is_little_endian, delimiter, defer_size) yield RawDataElement(tag, VR, length, value, value_tell, is_implicit_VR, is_little_endian)