def __contains__(self, name): """Extend dict.__contains__() to handle DICOM keywords. This is called for code like: >>> 'SliceLocation' in ds True Parameters ---------- name : str or int or 2-tuple The Element keyword or tag to search for. Returns ------- bool True if the DataElement is in the Dataset, False otherwise. """ if isinstance(name, (str, compat.text_type)): tag = tag_for_keyword(name) else: try: tag = Tag(name) except Exception: return False # Test against None as (0000,0000) is a possible tag if tag is not None: return dict.__contains__(self, tag) else: return dict.__contains__(self, name) # will no doubt raise an exception
def __contains__(self, name): """Extend dict.__contains__() to handle DICOM keywords. This is called for code like: >>> 'SliceLocation' in ds True Parameters ---------- name : str or int or 2-tuple The Element keyword or tag to search for. Returns ------- bool True if the DataElement is in the Dataset, False otherwise. """ if isinstance(name, (str, compat.text_type)): tag = tag_for_keyword(name) else: try: tag = Tag(name) except Exception: return False # Test against None as (0000,0000) is a possible tag if tag is not None: return dict.__contains__(self, tag) else: return dict.__contains__(self, name) # will no doubt raise an exception
def __getattr__(self, name): """Intercept requests for Dataset attribute names. If `name` matches a DICOM keyword, return the value for the DataElement with the corresponding tag. Parameters ---------- name A DataElement keyword or tag or a class attribute name. Returns ------- value If `name` matches a DICOM keyword, returns the corresponding DataElement's value. Otherwise returns the class attribute's value (if present). """ tag = tag_for_keyword(name) if tag is None: # `name` isn't a DICOM element keyword # Try the base class attribute getter (fix for issue 332) return super(Dataset, self).__getattribute__(name) tag = Tag(tag) if tag not in self: # DICOM DataElement not in the Dataset # Try the base class attribute getter (fix for issue 332) return super(Dataset, self).__getattribute__(name) else: return self[tag].value
def __delattr__(self, name): """Intercept requests to delete an attribute by `name`. If `name` is a DICOM keyword: Delete the corresponding DataElement from the Dataset. >>> del ds.PatientName Else: Delete the class attribute as any other class would do. >>> del ds._is_some_attribute Parameters ---------- name : str The keyword for the DICOM element or the class attribute to delete. """ # First check if a valid DICOM keyword and if we have that data element tag = tag_for_keyword(name) if tag is not None and tag in self: dict.__delitem__(self, tag) # direct to dict as we know we have key # If not a DICOM name in this dataset, check for regular instance name # can't do delete directly, that will call __delattr__ again elif name in self.__dict__: del self.__dict__[name] # Not found, raise an error in same style as python does else: raise AttributeError(name)
def __getattr__(self, name): """Intercept requests for Dataset attribute names. If `name` matches a DICOM keyword, return the value for the DataElement with the corresponding tag. Parameters ---------- name A DataElement keyword or tag or a class attribute name. Returns ------- value If `name` matches a DICOM keyword, returns the corresponding DataElement's value. Otherwise returns the class attribute's value (if present). """ tag = tag_for_keyword(name) if tag is None: # `name` isn't a DICOM element keyword # Try the base class attribute getter (fix for issue 332) return super(Dataset, self).__getattribute__(name) tag = Tag(tag) if tag not in self: # DICOM DataElement not in the Dataset # Try the base class attribute getter (fix for issue 332) return super(Dataset, self).__getattribute__(name) else: return self[tag].value
def tag(self): """Return the element's tag as a pydicom.tag.Tag.""" tag = self.components[0] if self.is_sequence: tag = tag.split('[')[0] # (gggg,eeee) based tag if ',' in tag: group, element = tag.split(',') if '(' in group: group = group.replace('(', '') if ')' in element: element = element.replace(')', '') if len(group) != 4 or len(element) != 4: raise ValueError(f"Unable to parse element path component: " f"'{self.components[0]}'") return Tag(group, element) # From this point on we assume that a keyword was supplied kw = tag # Keyword based tag - private keywords not allowed if repeater_has_keyword(kw): raise ValueError( f"Repeating group elements must be specified using " f"(gggg,eeee): '{self.components[0]}'") tag = tag_for_keyword(kw) # Test against None as 0x00000000 is a valid tag if tag is not None: return Tag(tag) raise ValueError( f"Unable to parse element path component: '{self.components[0]}'")
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_VRForLongitudinalTemporalInformationModified(ds: Dataset, log: list) -> bool: fixed = False msg = mesgtext_cc.ErrorInfo() kw = "LongitudinalTemporalInformationModified" Error_regex = ".*Invalid Value Representation SH \(CS Required\)" \ ".*{}.*".format(kw) idx = subfix_LookUpRegexInLog(Error_regex, log) if len(idx) == 0: idx.append(-1) log.append("Error - bad VR for {}".format(kw)) if kw in ds: tag = Dictionary.tag_for_keyword(kw) if ds[tag].VR == "SH": ds[tag].VR = "CS" for i in idx: msg.msg = log[i] msg.fix = "fixed by editing SH to CS" msg1 = msg.getWholeMessage() log[i] = msg1 fixed = True return fixed
def fix_RemoveFOVDimensionsWhenZero(ds: Dataset, log: list) -> bool: fixed = False kw = "FieldOfViewDimensions" Error_regex = ".*Value is zero for.*attribute.*Field.*View.*Dimension.*" msg = mesgtext_cc.ErrorInfo() idx = subfix_LookUpRegexInLog(Error_regex, log) if len(idx) == 0: idx.append(-1) log.append("Error - bad value (=0) for {}".format(kw)) if kw in ds: tag = Dictionary.tag_for_keyword(kw) elem = ds[tag] for i in elem.value: if i == 0: fixed = True break if fixed: del ds[kw] for i in idx: msg.msg = log[i] msg.fix = "fixed by removing the attribute" msg1 = msg.getWholeMessage() log[i] = msg1 return fixed
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 __delattr__(self, name): """Intercept requests to delete an attribute by `name`. If `name` is a DICOM keyword: Delete the corresponding DataElement from the Dataset. >>> del ds.PatientName Else: Delete the class attribute as any other class would do. >>> del ds._is_some_attribute Parameters ---------- name : str The keyword for the DICOM element or the class attribute to delete. """ # First check if a valid DICOM keyword and if we have that data element tag = tag_for_keyword(name) if tag is not None and tag in self: dict.__delitem__(self, tag) # direct to dict as we know we have key # If not a DICOM name in this dataset, check for regular instance name # can't do delete directly, that will call __delattr__ again elif name in self.__dict__: del self.__dict__[name] # Not found, raise an error in same style as python does else: raise AttributeError(name)
def get_old_anatomy(ds): bpe = tag_for_keyword('BodyPartExamined') ars = tag_for_keyword('AnatomicRegionSequence') old_bpe = None if bpe not in ds else ds[bpe].value old_ars_seq = None if ars not in ds else ds[bpe].value old_ars_val = (None, None, None) if old_ars_seq is not None: if len(old_ars_seq) > 0: old_ars_item = old_ars_seq[0] cm = None if CodeValue not in old_ars_item else \ old_ars_item[CodeValue].value cv = None if CodeMeaning not in old_ars_item else \ old_ars_item[CodeMeanin].value cs = None if CodingSchemeDesignator not in old_ars_item else \ old_ars_item[CodingSchemeDesignato].value old_ars_val = (cv, cm, cs) return old_bpe, old_ars_val
def generalfix_RealWorldValueMappingSequence(ds, log): kw = 'RealWorldValueMappingSequence' tg = tag_for_keyword(kw) if tg in ds: v = ds[tg].value for i, item in enumerate(v): in_key = 'LUTLabel' in_tg = tag_for_keyword(in_key) if in_tg not in item: new_el = DataElementX(in_tg, 'SH', 'Unknown') item[in_tg] = new_el err = "<{}> {}".format(in_key, validate_vr.tag2str(in_tg)) msg = ErrorInfo( "General Fix - The item number {} lacks {}".format(i, err), "fixed by adding a new element with value <{}>".format( new_el.value)) log.append(msg.getWholeMessage())
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 fix_SOPReferencedMacro(ds: Dataset, log: list, suggested_SOPClassUID: dict = {}): kw = 'ReferencedStudySequence' tg = tag_for_keyword(kw) if tg not in ds: return val = ds[tg].value if len(val) == 0: del ds[tg] return i = 0 while i < len(val): item = val[i] msg = ErrorInfo() if 'ReferencedSOPInstanceUID' not in item: msg.msg = 'General Fix - item {}/{} <ReferencedStudySequence>'\ ' lacks <ReferencedSOPInstanceUID> attribute'.format(i + 1, len(val)) msg.fix = 'fixed by removint the item' log.append(msg.getWholeMessage()) val.pop(i) continue if item['ReferencedSOPInstanceUID'].is_empty: msg.msg = 'General Fix - item {}/{} <ReferencedStudySequence>'\ ' holds an empty <ReferencedSOPInstanceUID> attribute'.format(i + 1, len(val)) msg.fix = 'fixed by removint the item' log.append(msg.getWholeMessage()) val.pop(i) continue if 'ReferencedSOPClassUID' not in item or\ item['ReferencedSOPClassUID'].is_empty: uid = item['ReferencedSOPInstanceUID'].value msg.msg = 'General Fix - item {}/{} <ReferencedStudySequence>'\ ' lacks <ReferencedSOPClassUID> attribute'.format(i + 1, len(val)) if uid not in suggested_SOPClassUID: msg.fix = 'fixed by removint the item' log.append(msg.getWholeMessage()) val.pop(i) continue else: msg.fix = 'fixed by querying the attribute '\ 'ReferencedSOPClassUID filling it with {}'.format( suggested_SOPClassUID[uid] ) log.append(msg.getWholeMessage()) item['ReferencedSOPClassUID'].value = suggested_SOPClassUID[ uid] i += 1 if len(val) == 0: msg = ErrorInfo() msg.msg = 'General Fix - Attribute <{}> is empty '.format(kw) msg.fix = 'fixed by removint the attribute' log.append(msg.getWholeMessage()) del ds[tg] return
def fix_type_based_on_dicom_vm(header): exc_keys = [] for key, val in header.items(): try: vr, vm, _, _, _ = DicomDictionary.get(tag_for_keyword(key)) except (ValueError, TypeError): exc_keys.append(key) continue if vr != "SQ": if vm != "1" and not isinstance(val, list): # anything else is a list header[key] = [val] elif vm == "1" and isinstance(val, list): if len(val) == 1: header[key] = val[0] else: if ( vr not in [ "UT", "ST", "LT", "FL", "FD", "AT", "OB", "OW", "OF", "SL", "SQ", "SS", "UL", "OB/OW", "OW/OB", "OB or OW", "OW or OB", "UN", ] and "US" not in vr ): val = cast_castable_floats(val) header[key] = "\\".join([str(item) for item in val]) else: for dataset in val: if isinstance(dataset, dict): fix_type_based_on_dicom_vm(dataset) else: log.warning( "%s SQ list item is not a dictionary - value = %s", key, dataset ) if len(exc_keys) > 0: log.warning( "%s Dicom data elements were not type fixed based on VM", len(exc_keys) )
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 = DataElementX(ttag, vr, '') element.value = element.empty_value ds[ttag] = element reason += "fixed by adding empty attribute" return reason
def fix_ReferencedImageSequence(ds, log: list) -> bool: # This patch is a prticular fixing procedure to replace SOPInstanceUID # with ReferencedSOPInstanceUID and SOPClassUID with ReferencedSOPClassUID fixed = False kw = 'ReferencedImageSequence' tg = Dictionary.tag_for_keyword(kw) if tg not in ds: return True val = ds[tg].value ref_cls_kw = 'ReferencedSOPClassUID' ref_cls_tg = Dictionary.tag_for_keyword(ref_cls_kw) ref_inst_kw = 'ReferencedSOPInstanceUID' ref_inst_tg = Dictionary.tag_for_keyword(ref_inst_kw) i = 0 while i < len(val): item = val[i] if 'SOPInstanceUID' in item: msg = mesgtext_cc.ErrorInfo() msg.msg = "Item {}/{} in <ReferencedImageSequence> holds "\ "<SOPInstanceUID> instead of <ReferencedSOPInstanceUID>".format(i + 1, len(val)) msg.fix = "fixed by changing the attribute into <ReferencedSOPInstanceUID>" log.append(msg.getWholeMessage()) item[ref_inst_tg] = DataElementX(ref_inst_tg, 'UI', item['SOPInstanceUID'].value) del item['SOPInstanceUID'] fixed = True if 'SOPClassUID' in item: msg = mesgtext_cc.ErrorInfo() msg.msg = "Item {}/{} in <ReferencedImageSequence> holds "\ "<SOPClassUID> instead of <ReferencedSOPClassUID>".format(i + 1, len(val)) msg.fix = "fixed by changing the attribute into <ReferencedSOPClassUID>" log.append(msg.getWholeMessage()) item[ref_cls_tg] = DataElementX(ref_cls_tg, 'UI', item['SOPClassUID'].value) del item['SOPClassUID'] fixed = True i += 1 return fixed
def __init__(self, fix_msg: str) -> None: self.message = fix_msg regexp = r'([^-]*)\s-\s(.*):-\>:(.*)\<function(.*)from file:(.*) line_number: (.*)\> \<function(.*)from file:(.*) line_number: (.*)\>' m = re.search(regexp, fix_msg) if m is None: raise MessageError("The message is not fix type") self.type = m.group(1) self.issue = m.group(2) issue_pattern = r'T<([^>]*)>\s(.*)' m_issue = re.search(issue_pattern, self.issue) if m_issue is not None: self.issue_short = m_issue.group(1) self.issue = m_issue.group(2) else: self.issue_short = None self.fix = m.group(3) self.fun1 = m.group(4) file1 = m.group(5) line1 = m.group(6) self.fun2 = m.group(7) file2 = m.group(8) line2 = m.group(9) self.file1_name = os.path.basename(file1) self.file1_link = "{}#L{}".format(file1, line1).replace(current_dir, git_ws) self.file2_name = os.path.basename(file2) self.file2_link = "{}#L{}".format(file2, line2).replace(current_dir, git_ws) element_pattern = r'(Element|attribute|keyword)[=\s]{,5}<([^>]*)>' m = re.search(element_pattern, self.issue) if m is not None: self.attribute = m.group(2) else: self.attribute = None if self.attribute is not None: self.tag = Dic.tag_for_keyword(self.attribute) else: ptrn = r'\(0x([0-9A-Fa-f]{4})[,\s]{,2}0x([0-9A-Fa-f]{4})\)' m = re.search(ptrn, self.issue) if m is not None: self.tag = int(m.group(1) + m.group(2), 16) else: self.tag = None module_pattern = r'(Module|Macro)[=\s]{,5}<([^>]*)>' m = re.search(module_pattern, self.issue) if m is not None: self.module_macro = m.group(2) else: self.module_macro = None
def generalfix_WindowWidth(ds, log): fixed = False wwkw = 'WindowWidth' wwtg = tag_for_keyword(wwkw) if wwtg in ds: if ds[wwtg].value == 0: ds[wwtg].value = 1 err = "<{}> {}".format(wwkw, validate_vr.tag2str(wwtg)) msg = ErrorInfo( "General Fix - Window width {} "\ "is not allowed to be 0".format(err), "fixed by replacing it with 1") log.append(msg.getWholeMessage()) fixed = True return fixed
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 str_to_tag(in_str: str) -> BaseTag: """Convert string representation to pydicom Tag The string can be a keyword, or two numbers separated by a comma """ if in_str[0].isupper(): res = tag_for_keyword(in_str) if res is None: raise ValueError("Invalid element ID: %s" % in_str) return Tag(res) try: group_num, elem_num = [int(x.strip(), 0) for x in in_str.split(",")] except Exception: raise ValueError("Invalid element ID: %s" % in_str) return Tag(group_num, elem_num)
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 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_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 fix_type_based_on_dicom_vm(header): exc_keys = [] for key, val in header.items(): try: vr, vm, _, _, _ = DicomDictionary.get(tag_for_keyword(key)) except (ValueError, TypeError): exc_keys.append(key) continue if vr != 'SQ': if vm != '1' and not isinstance(val, list): # anything else is a list header[key] = [val] else: for dataset in val: fix_type_based_on_dicom_vm(dataset) if len(exc_keys) > 0: log.warning('%s Dicom data elements were not type fixed based on VM', len(exc_keys))
def data_element(self, name): """Return the DataElement corresponding to the element keyword `name`. Parameters ---------- name : str A DICOM element keyword. Returns ------- pydicom.dataelem.DataElement or None For the given DICOM element `keyword`, return the corresponding Dataset DataElement if present, None otherwise. """ tag = tag_for_keyword(name) # Test against None as (0000,0000) is a possible tag if tag is not None: return self[tag] return None
def subfix_RemoveAttrib(ds: Dataset, log: list, error_regexp: str, keyword: str) -> 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], "fixed by removing the attribute") log[i] = msg.getWholeMessage() ErrorOccured = True if ErrorOccured: del ds[keyword] fixed = True return fixed
def data_element(self, name): """Return the DataElement corresponding to the element keyword `name`. Parameters ---------- name : str A DICOM element keyword. Returns ------- pydicom.dataelem.DataElement or None For the given DICOM element `keyword`, return the corresponding Dataset DataElement if present, None otherwise. """ tag = tag_for_keyword(name) # Test against None as (0000,0000) is a possible tag if tag is not None: return self[tag] return None
def _copy_attribute(self, dataset: Dataset, keyword: str) -> None: """Copies an attribute from `dataset` to `self`. Parameters ---------- dataset: pydicom.dataset.Dataset DICOM Data Set from which attribute should be copied keyword: str Keyword of the attribute """ tag = tag_for_keyword(keyword) try: data_element = dataset[tag] logger.debug('copied attribute "{}"'.format(keyword)) except KeyError: logger.debug('skipped attribute "{}"'.format(keyword)) return self.add(data_element)
def fix_type_based_on_dicom_vm(header): exc_keys = [] for key, val in header.items(): try: vr, vm, _, _, _ = DicomDictionary.get(tag_for_keyword(key)) except (ValueError, TypeError): exc_keys.append(key) continue if vr != 'SQ': if vm != '1' and not isinstance(val, list): # anything else is a list header[key] = [val] elif not isinstance(val, list): # To deal with DataElement that pydicom did not read as sequence # (e.g. stored as OB and pydicom parsing them as binary string) exc_keys.append(key) else: for dataset in val: fix_type_based_on_dicom_vm(dataset) if len(exc_keys) > 0: log.warning('%s Dicom data elements were not type fixed based on VM', len(exc_keys))
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 __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 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 __init__(self, issue_msg: str) -> None: self.message = issue_msg regexp = r'.*(Error|Warning)([-\s]*)(.*)' m = re.search(regexp, issue_msg) if m is None: raise MessageError('The issue is not a right type') self.type = m.group(1) self.issue_msg = m.group(3) issue_pattern = r'T<([^>]*)>\s(.*)' m_issue = re.search(issue_pattern, self.issue_msg) if m_issue is not None: self.issue_short = m_issue.group(1) self.issue = m_issue.group(2) else: self.issue_short = None element_pattern = r'(Element|attribute|keyword)[=\s]{,5}<([^>]*)>' m = re.search(element_pattern, issue_msg) if m is not None: self.attribute = m.group(2) else: self.attribute = None if self.attribute is not None: self.tag = Dic.tag_for_keyword(self.attribute) else: ptrn = r"\(0x([0-9A-Fa-f]{4})[,\s]*0x([0-9A-Fa-f]{4})\)" m = re.search(ptrn, issue_msg) if m is not None: self.tag = int(m.group(1) + m.group(2), 16) else: self.tag = None module_pattern = r'(Module|Macro)[=\s]{,5}<([^>]*)>' m = re.search(module_pattern, issue_msg) if m is not None: self.module_macro = m.group(2) else: self.module_macro = None
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)
def Tag(arg, arg2=None): """Create a Tag. General function for creating a Tag in any of the standard forms: * Tag(0x00100015) * Tag('0x00100015') * Tag((0x10, 0x50)) * Tag(('0x10', '0x50')) * Tag(0x0010, 0x0015) * Tag(0x10, 0x15) * Tag(2341, 0x10) * Tag('0xFE', '0x0010') * Tag("PatientName") Parameters ---------- arg : int or str or 2-tuple/list If int or str, then either the group or the combined group/element number of the DICOM tag. If 2-tuple/list then the (group, element) numbers as int or str. arg2 : int or str, optional The element number of the DICOM tag, required when `arg` only contains the group number of the tag. Returns ------- pydicom.tag.BaseTag """ if isinstance(arg, BaseTag): return arg if arg2 is not None: arg = (arg, arg2) # act as if was passed a single tuple if isinstance(arg, (tuple, list)): if len(arg) != 2: raise ValueError("Tag must be an int or a 2-tuple") valid = False if isinstance(arg[0], compat.string_types): valid = isinstance(arg[1], (str, compat.string_types)) if valid: arg = (int(arg[0], 16), int(arg[1], 16)) elif isinstance(arg[0], compat.number_types): valid = isinstance(arg[1], compat.number_types) if not valid: raise ValueError("Both arguments for Tag must be the same type, " "either string or int.") if arg[0] > 0xFFFF or arg[1] > 0xFFFF: raise OverflowError("Groups and elements of tags must each " "be <=2 byte integers") long_value = (arg[0] << 16) | arg[1] # Single str parameter elif isinstance(arg, (str, compat.text_type)): try: long_value = int(arg, 16) if long_value > 0xFFFFFFFF: raise OverflowError("Tags are limited to 32-bit length; " "tag {0!r}" .format(long_value)) except ValueError: # Try a DICOM keyword from pydicom.datadict import tag_for_keyword long_value = tag_for_keyword(arg) if long_value is None: raise ValueError("'{}' is not a valid int or DICOM keyword" .format(arg)) # Single int parameter else: long_value = arg if long_value > 0xFFFFFFFF: raise OverflowError("Tags are limited to 32-bit length; tag {0!r}" .format(long_value)) if long_value < 0: raise ValueError("Tags must be positive.") return BaseTag(long_value)