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 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 AddLaterality(ds: Dataset, log: list): needlaterality = checkLaterality(ds, 0, ds) old_laterality = None if "Laterality" not in ds else ds['Laterality'].value AddImageLateralityForBoth = False if needlaterality: if old_laterality is None: AddImageLateralityForBoth = True if old_laterality is not None: if old_laterality not in ['L', 'R']: if old_laterality.lower() == 'left': new_laterality = 'L' elif old_laterality.lower() == 'right': new_laterality = 'R' else: new_laterality = None if new_laterality is not None: msg = ErrorInfo() msg.msg = 'General Fix - {}'.format( "<Laterality> holds wrong value") msg.fix = "fixed by setting the <Laterality>"\ "from {} to '{}'".format(old_laterality, new_laterality) log.append(msg.getWholeMessage()) ds['Laterlaity'].value = new_laterality return else: AddImageLateralityForBoth = True else: return elif 'Laterality' in ds: del ds['Laterality']
def generalfix_VM1(ds, log): fixed = False elemsTobeCorrected = [] for key, a in ds.items(): a = ds[key] if key.is_private: continue try: dvr = dictionary_VM(key) except (BaseException): return fixed if dvr != '1': continue if a.VM <= 1: continue if (a.VR != 'LT' or a.VR != 'LO'): continue concat = '/'.join(a.value) ds[key] = DataElementX(key, a.VR, concat) fixed = True err = "<{}> {}".format(a.keyword, validate_vr.tag2str(a.tag)) msg = ErrorInfo( "General Fix - Value Multiplicity for {} "\ "is not allowed to be more than one".format(err), "fixed by concatenating all itmes into one {}".format(concat)) log.append(msg.getWholeMessage()) fixed = True return fixed
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 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 generalfix_RemoveUnwanterPixelAspctRatio(ds: Dataset, log: list) -> bool: fixed = False kw = "PixelAspectRatio" is_one_to_one = False if kw in ds: elem = ds[kw] if type(elem) == MultiValue: if len(elem) == 2: is_one_to_one = (elem.value[0] == elem.value[2]) if (Condition_UnwantedPixelAspectRatioWhenPixelSpacingPresent( ds, ds, ds ) or Condition_UnwantedPixelAspectRatioWhenImagerPixelSpacingPresent( ds, ds, ds ) or Condition_UnwantedPixelAspectRatioWhenNominalScannedPixelSpacingPresent( ds, ds, ds ) or Condition_UnwantedPixelAspectRatioWhenSharedPixelMeasuresMacro( ds, ds, ds ) or Condition_UnwantedPixelAspectRatioWhenPerFramePixelMeasuresMacro( ds, ds, ds) or Condition_UnwantedPixelAspectRatioWhenMPEG2MPHLTransferSyntax( ds, ds, ds) or is_one_to_one): msg = ErrorInfo() msg.msg = '{} Error - {}'.format( ErrorType.BadValue.value, "<PixelAspectRatio> is 1:1 or redundant") msg.fix = "fixed by removing the attribute" log.append(msg.getWholeMessage()) del ds["PixelAspectRatio"] fixed = True return fixed
def generalfix_TrailingNulls(ds: Dataset, log: list) -> bool: fixed = False elemsTobeCorrected = [] for key, a in ds.items(): a = ds[key] if key.is_private: continue if a.VR == 'UI' or a.VR == 'OB' or a.VR == 'OW' or a.VR == 'UN': continue if type(a) == pydicom.dataelem.RawDataElement: a = pydicom.dataelem.DataElement_from_raw(a) if type(a.value) == Sequence: for item in a.value: fixed = fixed or generalfix_TrailingNulls(item, log) elif type(a.value) == Dataset: fixed = fixed or generalfix_TrailingNulls(a.value, log) else: partial_fixed = subfix_HasTrailingNulls(a) if partial_fixed: err = "<{}> {}".format(a.keyword, validate_vr.tag2str(a.tag)) msg = ErrorInfo( "General Fix - Trailing null bytesz", "fixed by removing the trailing null bytes for {}".format( err)) log.append(msg.getWholeMessage()) elemsTobeCorrected.append(a) fixed = True return fixed
def verifyType2C(ds: Dataset, module: str, element: str, verbose: bool, log: list, fix_trivials: bool, condition_function, mbpo: bool, parent_ds: Dataset, root_ds: Dataset, multiplicityMin: uint32, multiplicityMax: uint32) -> bool: # Type 2C - Conditional Data Element (May be Empty) err_not_exists = False err_vr = False err_vm = False reason = "" if condition_function == 0: conditionNotSatisfied = True else: conditionNotSatisfied = not condition_function(ds, parent_ds, root_ds) if element in ds: elem = ds[element] else: if not conditionNotSatisfied: reason = "Error - T<{}> {}" reason = reason.format(ErrorType.Type2CAbsent.value, MMsgDC("MissingAttribute")) err_not_exists = True if not err_not_exists: if condition_function != 0 and conditionNotSatisfied and not mbpo: reason = "Error - T<{}> {}" reason = reason.format( ErrorType.Type2CPresent.value, MMsgDC( "AttributePresentWhenConditionUnsatisfiedWithoutMayBePresentOtherwise" )) if fix_trivials: tmp = ErrorInfo(reason + "{} {}", fix_by_removing(ds, element)) reason = tmp.getWholeMessage() if elem.is_empty: if not verifyVR(elem, module, element, verbose, log): reason = "Error - T<{}> {}" reason = reason.format(ErrorType.Type2CVR.value, MMsgDC("BadValueRepresentation")) err_vr = True else: if not verifyVM(elem, module, element, verbose, log, multiplicityMin, multiplicityMax, "source"): reason = "Error - T<{}> {}" reason = reason.format( ErrorType.Type2CVM.value, MMsgDC("BadAttributeValueMultiplicity")) err_vm = True if len(reason) != 0: if fix_trivials: ViolationMessage(reason, MMsgDC("Type2C"), module, element, log, verbose, True) else: ViolationMessage(reason, MMsgDC("Type2C"), module, element, log, verbose) else: ValidMessage(MMsgDC("Type2C"), module, element, log, verbose) return len(reason) == 0
def generalfix_RemoveEmptyCodes(parent_ds: Dataset, log: list, kw_to_be_removed: list = []) -> bool: global iod_dict if 'SOPClassUID' in parent_ds: iod_dict = get_full_attrib_list(parent_ds) fixed = False for key, elem in parent_ds.items(): elem = parent_ds[key] if type(elem) == pydicom.dataelem.RawDataElement: elem = pydicom.dataelem.DataElement_from_raw(elem) if type(elem.value) == Sequence: if elem.keyword.endswith("CodeSequence"): if iod_dict is None or elem.keyword not in iod_dict: type_ = None else: type_ = iod_dict[elem.keyword][0]['type'] if type_ != '1' or type_ != '1C' or type_ != '2'\ or type_ != '2C': fixed = fixed or subfix_checkandfixBasicCodeSeq(elem, log) if elem.is_empty: kw_to_be_removed.append((parent_ds, elem.keyword)) else: for (item, idx) in zip(elem.value, range(0, len(elem.value))): fixed = fixed or generalfix_RemoveEmptyCodes( item, log, kw_to_be_removed) elif type(elem.value) == Dataset: fixed = fixed or generalfix_RemoveEmptyCodes( elem.value, log, kw_to_be_removed, ) if 'SOPClassUID' in parent_ds: for ddss, k in kw_to_be_removed: if k in ddss: a = ddss[k] # print('{} is present and going to be removed'.format(k)) else: # print('{} is not present and going to continue anyway'.format(k)) continue if iod_dict is None or k not in iod_dict: type_ = None else: type_ = iod_dict[k][0]['type'] if type_ != '1' or type_ != '1C' or type_ != '2' or type_ != '2C': err = "{} <{}>".format(validate_vr.tag2str(a.tag), a.keyword) msg = ErrorInfo( "General Fix - Attribute type SQ has no items", "fixed by removing the whole attribute for {}".format(err)) log.append(msg.getWholeMessage()) del ddss[k]
def check(v, n, N, a): u = v.upper() if u != v: err = "<{}> {}".format(a.keyword, validate_vr.tag2str(a.tag)) msg = ErrorInfo( "General Fix - {} lower case is not allowed for Code "\ "String itme number {}/ {}".format(err, n, N), "fixed by capitalizing the value from {} to {}".format( v, u)) log.append(msg.getWholeMessage()) return u else: return 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 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 verifyType2(ds: Dataset, module: str, element: str, verbose: bool, log: list, fix_trivials: bool, multiplicityMin: uint32, multiplicityMax: uint32) -> bool: # Type 2 - Required Data Element (May be Empty) err_vr = False err_vm = False err_not_exists = False reason = "" if element in ds: elem = ds[element] else: reason = "Error - T<{}> {}" reason = reason.format(ErrorType.Type2.value, MMsgDC("MissingAttribute")) err_not_exists = True if fix_trivials: # add an empty attrib tmp = ErrorInfo(reason + "{} {}", fix_ByAddingEmptyAttrib(ds, element)) reason = tmp.getWholeMessage() if not err_not_exists: # do not check emptiness if not elem.is_empty: if not verifyVR(elem, module, element, verbose, log): reason = "Error - T<{}> {}" reason = reason.format(ErrorType.Type2VR.value, MMsgDC("BadValueRepresentation")) err_vr = True else: if not verifyVM(elem, module, element, verbose, log, multiplicityMin, multiplicityMax): reason = "Error - T<{}> {}" reason = reason.format( ErrorType.Type2VM.value, MMsgDC("BadAttributeValueMultiplicity")) err_vm = True if len(reason) != 0: if fix_trivials: ViolationMessage(reason, MMsgDC("Type2"), module, element, log, verbose, True) else: ViolationMessage(reason, MMsgDC("Type2"), module, element, log, verbose) else: ValidMessage(MMsgDC("Type2"), module, element, log, verbose) return len(reason) == 0
def check(v, n, N, a): v = str(v) if len(v) > 16: f = float(v) u = '{:1.10E}'.format(f) n = 10 while len(u) > 16 and n > 0: n -= 1 ptrn = '{{:1.{}E}}'.format(n) u = ptrn.format(f) err = "<{}> {}".format(a.keyword, validate_vr.tag2str(a.tag)) msg = ErrorInfo( "General Fix - {} value length greater than 16 is not allowed"\ "for DS value representation in itme number {}/ {}".format( err, n, N), "fixed by modifying the precision from {} to {}".format( v, u)) log.append(msg.getWholeMessage()) return u else: return None
def generalfix_MisplacedAttributes(ds: Dataset, log: list): paths = {} current_folder = os.path.dirname(__file__) with open(os.path.join(current_folder, 'config.json')) as json_file: fix_config = json.load(json_file) get_all_kw_paths(ds, [], paths) standard_ds = get_full_attrib_list(ds) not_in_std = {} misplaced = {} for kw, pp in paths.items(): tg = tag_for_keyword(kw) if kw not in standard_ds: not_in_std[kw] = pp else: std__ = standard_ds[kw] if len(std__) > 1: continue for parent, path in pp: correct_path = std__[0]['path'] if path != correct_path: if kw not in misplaced: misplaced[kw] = [(parent, path, correct_path)] else: misplaced[kw].append((parent, path, correct_path)) for kw, val in misplaced.items(): if not fix_config["MisplacedAttributes"][ "DisplaceIfAttributeIsInWrongPath"]: break for parent, path, correct_path in val: # print(parent, path, correct_path) delem = parent[kw] del parent[kw] new_paretn = ds inner_kw = kw parent_ds = ds for p in correct_path: if p not in parent_ds: new_ds = Dataset() parent_ds[p] = DataElementX(tag_for_keyword(p), 'SQ', DataElementSequence([new_ds])) elif parent_ds[p].is_empty: new_ds = Dataset() parent_ds[p].value = DataElementSequence([new_ds]) parent_ds = parent_ds[p].value[0] if kw not in parent_ds: parent_ds[kw] = delem msg = ErrorInfo( "General Fix - The DICOM attribute <{}> is not in " "correct place. " "current-path = {} vs. correct-path = {}".format( kw, path, correct_path), "fixed by attribute displacement") else: msg = ErrorInfo( "General Fix - The DICOM attribute <{}> is not in " "correct place. " "current-path = {} vs. correct-path = {}".format( kw, path, correct_path), "couldn't fix because there is already " "an attribute in correct place") log.append(msg.getWholeMessage()) for kw, val in not_in_std.items(): if not fix_config["MisplacedAttributes"]["RemoveIfAttributeIsNotInIOD"]: break for parent, path in val: if kw in parent: del parent[kw] msg = ErrorInfo( "General Fix - The keyword <{}> is not in " "standard DICOM IOD".format(kw), "fixed by removing the attribute") log.append(msg.getWholeMessage())