def set_dicom_attribute(self, keyword, value): """Sets specified DICOM attribute according to the keyword value pair Parameters ---------- keyword : Name of DICOM tag to set value for value : Value to set """ if tag_for_keyword(keyword): if keyword in self.dataset: if dictionary_VR( tag_for_keyword(keyword)) == "SQ" and isinstance( value, list): value = generate_sequence(keyword, value) self.dataset[tag_for_keyword(keyword)].value = value else: if dictionary_VR( tag_for_keyword(keyword)) == "SQ" and isinstance( value, list): value = generate_sequence(keyword, value) de = DataElement(tag_for_keyword(keyword), dictionary_VR(tag_for_keyword(keyword)), value) self.dataset[tag_for_keyword(keyword)] = de else: print("Keyword", keyword, "is an unknown DICOM attribute")
def CodeSeqItemGenerator(code_value: str, code_meaning: str, coding_shceme_designator: str) -> Dataset: output = Dataset() cv = Dictionary.tag_for_keyword('CodeValue') cm = Dictionary.tag_for_keyword('CodeMeaning') cs = Dictionary.tag_for_keyword('CodingSchemeDesignator') output[cv] = DataElementX(cv, Dictionary.dictionary_VR(cv), code_value) output[cm] = DataElementX(cm, Dictionary.dictionary_VR(cm), code_meaning) output[cs] = DataElementX(cs, Dictionary.dictionary_VR(cs), coding_shceme_designator) return output
def generalfix_CheckAndFixModality(ds: Dataset, log: list) -> bool: fixed = False modality_sop = { CTImageStorageSOPClassUID: 'CT', MRImageStorageSOPClassUID: 'MR', PETImageStorageSOPClassUID: 'PT', } if 'SOPClassUID' in ds: sop_class = ds['SOPClassUID'].value else: return False if sop_class not in modality_sop: return False mod_tg = tag_for_keyword('Modality') if mod_tg in ds: modality = ds[mod_tg].value else: modality = '' if modality == '' or modality != modality_sop[sop_class]: ds[mod_tg] = DataElementX(mod_tg, dictionary_VR(mod_tg), modality_sop[sop_class]) msg = ErrorInfo() msg.msg = 'General Fix - {}'.format("<Modality> is wrong or absent") msg.fix = "fixed by reading the <SOPClassUID> and setting <Modality>"\ " from '{}' to '{}'".format(modality, modality_sop[sop_class]) log.append(msg.getWholeMessage()) fixed = True return fixed
def update_dict(self): """ The dictionary to be provided as input to edit_dicom to make the file(s) consistent with self.fw_header """ if not isinstance(self._update_dict, dict): if self.header_diff_dict: info_str = f"Differing DICOM tags:\n {pformat(self.header_diff_dict)}" self.log.debug(info_str) update_dict = { k: v.fw_value for k, v in self.header_diff_dict.items() } # Remove OF, SQ, UI VR tags exclude_keys = [ k for k in update_dict.keys() if dictionary_VR(k) in self.exclude_vrs ] exclude_vr_tags = {k: update_dict.pop(k) for k in exclude_keys} if exclude_vr_tags: warn_str = ( f"{len(exclude_vr_tags)} DICOM tags have VRs {self.exclude_vrs} " "for which editing is not supported. The values for the " "following tags will not be edited despite info.header.dicom" f" and local values differing: {pformat(exclude_vr_tags)}" ) self.log.warning(warn_str) self._update_dict = update_dict return self._update_dict
def generalfix_AddPresentationLUTShape(ds: Dataset, log: list) -> bool: fixed = False photo_in_tg = tag_for_keyword('PhotometricInterpretation') if photo_in_tg not in ds: return fixed photo_in_v = ds[photo_in_tg].value pres_lut_shape_tg = tag_for_keyword('PresentationLUTShape') if pres_lut_shape_tg in ds: pres_lut_shape_a = ds[pres_lut_shape_tg] else: pres_lut_shape_a = DataElementX(pres_lut_shape_tg, dictionary_VR(pres_lut_shape_tg), '') old_pls = pres_lut_shape_a.value if photo_in_v == 'MONOCHROME2' and old_pls != 'IDENTITY': new_pls = 'IDENTITY' pres_lut_shape_a.value = new_pls fixed = True elif photo_in_v == 'MONOCHROME1' and old_pls != 'INVERSE': new_pls = 'INVERSE' pres_lut_shape_a.value = new_pls fixed = True if fixed: ds[pres_lut_shape_tg] = pres_lut_shape_a msg = ErrorInfo() msg.msg = 'General Fix - {}'.format( "<PresentationLUTShape> is wrong or absent") msg.fix = "fixed by setting the <PresentationLUTShape>"\ " from '{}' to '{}'".format(old_pls, new_pls) log.append(msg.getWholeMessage()) return fixed
def __init__(self, tag, VR, value, file_value_tell=None, is_undefined_length=False, already_converted=False): """Create a new :class:`DataElement`. Parameters ---------- tag : int or or str or list or tuple The DICOM (group, element) tag in any form accepted by :func:`~pydicom.tag.Tag` such as ``[0x0010, 0x0010]``, ``(0x10, 0x10)``, ``0x00100010``, etc. VR : str The 2 character DICOM value representation (see DICOM Standard, Part 5, :dcm:`Section 6.2<part05/sect_6.2.html>`). value The value of the data element. One of the following: * a single string value * a number * a :class:`list` or :class:`tuple` with all strings or all numbers * a multi-value string with backslash separator file_value_tell : int or None Used internally by :class:`~pydicom.dataset.Dataset` to store the write position for the ``ReplaceDataElementValue()`` method. Default is ``None``. is_undefined_length : bool Used internally to store whether the length field for this element was ``0xFFFFFFFFL``, i.e. 'undefined length'. Default is ``False``. already_converted : bool Used to determine whether or not the element's value requires conversion to a value with VM > 1. Default is ``False``. """ if not isinstance(tag, BaseTag): tag = Tag(tag) self.tag = tag # a known tag shall only have the VR 'UN' if it has a length that # exceeds the size that can be encoded in 16 bit - all other cases # can be seen as an encoding error and can be corrected if (VR == 'UN' and not tag.is_private and config.replace_un_with_known_vr and (is_undefined_length or value is None or len(value) < 0xffff)): try: VR = dictionary_VR(tag) except KeyError: pass self.VR = VR # Note: you must set VR before setting value if already_converted: self._value = value else: self.value = value # calls property setter which will convert self.file_tell = file_value_tell self.is_undefined_length = is_undefined_length self.private_creator = None
def subfix_AddOrChangeAttrib(ds: Dataset, log: list, error_regexp: str, fix_message: str, keyword: str, value) -> bool: fixed = False t = Dictionary.tag_for_keyword(keyword) if t is None: return False desc = Dictionary.dictionary_description(t) ErrorOccured = False log_l = len(log) for i in range(0, log_l): if re.match(error_regexp, log[i]) is not None: msg = mesgtext_cc.ErrorInfo(log[i], fix_message) log[i] = msg.getWholeMessage() ErrorOccured = True if ErrorOccured: if keyword in ds: ds[keyword].value = value # just modify fixed = True else: vr = Dictionary.dictionary_VR(t) vm = 1 elem = DataElementX(t, vr, value) ds[keyword] = elem fixed = True return fixed
def fix_separator_callback(raw_elem, **kwargs): """Used by fix_separator as the callback function from read_dataset """ return_val = raw_elem try_replace = False # If elements are implicit VR, attempt to determine the VR if raw_elem.VR is None: try: VR = datadict.dictionary_VR(raw_elem.tag) # Not in the dictionary, process if flag says to do so except KeyError: try_replace = kwargs['process_unkown_VR'] else: try_replace = VR in kwargs['for_VRs'] else: try_replace = raw_elem.VR in kwargs['for_VRs'] if try_replace: # Note value has not been decoded yet when this function called, # so need to replace backslash as bytes new_value = raw_elem.value.replace(kwargs['invalid_separator'], b"\\") return_val = raw_elem._replace(value=new_value) return return_val
def _get_vr_ord(self, key, ordinate): tag = keyword_dict[key] vr = dictionary_VR(tag) if vr == 'TM': return '%06d.000000' % ordinate else: return ordinate
def reformatJSON(self, request_json: dict): """ iterate over key/values in json {"00080005": {"Value": ["ISO_IR 100"], "vr": "CS"} pydicom.datadict.dictionary_VR(tag) """ pydicom_json = {} for key, value in request_json.items(): if isinstance(value, list): scheduledsequences = [] for item in value: scheduledsequences.append(self.reformatJSON(item)) pydicom_json[key] = {"Value": scheduledsequences, "vr": dictionary_VR(key)} else: pydicom_json[key] = {"Value": [value], "vr": dictionary_VR(key)} return pydicom_json
def get_pydicom_header(dcm): # Extract the header values errors = walk_dicom(dcm, callbacks=[fix_VM1_callback], recursive=True) if errors: result = "" for error in errors: result += "\n {}".format(error) log.warning(f"Errors found in walking dicom: {result}") header = {} exclude_tags = [ "[Unknown]", "PixelData", "Pixel Data", "[User defined data]", "[Protocol Data Block (compressed)]", "[Histogram tables]", "[Unique image iden]", "ContourData", "EncryptedAttributesSequence", ] tags = dcm.dir() for tag in tags: try: if (tag not in exclude_tags) and ( type(dcm.get(tag)) != pydicom.sequence.Sequence ): value = dcm.get(tag) if value or value == 0: # Some values are zero # Put the value in the header if ( type(value) == str and len(value) < 10240 ): # Max pydicom field length header[tag] = format_string(value) else: header[tag] = assign_type(value) else: # Strangeness between pydicom versions for backwards compatibility. # Pydicom v2.0.0 reads an empty PN value as a single space, mock doing that # Here so we don't accidentally try to set this value back to a space. if dictionary_VR(tag) == 'PN': header[tag] = format_string(' ') else: log.debug("No value found for tag: " + tag) if (tag not in exclude_tags) and type( dcm.get(tag) ) == pydicom.sequence.Sequence: seq_data = get_seq_data(dcm.get(tag), exclude_tags) # Check that the sequence is not empty if seq_data: header[tag] = seq_data except: log.debug("Failed to get " + tag) pass fix_type_based_on_dicom_vm(header) return header
def DataElement_from_raw(raw_data_element, encoding=None): """Return a :class:`DataElement` created from `raw_data_element`. Parameters ---------- raw_data_element : RawDataElement namedtuple The raw data to convert to a :class:`DataElement`. encoding : str, optional The character encoding of the raw data. Returns ------- DataElement """ # XXX buried here to avoid circular import # filereader->Dataset->convert_value->filereader # (for SQ parsing) if in_py2: encoding = encoding or default_encoding from pydicom.values import convert_value raw = raw_data_element # If user has hooked into conversion of raw values, call his/her routine if config.data_element_callback: data_elem = config.data_element_callback raw = data_elem(raw_data_element, **config.data_element_callback_kwargs) VR = raw.VR if VR is None: # Can be if was implicit VR try: VR = dictionary_VR(raw.tag) except KeyError: # just read the bytes, no way to know what they mean if raw.tag.is_private: # for VR for private tags see PS3.5, 6.2.2 if raw.tag.is_private_creator: VR = 'LO' else: VR = 'UN' # group length tag implied in versions < 3.0 elif raw.tag.element == 0: VR = 'UL' else: msg = "Unknown DICOM tag {0:s}".format(str(raw.tag)) msg += " can't look up VR" raise KeyError(msg) try: value = convert_value(VR, raw, encoding) except NotImplementedError as e: raise NotImplementedError("{0:s} in tag {1!r}".format(str(e), raw.tag)) if raw.tag in _LUT_DESCRIPTOR_TAGS and value[0] < 0: # We only fix the first value as the third value is 8 or 16 value[0] += 65536 return DataElement(raw.tag, VR, value, raw.value_tell, raw.length == 0xFFFFFFFF, already_converted=True)
def VR(self): """Find the correct Value Representation for this tag from pydicom""" try: return dictionary_VR(Tag(self.tag)) except KeyError as e: # unknown tag. Just return set value, assuming user want to just # get on with it return VRs.LongString.short_name
def tag2str(ttag: BaseTag): if Dictionary.dictionary_has_tag(ttag): desc = Dictionary.dictionary_description(ttag) vr = Dictionary.dictionary_VR(ttag) txt = "->{}:{}".format(desc, vr) else: txt = '' msg = "(0x{:0>4x}, 0x{:0>4x})".format(ttag.group, ttag.element, txt) return msg
def fix_ByAddingEmptyAttrib(ds: Dataset, element: str) -> str: reason = '' ttag = tag_for_keyword(element) if ttag is not None: vr = Dic.dictionary_VR(ttag) element = DataElement(ttag, vr, '') element.value = element.empty_value ds[ttag] = element reason += "fixed by adding empty attribute" return reason
def DataElement_from_raw(raw_data_element, encoding=None): """Return a DataElement created from the data in `raw_data_element`. Parameters ---------- raw_data_element : RawDataElement namedtuple The raw data to convert to a DataElement encoding : str The encoding of the raw data Returns ------- pydicom.dataelem.DataElement """ # XXX buried here to avoid circular import # filereader->Dataset->convert_value->filereader # (for SQ parsing) if in_py2: encoding = encoding or default_encoding from pydicom.values import convert_value raw = raw_data_element # If user has hooked into conversion of raw values, call his/her routine if config.data_element_callback: data_elem = config.data_element_callback raw = data_elem(raw_data_element, **config.data_element_callback_kwargs) VR = raw.VR if VR is None: # Can be if was implicit VR try: VR = dictionary_VR(raw.tag) except KeyError: # just read the bytes, no way to know what they mean if raw.tag.is_private: VR = 'OB' # group length tag implied in versions < 3.0 elif raw.tag.element == 0: VR = 'UL' else: msg = "Unknown DICOM tag {0:s}".format(str(raw.tag)) msg += " can't look up VR" raise KeyError(msg) try: value = convert_value(VR, raw, encoding) except NotImplementedError as e: raise NotImplementedError("{0:s} in tag {1!r}".format(str(e), raw.tag)) return DataElement(raw.tag, VR, value, raw.value_tell, raw.length == 0xFFFFFFFF, already_converted=True)
def __init__(self, tag, VR, value, file_value_tell=None, is_undefined_length=False, already_converted=False): """Create a new DataElement. Parameters ---------- tag The DICOM (group, element) tag in any form accepted by pydicom.tag.Tag such as [0x0010, 0x0010], (0x10, 0x10), 0x00100010, etc. VR : str The 2 character DICOM value representation (see DICOM standard part 5, Section 6.2). value The value of the data element. One of the following: * a single string value * a number * a list or tuple with all strings or all numbers * a multi-value string with backslash separator file_value_tell : int or None Used internally by Dataset to store the write position for the ReplaceDataElementValue() method. Default is None. is_undefined_length : bool Used internally to store whether the length field for this element was 0xFFFFFFFFL, i.e. 'undefined length'. Default is False. already_converted : bool Used to determine whether or not `value` requires conversion to a value with VM > 1. Default is False. """ if not isinstance(tag, BaseTag): tag = Tag(tag) self.tag = tag # a known tag shall only have the VR 'UN' if it has a length that # exceeds the size that can be encoded in 16 bit - all other cases # can be seen as an encoding error and can be corrected if VR == 'UN' and (is_undefined_length or value is None or len(value) < 0xffff): try: VR = dictionary_VR(tag) except KeyError: pass self.VR = VR # Note!: you must set VR before setting value if already_converted: self._value = value else: self.value = value # calls property setter which will convert self.file_tell = file_value_tell self.is_undefined_length = is_undefined_length
def DataElement_from_raw(raw_data_element, encoding=None): """Return a DataElement created from the data in `raw_data_element`. Parameters ---------- raw_data_element : RawDataElement namedtuple The raw data to convert to a DataElement encoding : str The encoding of the raw data Returns ------- pydicom.dataelem.DataElement """ # XXX buried here to avoid circular import # filereader->Dataset->convert_value->filereader # (for SQ parsing) if in_py2: encoding = encoding or default_encoding from pydicom.values import convert_value raw = raw_data_element # If user has hooked into conversion of raw values, call his/her routine if config.data_element_callback: data_elem = config.data_element_callback raw = data_elem(raw_data_element, **config.data_element_callback_kwargs) VR = raw.VR if VR is None: # Can be if was implicit VR try: VR = dictionary_VR(raw.tag) except KeyError: # just read the bytes, no way to know what they mean if raw.tag.is_private: # for VR for private tags see PS3.5, 6.2.2 if raw.tag.is_private_creator: VR = 'LO' else: VR = 'UN' # group length tag implied in versions < 3.0 elif raw.tag.element == 0: VR = 'UL' else: msg = "Unknown DICOM tag {0:s}".format(str(raw.tag)) msg += " can't look up VR" raise KeyError(msg) try: value = convert_value(VR, raw, encoding) except NotImplementedError as e: raise NotImplementedError("{0:s} in tag {1!r}".format(str(e), raw.tag)) return DataElement(raw.tag, VR, value, raw.value_tell, raw.length == 0xFFFFFFFF, already_converted=True)
def verifyVR(elem: DataElement, module: str, element: str, verbose: bool, log: list, fix_trivial=False): # tag = getTag(); # if (tag.isPrivateTag()) : # return True v = elem.value try: vrd = dictionary_VR(elem.tag) except BaseException as err: print(err) mssg = EMsgDC("NoSuchElementInDictionary") + " " if len(element) != 0: mssg += MMsgDC("Element") + "=<" + element + ">" if len(module) != 0: mssg += MMsgDC("Module") + "=<" + module + ">" log.append(mssg) return False vre = elem.VR vrds = [] vre_equlas_vrd = False if len(vrd) > 2 and len(vre) == 2: vrds = vrd.split(' or ') for dic_vr in vrds: if dic_vr == vre: vre_equlas_vrd = True break else: vre_equlas_vrd = (vre == vrd) if not vre_equlas_vrd and not(vrd == "OX" and vre == "OB" or vre == "OW") \ and not(vrd == "XS" and vre == "US" or vre == "SS") \ and not(vrd == "XO" and vre == "US" or vre == "SS" or vre == "OW") \ and not(vrd == "XL" and vre == "UL" or vre == "SL"): mssg = EMsgDC("BadValueRepresentation") \ + " " + vre + " (" + vrd + " " + MMsgDC("Required") + ")" # print(vrds,'<-->' ,vrd,'<-->', vre, '<-->', elem) if len(element) != 0: mssg += MMsgDC("Element") + "=<" + element + ">" if len(module) != 0: mssg += MMsgDC("Module") + "=<" + module + ">" log.append(mssg) if fix_trivial: elem.VR = vrd mssg += " :fixed: by changing the vr" return False else: return True
def update_and_insert_additional_DICOM_attributes_in_ds( ds, keyword_and_value_dict): # For every keyword for keyword in keyword_and_value_dict: # Get corresponding tag and value tag = tag_for_keyword(keyword) # Verify that it is a valid keyword if tag is None: print("Unknown DICOM attribute:", keyword) continue # Get corresponding value value = None if dictionary_VR(tag) == "SQ": value = generate_sequence(keyword, keyword_and_value_dict[keyword]) else: value = keyword_and_value_dict[keyword] # If keyword already set, update its value, otherwise, create a new data element if keyword in ds: ds[tag].value = value else: ds[tag] = DataElement(tag, dictionary_VR(tag), value) # Return edited dataset return ds
def add_anatomy(ds: Dataset, BodyPartExamined_value: str, AnatomicRegionSequence_value: tuple, log: list, check_consistency: bool = True): bpe = tag_for_keyword('BodyPartExamined') ars = tag_for_keyword('AnatomicRegionSequence') old_bpe, old_ars = get_old_anatomy(ds) if old_bpe is None: if check_consistency: new_bpe, new_ars = \ CorrectAnatomicInfo( BodyPartExamined_value, AnatomicRegionSequence_value) else: new_bpe, new_ars = (BodyPartExamined_value, AnatomicRegionSequence_value) else: new_bpe, new_ars = CorrectAnatomicInfo(old_bpe, old_ars) if old_bpe != new_bpe and new_bpe is not None: bpe_a = DataElementX(bpe, dictionary_VR(bpe), new_bpe) old_bpe_txt = old_bpe if bpe not in ds else ds[bpe].value ds[bpe] = bpe_a msg = ErrorInfo() msg.msg = 'General Fix - {}'.format("<BodyPartExamined> is absent") msg.fix = "fixed by setting the <BodyPartExamined>"\ "from {} to '{}'".format(old_bpe_txt, new_bpe) log.append(msg.getWholeMessage()) if is_accurate_code_seq(new_ars) and not is_code_equal(old_ars, new_ars): code_value, code_meaning, coding_scheme_designator = new_ars if ars in ds: old_item_text = subfix_CodeSeqItem2txt(ds[ars], 0) else: old_item_text = 'None' new_item = CodeSeqItemGenerator(str(code_value), code_meaning, coding_scheme_designator) ars_a = DataElementX(ars, 'SQ', DicomSequence([ new_item, ])) ds[ars] = ars_a msg = ErrorInfo() msg.msg = 'General Fix - {}'.format( "<AnatomicRegionSequence> is absent") msg.fix = "fixed by setting the <AnatomicRegionSequence>"\ "from {} to '{}'".format( old_item_text, subfix_CodeSeqItem2txt(ars_a, 0)) log.append(msg.getWholeMessage()) AddLaterality(ds, log)
def overwriteDicomFileTag(imagePath, dicomTag, newValue): try: if isinstance(imagePath, list): datasetList = readDICOM_Image.getSeriesDicomDataset(imagePath) for index, dataset in enumerate(datasetList): if isinstance(dicomTag, str): try: dataset.data_element(dicomTag).value = newValue except: dataset.add_new(dicomTag, dictionary_VR(dicomTag), newValue) else: try: dataset[hex(dicomTag)].value = newValue except: dataset.add_new(hex(dicomTag), dictionary_VR(hex(dicomTag)), newValue) saveDicomToFile(dataset, output_path=imagePath[index]) else: dataset = readDICOM_Image.getDicomDataset(imagePath) if isinstance(dicomTag, str): try: dataset.data_element(dicomTag).value = newValue except: dataset.add_new(dicomTag, dictionary_VR(dicomTag), newValue) else: try: dataset[hex(dicomTag)].value = newValue except: dataset.add_new(hex(dicomTag), dictionary_VR(hex(dicomTag)), newValue) saveDicomToFile(dataset, output_path=imagePath) return except Exception as e: print('Error in saveDICOM_Image.overwriteDicomFileTag: ' + str(e))
def subfix_UpdateOrInsertCodeAttribute(seqelem: DataElementX, index: int, kw: str, value: str) -> str: text_fun = lambda ds, att: '{}: {}\t'.format(att, ds[att]) out_msg = '' if kw in seqelem.value[index]: out_msg = " {} modified <{}> -> <{}>".format( kw, seqelem.value[index][kw].value, value) seqelem.value[index][kw].value = value else: out_msg = "{} = <{}> was added".format(kw, value) newtag = Dictionary.tag_for_keyword(kw) newvr = Dictionary.dictionary_VR(newtag) elem = DataElementX(newtag, newvr, value) seqelem.value[index].add(elem) return out_msg
def put_attribute_in_path(ds: Dataset, path: list, a: DataElementX): if not path: if a.tag not in ds: ds[a.tag] = a else: kw = path.pop(0) tg = Dictionary.tag_for_keyword(kw) vr = Dictionary.dictionary_VR(tg) if vr == 'SQ': if tg in ds and ds[tg].VM > 0: inner_sq = ds[tg] item = inner_sq.value[0] else: item = Dataset() new_element = DataElementX(tg, vr, Sequence([item])) ds[tg] = new_element put_attribute_in_path(item, path, a)
def copy_additional_dicom_attributes(self, dataset_to_copy_from, dataset_to_copy_to, additional_dicom_attributes): """Copies additional DICOM attributes for this module from one dataset to another Parameters ---------- dataset_to_copy_from : Dataset to copy DICOM attributes from dataset_to_copy_to : Dataset to copy DICOM attributes to additional_dicom_attributes : List of additional DICOM attributes to copy """ for dicom_attribute in additional_dicom_attributes: tag = tag_for_keyword(dicom_attribute) if dicom_attribute in dataset_to_copy_from: dataset_to_copy_to[tag] = dataset_to_copy_from[tag] else: de = DataElement(tag, dictionary_VR(tag), "") dataset_to_copy_to[tag] = de
def copy_required_dicom_attributes(self, dataset_to_copy_from, dataset_to_copy_to): """Copies required DICOM attributes for this module from one dataset to another Parameters ---------- dataset_to_copy_from : Dataset to copy DICOM attributes from dataset_to_copy_to : Dataset to copy DICOM attributes to """ for dicom_attribute in self.required_dicom_attributes: tag = tag_for_keyword(dicom_attribute) if dicom_attribute in dataset_to_copy_from: dataset_to_copy_to[tag] = dataset_to_copy_from[tag] elif dicom_attribute in dataset_to_copy_to: pass else: de = DataElement(tag, dictionary_VR(tag), "") dataset_to_copy_to[tag] = de
def set_dicom_tag_value(ds, tag, value): """Set or update a DICOM tag value in the pydicom dataset. Parameters ---------- ds : pydicom Dataset The pydicom dataset for the tag to be added/updated to. tag : str, int or tuple DICOM tag or keyword to be added. value : any New value for the tag's element. """ try: ds[tag].value = value except KeyError: if tag in keyword_dict: # Keyword provided rather than int or tuple tag = keyword_dict[tag] ds.add_new(tag, dictionary_VR(tag), value)
def safe_get_(dcm: FileDataset, tag: int) -> Optional[ParsedElementValue]: try: element = dcm[tag] VR, element_value = dictionary_VR(tag), element.value if element_value == "" or element_value is None: return None vr_parser = DicomVRParseDictionary.get(VR, lambda value: value) if isinstance(element_value, MultiValue) is not isinstance(element_value, Sequence): return cast(ParsedElementValue, [vr_parser(item) for item in element_value]) return vr_parser(element_value) except KeyError: logger.debug(f"Cannot find element using for tag={to_dicom_tag(tag)}") return None except ValueError as error: logger.warning(f"Encountered ValueError extracting element for tag={to_dicom_tag(tag)} - err={error}") return None
def __setattr__(self, name, value): """Intercept any attempts to set a value for an instance attribute. If name is a DICOM keyword, set the corresponding tag and DataElement. Else, set an instance (python) attribute as any other class would do. Parameters ---------- name : str The element keyword for the DataElement you wish to add/change. If `name` is not a DICOM element keyword then this will be the name of the attribute to be added/changed. value The value for the attribute to be added/changed. """ tag = tag_for_keyword(name) if tag is not None: # successfully mapped name to a tag if tag not in self: # don't have this tag yet->create the data_element instance VR = dictionary_VR(tag) data_element = DataElement(tag, VR, value) else: # already have this data_element, just changing its value data_element = self[tag] data_element.value = value # Now have data_element - store it in this dict self[tag] = data_element elif repeater_has_keyword(name): # Check if `name` is repeaters element raise ValueError('{} is a DICOM repeating group ' 'element and must be added using ' 'the add() or add_new() methods.' .format(name)) else: # name not in dicom dictionary - setting a non-dicom instance # attribute # XXX note if user mis-spells a dicom data_element - no error!!! super(Dataset, self).__setattr__(name, value)
def fix_separator_callback( raw_elem: "RawDataElement", **kwargs: Any ) -> "RawDataElement": """Used by fix_separator as the callback function from read_dataset """ return_val = raw_elem try_replace = False # If elements are implicit VR, attempt to determine the VR if raw_elem.VR is None: try: vr = datadict.dictionary_VR(raw_elem.tag) # Not in the dictionary, process if flag says to do so except KeyError: try_replace = kwargs['process_unknown_VRs'] else: try_replace = vr in kwargs['for_VRs'] else: try_replace = raw_elem.VR in kwargs['for_VRs'] if try_replace: # Note value has not been decoded yet when this function called, # so need to replace backslash as bytes new_value = None if raw_elem.value is not None: if kwargs['invalid_separator'] == b" ": stripped_val = raw_elem.value.strip() strip_count = len(raw_elem.value) - len(stripped_val) new_value = stripped_val.replace( kwargs['invalid_separator'], b"\\" ) + b" " * strip_count else: new_value = raw_elem.value.replace( kwargs['invalid_separator'], b"\\" ) return_val = raw_elem._replace(value=new_value) return return_val
def data_element_generator(fp, is_implicit_VR, is_little_endian, stop_when=None, defer_size=None, encoding=default_encoding, specific_tags=None): """Create a generator to efficiently return the raw data elements. Parameters ---------- fp : file-like object is_implicit_VR : boolean is_little_endian : boolean stop_when : None, callable, optional If None (default), then the whole file is read. A callable which takes tag, VR, length, and returns True or False. If it returns True, read_data_element will just return. defer_size : int, str, None, optional See ``dcmread`` for parameter info. encoding : Encoding scheme specific_tags : list or None See ``dcmread`` for parameter info. Returns ------- 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 : boolean 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 = config.debugging element_struct_unpack = element_struct.unpack defer_size = size_in_bytes(defer_size) tag_set = set() has_specific_char_set = True if specific_tags is not None: for tag in specific_tags: if isinstance(tag, (str, compat.text_type)): tag = Tag(tag_for_keyword(tag)) if isinstance(tag, BaseTag): tag_set.add(tag) has_specific_char_set = Tag(0x08, 0x05) in tag_set tag_set.add(Tag(0x08, 0x05)) has_tag_set = len(tag_set) > 0 while True: # Read tag, VR, length, get ready to read value bytes_read = fp_read(8) if len(bytes_read) < 8: return # 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 not in_py2: 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 != 0xFFFFFFFF: 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) return # Reading the value # First case (most common): reading a value with a defined length if length != 0xFFFFFFFF: # don't defer loading of Specific Character Set value as it is # needed immediately to get the character encoding for other tags if has_tag_set and tag not in tag_set: # skip the tag if not in specific tags fp.seek(fp_tell() + length) continue if (defer_size is not None and length > defer_size and tag != BaseTag(0x00080005)): # 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 == BaseTag(0x00080005): from pydicom.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) if not has_specific_char_set: continue 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 = dictionary_VR(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) if has_tag_set and tag not in tag_set: continue 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 pydicom.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) if not has_specific_char_set: continue # tags with undefined length are skipped after read if has_tag_set and tag not in tag_set: continue 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, encoding=default_encoding, specific_tags=None): """Create a generator to efficiently return the raw data elements. Parameters ---------- fp : file-like object is_implicit_VR : boolean is_little_endian : boolean stop_when : None, callable, optional If None (default), then the whole file is read. A callable which takes tag, VR, length, and returns True or False. If it returns True, read_data_element will just return. defer_size : int, str, None, optional See ``dcmread`` for parameter info. encoding : Encoding scheme specific_tags : list or None See ``dcmread`` for parameter info. Returns ------- 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 : boolean 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 = config.debugging element_struct_unpack = element_struct.unpack defer_size = size_in_bytes(defer_size) tag_set = set() if specific_tags is not None: for tag in specific_tags: if isinstance(tag, (str, compat.text_type)): tag = Tag(tag_for_keyword(tag)) if isinstance(tag, BaseTag): tag_set.add(tag) tag_set.add(Tag(0x08, 0x05)) has_tag_set = len(tag_set) > 0 while True: # Read tag, VR, length, get ready to read value bytes_read = fp_read(8) if len(bytes_read) < 8: return # 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 not in_py2: 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 != 0xFFFFFFFF: 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) return # Reading the value # First case (most common): reading a value with a defined length if length != 0xFFFFFFFF: # don't defer loading of Specific Character Set value as it is # needed immediately to get the character encoding for other tags if has_tag_set and tag not in tag_set: # skip the tag if not in specific tags fp.seek(fp_tell() + length) continue if (defer_size is not None and length > defer_size and tag != BaseTag(0x00080005)): # 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 == BaseTag(0x00080005): from pydicom.values import convert_string encoding = convert_string(value, is_little_endian) # 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 = dictionary_VR(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) if has_tag_set and tag not in tag_set: continue 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 pydicom.values import convert_string encoding = convert_string(value, is_little_endian) # Store the encoding value in the generator for use # with future elements (SQs) encoding = convert_encodings(encoding) # tags with undefined length are skipped after read if has_tag_set and tag not in tag_set: continue yield RawDataElement(tag, VR, length, value, value_tell, is_implicit_VR, is_little_endian)