def to_json(self, bulk_data_element_handler, bulk_data_threshold, dump_handler): """Converts a DataElement to JSON representation. Parameters ---------- bulk_data_element_handler: Union[Callable, None] callable that accepts a bulk data element and returns the "BulkDataURI" for retrieving the value of the data element via DICOMweb WADO-RS bulk_data_threshold: int size of base64 encoded data element above which a value will be provided in form of a "BulkDataURI" rather than "InlineBinary" Returns ------- dict mapping representing a JSON encoded data element Raises ------ TypeError when size of encoded data element exceeds `bulk_data_threshold` but `bulk_data_element_handler` is ``None`` and hence not callable """ # TODO: Determine whether more VRs need to be converted to strings _VRs_TO_QUOTE = [ 'AT', ] json_element = { 'vr': self.VR, } if self.VR in jsonrep.BINARY_VR_VALUES: if self.value is not None: binary_value = self.value encoded_value = base64.b64encode(binary_value).decode('utf-8') if len(encoded_value) > bulk_data_threshold: if bulk_data_element_handler is None: raise TypeError( 'No bulk data element handler provided to ' 'generate URL for value of data element "{}".'. format(self.name)) json_element['BulkDataURI'] = bulk_data_element_handler( self) else: logger.info('encode bulk data element "{}" inline'.format( self.name)) json_element['InlineBinary'] = encoded_value elif self.VR == 'SQ': # recursive call to co-routine to format sequence contents value = [ json.loads( e.to_json( bulk_data_element_handler=bulk_data_element_handler, bulk_data_threshold=bulk_data_threshold, dump_handler=dump_handler)) for e in self ] json_element['Value'] = value elif self.VR == 'PN': elem_value = self.value if elem_value is not None: if compat.in_py2: elem_value = PersonNameUnicode(elem_value, 'UTF8') if len(elem_value.components) > 2: json_element['Value'] = [ { 'Phonetic': elem_value.components[2], }, ] elif len(elem_value.components) > 1: json_element['Value'] = [ { 'Ideographic': elem_value.components[1], }, ] else: json_element['Value'] = [ { 'Alphabetic': elem_value.components[0], }, ] else: if self.value is not None: is_multivalue = isinstance(self.value, MultiValue) if self.VM > 1 or is_multivalue: value = self.value else: value = [self.value] # ensure it's a list and not another iterable # (e.g. tuple), which would not be JSON serializable if self.VR in _VRs_TO_QUOTE: json_element['Value'] = [str(v) for v in value] else: json_element['Value'] = [v for v in value] if hasattr(json_element, 'Value'): json_element['Value'] = jsonrep.convert_to_python_number( json_element['Value'], self.VR) return json_element
def to_json_dict(self, bulk_data_element_handler: Optional[Callable[ ["DataElement"], str]], bulk_data_threshold: int) -> Dict[str, object]: """Return a dictionary representation of the :class:`DataElement` conforming to the DICOM JSON Model as described in the DICOM Standard, Part 18, :dcm:`Annex F<part18/chaptr_F.html>`. .. versionadded:: 1.4 Parameters ---------- bulk_data_element_handler: callable or None Callable that accepts a bulk data element and returns the "BulkDataURI" for retrieving the value of the data element via DICOMweb WADO-RS bulk_data_threshold: int Size of base64 encoded data element above which a value will be provided in form of a "BulkDataURI" rather than "InlineBinary". Ignored if no bulk data handler is given. Returns ------- dict Mapping representing a JSON encoded data element """ json_element = { 'vr': self.VR, } if self.VR in jsonrep.BINARY_VR_VALUES: if not self.is_empty: binary_value = self.value encoded_value = base64.b64encode(binary_value).decode('utf-8') if (bulk_data_element_handler is not None and len(encoded_value) > bulk_data_threshold): json_element['BulkDataURI'] = ( bulk_data_element_handler(self)) else: logger.info( f"encode bulk data element '{self.name}' inline") json_element['InlineBinary'] = encoded_value elif self.VR == 'SQ': # recursive call to get sequence item JSON dicts value = [ ds.to_json(bulk_data_element_handler=bulk_data_element_handler, bulk_data_threshold=bulk_data_threshold, dump_handler=lambda d: d) for ds in self.value ] json_element['Value'] = value elif self.VR == 'PN': if not self.is_empty: elem_value = [] if self.VM > 1: value = self.value else: value = [self.value] for v in value: comps = {'Alphabetic': v.components[0]} if len(v.components) > 1: comps['Ideographic'] = v.components[1] if len(v.components) > 2: comps['Phonetic'] = v.components[2] elem_value.append(comps) json_element['Value'] = elem_value elif self.VR == 'AT': if not self.is_empty: value = self.value if self.VM == 1: value = [value] json_element['Value'] = [format(v, '08X') for v in value] else: if not self.is_empty: if self.VM > 1: value = self.value else: value = [self.value] json_element['Value'] = [v for v in value] if hasattr(json_element, 'Value'): json_element['Value'] = jsonrep.convert_to_python_number( json_element['Value'], self.VR) return json_element
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))
def to_json_dict(self, bulk_data_element_handler, bulk_data_threshold): """Return a dictionary representation of the :class:`DataElement` conforming to the DICOM JSON Model as described in the DICOM Standard, Part 18, :dcm:`Annex F<part18/chaptr_F.html>`. Parameters ---------- bulk_data_element_handler: callable or None Callable that accepts a bulk data element and returns the "BulkDataURI" for retrieving the value of the data element via DICOMweb WADO-RS bulk_data_threshold: int Size of base64 encoded data element above which a value will be provided in form of a "BulkDataURI" rather than "InlineBinary" Returns ------- dict Mapping representing a JSON encoded data element Raises ------ TypeError When size of encoded data element exceeds `bulk_data_threshold` but `bulk_data_element_handler` is ``None`` and hence not callable """ json_element = {'vr': self.VR, } if self.VR in jsonrep.BINARY_VR_VALUES: if not self.is_empty: binary_value = self.value encoded_value = base64.b64encode(binary_value).decode('utf-8') if len(encoded_value) > bulk_data_threshold: if bulk_data_element_handler is None: raise TypeError( 'No bulk data element handler provided to ' 'generate URL for value of data element "{}".' .format(self.name) ) json_element['BulkDataURI'] = bulk_data_element_handler( self ) else: logger.info( 'encode bulk data element "{}" inline'.format( self.name ) ) json_element['InlineBinary'] = encoded_value elif self.VR == 'SQ': # recursive call to get sequence item JSON dicts value = [ ds.to_json( bulk_data_element_handler=bulk_data_element_handler, bulk_data_threshold=bulk_data_threshold, dump_handler=lambda d: d ) for ds in self ] json_element['Value'] = value elif self.VR == 'PN': if not self.is_empty: elem_value = [] if self.VM > 1: value = self.value else: value = [self.value] for v in value: if compat.in_py2: v = PersonNameUnicode(v, 'UTF8') comps = {'Alphabetic': v.components[0]} if len(v.components) > 1: comps['Ideographic'] = v.components[1] if len(v.components) > 2: comps['Phonetic'] = v.components[2] elem_value.append(comps) json_element['Value'] = elem_value elif self.VR == 'AT': if not self.is_empty: value = self.value if self.VM == 1: value = [value] json_element['Value'] = [format(v, '08X') for v in value] else: if not self.is_empty: if self.VM > 1: value = self.value else: value = [self.value] json_element['Value'] = [v for v in value] if hasattr(json_element, 'Value'): json_element['Value'] = jsonrep.convert_to_python_number( json_element['Value'], self.VR ) return json_element