def convert_ATvalue( byte_string: bytes, is_little_endian: bool, struct_format: Optional[str] = None ) -> Union[BaseTag, MutableSequence[BaseTag]]: """Return a decoded 'AT' value. Parameters ---------- byte_string : bytes The encoded 'AT' element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str, optional Not used. Returns ------- BaseTag or MultiValue of BaseTag The decoded value(s). """ length = len(byte_string) if length == 4: return convert_tag(byte_string, is_little_endian) # length > 4 if length % 4 != 0: logger.warning("Expected length to be multiple of 4 for VR 'AT', " f"got length {length}") return MultiValue(Tag, [ convert_tag(byte_string, is_little_endian, offset=x) for x in range(0, length, 4) ])
def read_item(fp): """Read and return a single Item in the fragmented data stream""" try: tag = fp.read_tag() except EOFError: # already read delimiter before passing data here, so should just run out return None if tag == SequenceDelimiterTag: # No more items, time for sequence to stop reading length = fp.read_UL() logger.debug("%04x: Sequence Delimiter, length 0x%x", fp.tell() - 8, length) if length != 0: logger.warning( "Expected 0x00000000 after delimiter, found 0x%x, at data position 0x%x", length, fp.tell() - 4) return None if tag != ItemTag: logger.warning("Expected Item with tag %s at data position 0x%x", ItemTag, fp.tell() - 4) length = fp.read_UL() else: length = fp.read_UL() logger.debug("%04x: Item, length 0x%x", fp.tell() - 8, length) if length == 0xFFFFFFFF: raise ValueError( "Encapsulated data fragment had Undefined Length at data position 0x%x" % fp.tell() - 4) item_data = fp.read(length) return item_data
def _TM_from_str(value: str) -> TM: value = value.rstrip() length = len(value) if (length < 2 or length > 16) and length != 0: logger.warning( f"Expected length between 2 and 16, got length {length}") return TM(value)
def _DT_from_str(value: str) -> DT: value = value.rstrip() length = len(value) if length < 4 or length > 26: logger.warning( f"Expected length between 4 and 26, got length {length}") return DT(value)
def read_sequence_item(fp: BinaryIO, is_implicit_VR: bool, is_little_endian: bool, encoding: Union[str, MutableSequence[str]], offset: int = 0) -> Optional[Dataset]: """Read and return a single :class:`~pydicom.sequence.Sequence` item, i.e. a :class:`~pydicom.dataset.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 BaseException: raise IOError( f"No tag to read at file position {fp.tell() + offset:X}") tag = (group, element) if tag == SequenceDelimiterTag: # No more items, time to stop reading logger.debug(f"{fp.tell() - 8 + offset:08x}: End of Sequence") if length != 0: logger.warning( f"Expected 0x00000000 after delimiter, found 0x{length:X}, " f"at position 0x{fp.tell() - 4 + offset:X}") return None if tag != ItemTag: logger.warning( f"Expected sequence item with tag {ItemTag} at file position " f"0x{fp.tell() - 4 + offset:X}") else: logger.debug(f"{fp.tell() - 4 + offset:08x}: {bytes2hex(bytes_read)} " "Found Item tag (start of item)") if length == 0xFFFFFFFF: ds = read_dataset(fp, is_implicit_VR, is_little_endian, bytelength=None, parent_encoding=encoding, at_top_level=False) ds.is_undefined_length_sequence_item = True else: ds = read_dataset(fp, is_implicit_VR, is_little_endian, length, parent_encoding=encoding, at_top_level=False) ds.is_undefined_length_sequence_item = False logger.debug(f"{fp.tell() + offset:08X}: Finished sequence item") ds.seq_item_tell = seq_item_tell return ds
def convert_numbers(byte_string, is_little_endian, struct_format): """Convert `byte_string` to a value, depending on `struct_format`. Given an encoded DICOM Element value, use `struct_format` and the endianness of the data to decode it. Parameters ---------- byte_string : bytes The raw byte data to decode. is_little_endian : bool The encoding of `byte_string`. struct_format : str The type of data encoded in `byte_string`. Returns ------- str If there is no encoded data in `byte_string` then an empty string will be returned. value If `byte_string` encodes a single value then it will be returned. list If `byte_string` encodes multiple values then a list of the decoded values will be returned. """ endianChar = '><'[is_little_endian] # "=" means use 'standard' size, needed on 64-bit systems. bytes_per_value = calcsize("=" + struct_format) length = len(byte_string) if length % bytes_per_value != 0: logger.warning("Expected length to be even multiple of number size") format_string = "%c%u%c" % (endianChar, length // bytes_per_value, struct_format) value = unpack(format_string, byte_string) # if the number is empty, then return the empty # string rather than empty list if len(value) == 0: return '' elif len(value) == 1: return value[0] else: # convert from tuple to a list so can modify if need to return list(value)
def convert_numbers(byte_string, is_little_endian, struct_format): """Convert `byte_string` to a value, depending on `struct_format`. Given an encoded DICOM Element value, use `struct_format` and the endianness of the data to decode it. Parameters ---------- byte_string : bytes The raw byte data to decode. is_little_endian : bool The encoding of `byte_string`. struct_format : str The type of data encoded in `byte_string`. Returns ------- str If there is no encoded data in `byte_string` then an empty string will be returned. value If `byte_string` encodes a single value then it will be returned. list If `byte_string` encodes multiple values then a list of the decoded values will be returned. """ endianChar = '><' [is_little_endian] # "=" means use 'standard' size, needed on 64-bit systems. bytes_per_value = calcsize("=" + struct_format) length = len(byte_string) if length % bytes_per_value != 0: logger.warning("Expected length to be even multiple of number size") format_string = "%c%u%c" % (endianChar, length // bytes_per_value, struct_format) value = unpack(format_string, byte_string) # if the number is empty, then return the empty # string rather than empty list if len(value) == 0: return '' elif len(value) == 1: return value[0] else: # convert from tuple to a list so can modify if need to return list(value)
def read_sequence_item(fp, is_implicit_VR, is_little_endian, encoding, offset=0): """Read and return a single :class:`~pydicom.sequence.Sequence` item, i.e. a :class:`~pydicom.dataset.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 BaseException: 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, at_top_level=False) ds.is_undefined_length_sequence_item = True else: ds = read_dataset(fp, is_implicit_VR, is_little_endian, length, parent_encoding=encoding, at_top_level=False) 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 convert_numbers( byte_string: bytes, is_little_endian: bool, struct_format: str) -> Union[str, int, float, List[Union[int, float]]]: """Return a decoded numerical VR value. Given an encoded DICOM Element value, use `struct_format` and the endianness of the data to decode it. Parameters ---------- byte_string : bytes The encoded numerical VR element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str The format of the numerical data encoded in `byte_string`. Should be a valid format for :func:`struct.unpack()` without the endianness. Returns ------- str If there is no encoded data in `byte_string` then an empty string will be returned. value If `byte_string` encodes a single value then it will be returned. list If `byte_string` encodes multiple values then a list of the decoded values will be returned. """ endianChar = '><'[is_little_endian] # "=" means use 'standard' size, needed on 64-bit systems. bytes_per_value = calcsize("=" + struct_format) length = len(byte_string) if length % bytes_per_value != 0: logger.warning("Expected length to be even multiple of number size") format_string = f"{endianChar}{length // bytes_per_value}{struct_format}" value: Union[Tuple[int, ...], Tuple[float, ...]] = (unpack(format_string, byte_string)) # if the number is empty, then return the empty # string rather than empty list if len(value) == 0: return '' if len(value) == 1: return value[0] # convert from tuple to a list so can modify if need to return list(value)
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, value_tell_offset=offset ) ds.is_undefined_length_sequence_item = True else: ds = read_dataset( fp, is_implicit_VR, is_little_endian, length, parent_encoding=encoding, value_tell_offset=offset ) 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_item(fp): """Read and return a single Item in the fragmented data stream""" try: tag = fp.read_tag() except EOFError: # already read delimiter before passing data here, so should just run out return None if tag == SequenceDelimiterTag: # No more items, time for sequence to stop reading length = fp.read_UL() logger.debug("%04x: Sequence Delimiter, length 0x%x", fp.tell() - 8, length) if length != 0: logger.warning("Expected 0x00000000 after delimiter, found 0x%x, at data position 0x%x", length, fp.tell() - 4) return None if tag != ItemTag: logger.warning("Expected Item with tag %s at data position 0x%x", ItemTag, fp.tell() - 4) length = fp.read_UL() else: length = fp.read_UL() logger.debug("%04x: Item, length 0x%x", fp.tell() - 8, length) if length == 0xFFFFFFFF: raise ValueError("Encapsulated data fragment had Undefined Length at data position 0x%x" % fp.tell() - 4) item_data = fp.read(length) return item_data
def from_json(cls, dataset_class, tag, vr, value, value_key, bulk_data_uri_handler=None, encodings=None): """Creates a DataElement from JSON. Parameters ---------- dataset_class: Dataset derived class class used to create sequence items tag: pydicom.tag.Tag data element tag vr: str data element value representation value: list data element value(s) value_key: Union[str, None] key of the data element that contains the value (options: ``{"Value", "InlineBinary", "BulkDataURI"}``) bulk_data_uri_handler: Union[Callable, None] callable that accepts the "BulkDataURI" of the JSON representation of a data element and returns the actual value of that data element (retrieved via DICOMweb WADO-RS) Returns ------- pydicom.dataelem.DataElement """ # TODO: test wado-rs retrieve wrapper try: vm = dictionary_VM(tag) except KeyError: # Private tag vm = str(len(value)) if value_key == 'Value': if not (isinstance(value, list)): fmt = '"{}" of data element "{}" must be a list.' raise TypeError(fmt.format(value_key, tag)) elif value_key in {'InlineBinary', 'BulkDataURI'}: if isinstance(value, list): fmt = '"{}" of data element "{}" must be a {}.' expected_type = ('string' if value_key == 'BulkDataURI' else 'bytes-like object') raise TypeError(fmt.format(value_key, tag, expected_type)) if vr == 'SQ': elem_value = [] for value_item in value: ds = dataset_class() if value_item: for key, val in value_item.items(): if 'vr' not in val: fmt = 'Data element "{}" must have key "vr".' raise KeyError(fmt.format(tag)) unique_value_keys = tuple( set(val.keys()) & set(jsonrep.JSON_VALUE_KEYS)) if len(unique_value_keys) == 0: logger.debug( 'data element has neither key "{}".'.format( '" nor "'.join(jsonrep.JSON_VALUE_KEYS))) elem = DataElement(tag=tag, value='', VR=vr) else: value_key = unique_value_keys[0] elem = cls.from_json(dataset_class, key, val['vr'], val[value_key], value_key) ds.add(elem) elem_value.append(ds) elif vr == 'PN': # Special case, see DICOM Part 18 Annex F2.2 elem_value = [] for v in value: if not isinstance(v, dict): # Some DICOMweb services get this wrong, so we # workaround the issue and warn the user # rather than raising an error. logger.error( 'value of data element "{}" with VR Person Name (PN) ' 'is not formatted correctly'.format(tag)) elem_value.append(v) else: elem_value.extend(list(v.values())) if vm == '1': try: elem_value = elem_value[0] except IndexError: elem_value = '' else: if vm == '1': if value_key == 'InlineBinary': elem_value = base64.b64decode(value) elif value_key == 'BulkDataURI': if bulk_data_uri_handler is None: logger.warning( 'no bulk data URI handler provided for retrieval ' 'of value of data element "{}"'.format(tag)) elem_value = b'' else: elem_value = bulk_data_uri_handler(value) else: if value: elem_value = value[0] else: elem_value = value else: elem_value = value if elem_value is None: logger.warning('missing value for data element "{}"'.format(tag)) elem_value = '' elem_value = jsonrep.convert_to_python_number(elem_value, vr) try: if compat.in_py2 and vr == "PN": elem_value = PersonNameUnicode(elem_value, 'UTF8') return DataElement(tag=tag, value=elem_value, VR=vr) except Exception: raise ValueError( 'Data element "{}" could not be loaded from JSON: {}'.format( tag, elem_value))