def blank_tag(dicom, field): """blank tag calls update_tag with value set to an empty string. If the tag cannot be found, warns the user and doesn't touch (in case of imaging data, or not found) Parameters ========== dicom: the pydicom.dataset Dataset (pydicom.read_file) field: the name of the field to blank """ # Case 1: We are provided a field that is a string, retrieve data element if isinstance(field, str): element = dicom.data_element(field) else: element = dicom.get(field) if element is not None: # Assert we have a data element if not isinstance(element, DataElement): bot.warning("Issue parsing %s as a DataElement, not blanked." % field) return dicom # We cannot blank VR types of US or SS if element.VR not in ["US", "SS"]: return update_tag(dicom, field, "") bot.warning("Cannot determine tag for %s, skipping blank." % field) return dicom
def add_field(self, field, value): """add a field to the dicom. If it's already present, update the value. """ value = parse_value(item=self.lookup, value=value, field=field, dicom=self.dicom) # Assume we don't want to add an empty value if value is not None: # If provided a field object, create based on keyword or tag identifer name = field if isinstance(field, DicomField): name = field.element.keyword or field.stripped_tag # Generate a tag item, add if it's a name found in the dicom dictionary tag = get_tag(name) # Second try, it might be a private (or other numerical) string identifier if not tag: tag = add_tag(name) if tag: uid = str(tag["tag"]) element = DataElement(tag["tag"], tag["VR"], value) self.dicom.add(element) self.fields[uid] = DicomField(element, name, uid) bot.warning("Cannot find tag for field %s, skipping." % name)
def find_deid(path=None): '''find_deid is a helper function to load_deid to find a deid file in a folder, or return the path provided if it is the file. Parameters ========== path: a path on the filesystem. If not provided, will assume PWD. ''' if path is None: path = os.getcwd() # The user has provided a directory if os.path.isdir(path): contenders = [ "%s/%s" % (path, x) for x in os.listdir(path) if x.startswith('deid') ] if len(contenders) == 0: bot.error("No deid settings files found in %s, exiting." % (path)) sys.exit(1) elif len(contenders) > 1: bot.warning("Multiple deid files found in %s, will use first." % (path)) path = contenders[0] # We have a file path at this point if not os.path.exists(path): bot.error("Cannot find deid file %s, exiting." % (path)) sys.exit(1) return path
def perform_action(self, field, value, action): """perform action takes an action (dictionary with field, action, value) and performs the action on the loaded dicom. Parameters ========== fields: if provided, a filtered list of fields for expand action: the action from the parsed deid to take "field" (eg, PatientID) the header field to process "action" (eg, REPLACE) what to do with the field "value": if needed, the field from the response to replace with """ # Validate the action if action not in valid_actions: bot.warning("%s in not a valid choice. Defaulting to blanked." % action) action = "BLANK" # A values list returns fields with the value (can be private tags if not removed) if re.search("^values", field): values = self.lookup.get(re.sub("^values:", "", field), []) fields = self.find_by_values(values=values) # A fields list is used vertabim # In expand_field_expression below, the stripped_tag is being passed in to field. At this point, # expanders for %fields lists have already been processed and each of the contenders is an # identified, unique field. It is important to use stripped_tag at this point instead of # element.keyword as private tags will not have a keyword and can only be identified by tag number. elif re.search("^fields", field): listing = {} for uid, contender in self.lookup.get( re.sub("^fields:", "", field), {}).items(): listing.update( expand_field_expression( field=contender.stripped_tag, dicom=self.dicom, contenders=self.fields, )) fields = listing else: # If there is an expander applied to field, we iterate over fields = expand_field_expression(field=field, dicom=self.dicom, contenders=self.fields) # If it's an addition, we might not have fields if action == "ADD": self.add_field(field, value) # Otherwise, these are operations on existing fields else: """clone the fields dictionary. delete actions must also delete from the fields dictionary. performing the clone and iterating on the clone allows the deletions while preventing a runtime error - "dictionary changed size during iterations" """ temp_fields = deepcopy(fields) for uid, field in temp_fields.items(): self._run_action(field=field, action=action, value=value)
def extract_fields_list(dicom, actions, fields=None): """Given a list of actions for a named group (a list) extract values from the dicom based on the list of actions provided. This function always returns a list intended to update some lookup to be used to further process the dicom. """ subset = {} if not fields: fields = get_fields(dicom) for action in actions: if action["action"] == "FIELD": subset.update( expand_field_expression( field=action["field"], dicom=dicom, contenders=fields ) ) else: bot.warning( "Unrecognized action %s for fields list extraction." % action["action"] ) return subset
def load_combined_deid(deids): '''load one or more deids, either based on a path or a tag Parameters ========== deids: should be a custom list of deids ''' if not isinstance(deids, list): bot.warning("load_combined_deids expects a list.") sys.exit(1) found_format = None deid = None for single_deid in deids: # If not a tag or path, returns None next_deid = get_deid(tag=single_deid, exit_on_fail=False, quiet=True, load=True) if next_deid is not None: # Formats must match if found_format is None: found_format = next_deid['format'] else: if found_format != next_deid['format']: bot.error('Mismatch in deid formats, %s and %s' % (found_format, next_deid['format'])) sys.exit(1) # If it's the first one, use as starter template if deid is None: deid = next_deid else: # Update filter, appending to end to give first preference if "filter" in next_deid: if "filter" not in deid: deid['filter'] = next_deid['filter'] else: for name, group in next_deid['filter'].items(): if name in deid['filter']: deid['filter'][ name] = deid['filter'][name] + group else: deid['filter'][name] = group if "header" in next_deid: if "header" not in deid: deid['header'] = next_deid['header'] else: deid['header'] = deid['header'] + next_deid['header'] else: bot.warning('Problem loading %s, skipping.' % single_deid) return deid
def parse_value(item, value, field=None): """parse_value will parse the value field of an action, either returning: 1. the string (string or from function) 2. a variable looked up (var:FieldName) """ # If item is passed as None if item is None: item = dict() # Does the user want a custom value? if re.search("[:]", value): value_type, value_option = value.split(":", 1) if value_type.lower() == "var": # If selected variable not provided, skip if value_option not in item: return None return item[value_option] # The user is providing a specific function elif value_type.lower() == "func": if value_option not in item: bot.warning("%s not found in item lookup %s" % (value_option)) return None # item is the lookup, value from the recipe, and field # If the user writes a custom function for private, will need # to convert field to str() in the function. return item[value_option](item, value, field) bot.warning("%s is not a valid value type, skipping." % (value_type)) return None return value
def extract_sequence(sequence, prefix=None): """return a pydicom.sequence.Sequence recursively as a flattened list of items. For example, a nested FieldA and FieldB would return as: {'FieldA__FieldB': '111111'} Parameters ========== sequence: the sequence to extract, should be pydicom.sequence.Sequence prefix: the parent name """ items = {} for item in sequence: # If it's a Dataset, we need to further unwrap it if isinstance(item, Dataset): for subitem in item: items.update(extract_item(subitem, prefix=prefix)) # Private tags are DataElements elif isinstance(item, DataElement): items[item.tag] = extract_item(item, prefix=prefix) else: bot.warning( "Unrecognized type %s in extract sequences, skipping." % type(item)) return items
def _perform_action(dicom, field, action, value=None, item=None): '''_perform_action is the base function for performing an action. perform_action (above) typically is called using a loaded deid, and perform_addition is typically done via an addition in a config Both result in a call to this function. If an action fails or is not done, None is returned, and the calling function should handle this. ''' done = False result = None if action not in valid_actions: bot.warning('%s in not a valid choice [%s]. Defaulting to blanked.' % (action, ".".join(valid_actions))) action = "BLANK" if field in dicom and action != "ADD": # Blank the value if action == "BLANK": result = blank_tag(dicom, field) done = True # Code the value with something in the response elif action == "REPLACE": value = parse_value(item, value) if value is not None: # If we make it here, do the replacement done = True result = update_tag(dicom, field=field, value=value) # Code the value with something in the response elif action == "JITTER": value = parse_value(item, value) if value is not None: # Jitter the field by the supplied value done = True result = jitter_timestamp(dicom, field=field, value=value) # Do nothing. Keep the original elif action == "KEEP": done = True result = dicom # Remove the field entirely elif action == "REMOVE": result = remove_tag(dicom, field) done = True if not done: bot.warning("%s %s not done" % (action, field)) elif action == "ADD": value = parse_value(item, value) if value is not None: result = add_tag(dicom, field, value) return result
def parse_value(item, value, field=None): '''parse_value will parse the value field of an action, either returning: 1. the string (string or from function) 2. a variable looked up (var:FieldName) ''' # If item is passed as None if item is None: item = dict() # Does the user want a custom value? if re.search('[:]', value): value_type, value_option = value.split(':') if value_type.lower() == "var": # If selected variable not provided, skip if value_option not in item: return None return item[value_option] # The user is providing a specific function elif value_type.lower() == "func": if value_option not in item: bot.warning('%s not found in item lookup %s' % (value_option)) return None return item[value_option](item, value, field) bot.warning('%s is not a valid value type, skipping.' % (value_type)) return None return value
def get_figure(self, show=False, image_type="cleaned", title=None): """get a figure for an original or cleaned image. If the image was already clean, it is simply a copy of the original. If show is True, plot the image. If a 4d image is discovered, we use randomly choose a slice. """ if hasattr(self, image_type): _, ax = plt.subplots(figsize=(10, 6)) # Retrieve full image image = getattr(self, image_type) # Handle 4d data by choosing one dimension if len(image.shape) == 4: channel = random.choice(range(image.shape[3])) bot.warning( "Image detected as 4d, will sample channel %s and middle slice" % channel ) image = image[math.floor(image.shape[0] / 2), :, :, channel] ax.imshow(image, cmap=self.cmap) if title is not None: plt.title(title, fontdict=self.font) if show is True: plt.show() return plt
def apply_filter(dicom,field,filter_name,value): '''essentially a switch statement to apply a filter to a dicom file. ''' filter_name = filter_name.lower().strip() if filter_name == "contains": return dicom.contains(field,value) if filter_name == "notcontains": return dicom.notContains(field,value) elif filter_name == "equals": return dicom.equals(field,value) elif filter_name == "missing": return dicom.missing(field) elif filter_name == "present": return not dicom.missing(field) elif filter_name == "empty": return dicom.empty(field) elif filter_name == "notequals": return dicom.notEquals(field,value) bot.warning("%s is not a valid filter name, returning False" %filter_name) return False
def validate_dicoms(dcm_files, force=False): """validate dicoms will test opening one or more dicom files, and return a list of valid files. Parameters ========== dcm_files: one or more dicom files to test """ if not isinstance(dcm_files, list): dcm_files = [dcm_files] valids = [] bot.debug("Checking %s dicom files for validation." % (len(dcm_files))) for dcm_file in dcm_files: try: with open(dcm_file, "rb") as filey: read_file(filey, force=force) valids.append(dcm_file) except: bot.warning( "Cannot read input file {0!s}, skipping.".format(dcm_file)) bot.debug("Found %s valid dicom files" % (len(valids))) return valids
def perform_action(dicom, action, item=None, fields=None, return_seen=False): '''perform action takes Parameters ========== dicom: a loaded dicom file (pydicom read_file) item: a dictionary with keys as fields, values as values fields: if provided, a filtered list of fields for expand action: the action from the parsed deid to take "deid" (eg, PatientID) the header field to process "action" (eg, REPLACE) what to do with the field "value": if needed, the field from the response to replace with ''' field = action.get('field') # e.g: PatientID, endswith:ID value = action.get('value') # "suid" or "var:field" action = action.get('action') # "REPLACE" # Validate the action if action not in valid_actions: bot.warning('%s in not a valid choice. Defaulting to blanked.' % action) action = "BLANK" # If there is an expander applied to field, we iterate over fields = expand_field_expression(field=field, dicom=dicom, contenders=fields) # Keep track of fields we have seen seen = [] # An expanded field must END with that field expanded_regexp = "__%s$" % field for field in fields: seen.append(field) # Handle top level field _perform_action(dicom=dicom, field=field, item=item, action=action, value=value) # Expand sequences if item: expanded_fields = [x for x in item if re.search(expanded_regexp, x)] # FieldA__FieldB for expanded_field in expanded_fields: _perform_expanded_action(dicom=dicom, expanded_field=expanded_field, item=item, action=action, value=value) if return_seen: return dicom, seen return dicom
def parse_label(section, config, section_name, members, label=None): """parse label will add a (optionally named) label to the filter section, including one or more criteria Parameters ========== section: the section name (e.g., header) must be one in sections config: the config (dictionary) parsed thus far section_name: an optional name for a section members: the lines beloning to the section/section_name label: an optional name for the group of commands """ criteria = {"filters": [], "coordinates": []} if label is not None: label = label.replace("label", "", 1).split("#")[0].strip() criteria["name"] = label while len(members) > 0: member = members.pop(0).strip() # We have a coordinate line (coordinates to remove, mask 0) if member.lower().startswith("coordinates"): coordinate = member.replace("coordinates", "").strip() criteria["coordinates"].append([0, coordinate]) continue # Coordinates to keep (mask 1) elif member.lower().startswith("keepcoordinates"): coordinate = member.replace("keepcoordinates", "").strip() criteria["coordinates"].append([1, coordinate]) continue operator = None entry = None if member.startswith("+"): operator = "and" member = member.replace("+", "", 1).strip() elif member.startswith("||"): operator = "or" member = member.replace("||", "", 1).strip() # Skip over comments if member.startswith("#"): continue # Now that operators removed, parse member if not member.lower().startswith(filters): bot.warning("%s filter is not valid, skipping." % member.lower()) else: # Returns single member with field, values, operator, # Or if multiple or/and in statement, a list entry = parse_member(member, operator) if entry is not None: criteria["filters"].append(entry.copy()) config[section][section_name].append(criteria) return config
def extract_values_list(dicom, actions, fields=None): """Given a list of actions for a named group (a list) extract values from the dicom based on the list of actions provided. This function always returns a list intended to update some lookup to be used to further process the dicom. """ values = set() # The function can be provided fields to save re-parsing if not fields: fields = get_fields(dicom) for action in actions: # Extract some subset of fields based on action subset = expand_field_expression( field=action["field"], dicom=dicom, contenders=fields ) # Just grab the entire value string for a field, no parsing if action["action"] == "FIELD": for uid, field in subset.items(): if field.element.value not in ["", None]: values.add(field.element.value) # Split action, can optionally have a "by" and/or minlength parameter elif action["action"] == "SPLIT": # Default values for split are length 1 and character empty space bot.debug("Parsing action %s" % action) split_by = " " minlength = 1 if "value" in action: for param in action["value"].split(";"): param_name, param_val = param.split("=") param_name = param_name.strip() param_val = param_val.strip() # Set a custom parameter legnth if param_name == "minlength": minlength = int(param_val) bot.debug("Minimum length set to %s" % minlength) elif param_name == "by": split_by = param_val.strip("'").strip('"') bot.debug("Splitting value set to %s" % split_by) for uid, field in subset.items(): new_values = (str(field.element.value) or "").split(split_by) for new_value in new_values: if len(new_value) >= minlength: values.add(new_value) else: bot.warning( "Unrecognized action %s for values list extraction." % action["action"] ) return list(values)
def __init__(self, deid=None, base=False, default_base='dicom'): # If deid is None, use the default if deid is None: bot.warning('No specification, loading default base deid.%s' % default_base) base = True self._init_deid(deid, base=base, default_base=default_base)
def _perform_action(field, item, action, value=None): '''_perform_action is the base function for performing an action. It is equivalent to the dicom module version, except we work with dictionary field/value instead of dicom headers. If no action is done, None is returned ''' done = False if action not in valid_actions: bot.warning('%s in not a valid choice [%s]. Defaulting to blanked.' % (action, ".".join(valid_actions))) action = "BLANK" if field in item and action != "ADD": # Blank the value if action == "BLANK": item[field] = "" done = True # Code the value with something in the response elif action == "REPLACE": value = parse_value(item, value) if value is not None: done = True item[field] = value else: bot.warning("REPLACE failed for %s" % field) # Code the value with something in the response elif action == "JITTER": value = parse_value(item, value) if value is not None: done = True item = jitter_timestamp(field=field, value=value, item=item) else: bot.warning('JITTER failed for %s' % field) # Do nothing. Keep the original elif action == "KEEP": done = True bot.debug('KEEP %s' % field) # Remove the field entirely elif action == "REMOVE": del item[field] done = True if not done: bot.warning("%s not done for %s" % (action, field)) elif action == "ADD": value = parse_value(item, value) if value is not None: item[field] = value else: bot.warning('ADD failed for %s' % field) return item
def jitter_timestamp(dicom, field, value): """if present, jitter a timestamp in dicom field "field" by number of days specified by "value" The value can be positive or negative. Parameters ========== dicom: the pydicom Dataset field: the field with the timestamp value: number of days to jitter by. Jitter bug! """ if not isinstance(value, int): value = int(value) original = dicom.get(field) if original is not None: # Create default for new value new_value = None # If we have a string, we need to create a DataElement if isinstance(field, str): dcmvr = dicom.data_element(field).VR else: dcmvr = dicom.get(field).VR original = dicom.get(field).value # DICOM Value Representation can be either DA (Date) DT (Timestamp), # or something else, which is not supported. if dcmvr == "DA": # NEMA-compliant format for DICOM date is YYYYMMDD new_value = get_timestamp(original, jitter_days=value, format="%Y%m%d") elif dcmvr == "DT": # NEMA-compliant format for DICOM timestamp is # YYYYMMDDHHMMSS.FFFFFF&ZZXX new_value = get_timestamp(original, jitter_days=value, format="%Y%m%d%H%M%S.%f%z") else: # Do nothing and issue a warning. bot.warning("JITTER not supported for %s with VR=%s" % (field, dcmvr)) if new_value is not None and new_value != original: # Only update if there's something to update AND there's been change dicom = update_tag(dicom, field=field, value=new_value) return dicom
def blank_tag(dicom, field): '''blank tag calls update_tag with value set to an empty string. If the tag cannot be found, warns the user and doesn't touch (in case of imaging data, or not found) ''' # We cannot blank VR types of US or SS element = dicom.data_element(field) if element is not None: if element.VR not in ['US', 'SS']: return update_tag(dicom, field, "") bot.warning('Cannot determine tag for %s, skipping blank.' % field) return dicom
def replace_identifiers( dicom_files, ids=None, deid=None, save=False, overwrite=False, output_folder=None, force=True, config=None, strip_sequences=False, remove_private=False, ): """replace identifiers using pydicom, can be slow when writing and saving new files. If you want to replace sequences, they need to be extracted with get_identifiers and expand_sequences to True. """ if not isinstance(dicom_files, list): dicom_files = [dicom_files] # Warn the user that we use the default deid recipe if not deid: bot.warning("No deid specification provided, will use defaults.") # ids (a lookup) is not required ids = ids or {} # Parse through dicom files, update headers, and save updated_files = [] for dicom_file in dicom_files: parser = DicomParser(dicom_file, force=force, config=config, recipe=deid) # If a custom lookup was provided, update the parser if parser.dicom_file in ids: parser.lookup.update(ids[parser.dicom_file]) parser.parse(strip_sequences=strip_sequences, remove_private=remove_private) # Save to file, otherwise return updated objects if save is True: ds = save_dicom( dicom=parser.dicom, dicom_file=parser.dicom_file, output_folder=output_folder, overwrite=overwrite, ) updated_files.append(ds) else: updated_files.append(parser.dicom) return updated_files
def blank_field(self, field): """Blank a field""" element = self.get_nested_field(field) # Assert we have a data element, and can blank a string if element: if not isinstance(element, DataElement): bot.warning("Issue parsing %s as a DataElement, not blanked." % field) elif element.VR in ["US", "SS"]: element.value = "" else: bot.warning("Unrecognized VR for %s, skipping blank." % field)
def perform_action(self, field, value, action): """perform action takes an action (dictionary with field, action, value) and performs the action on the loaded dicom. Parameters ========== fields: if provided, a filtered list of fields for expand action: the action from the parsed deid to take "field" (eg, PatientID) the header field to process "action" (eg, REPLACE) what to do with the field "value": if needed, the field from the response to replace with """ # Validate the action if action not in valid_actions: bot.warning("%s in not a valid choice. Defaulting to blanked." % action) action = "BLANK" # A values list returns fields with the value (can be private tags if not removed) if re.search("^values", field): values = self.lookup.get(re.sub("^values:", "", field), []) fields = self.find_by_values(values=values) # A fields list is used vertabim elif re.search("^fields", field): listing = {} for uid, contender in self.lookup.get( re.sub("^fields:", "", field), {}).items(): listing.update( expand_field_expression( field=contender.element.keyword, dicom=self.dicom, contenders=self.fields, )) fields = listing else: # If there is an expander applied to field, we iterate over fields = expand_field_expression(field=field, dicom=self.dicom, contenders=self.fields) # If it's an addition, we might not have fields if action == "ADD": self.add_field(field, value) # Otherwise, these are operations on existing fields else: for uid, field in fields.items(): self._run_action(field=field, action=action, value=value)
def parse_label(section,config,section_name,members,label=None): '''parse label will add a (optionally named) label to the filter section, including one or more criteria Parameters ========== section: the section name (e.g., header) must be one in sections config: the config (dictionary) parsed thus far section_name: an optional name for a section members: the lines beloning to the section/section_name label: an optional name for the group of commands ''' criteria = {'filters':[], 'coordinates':[]} if label is not None: label = (label.replace('label','',1) .split('#')[0] .strip()) criteria['name'] = label while len(members) > 0: member = members.pop(0).strip() # We have a coordinate line if member.lower().startswith('coordinates'): coordinate = member.lower().replace('coordinates','').strip() criteria['coordinates'].append(coordinate) continue operator = None entry = None if member.startswith('+'): operator = 'and' member = member.replace('+','',1).strip() elif member.startswith('||'): operator = 'or' member = member.replace('||','',1).strip() # Now that operators removed, parse member if not member.lower().startswith(filters): bot.warning('%s filter is not valid, skipping.' %member.lower()) else: # Returns single member with field, values, operator, # Or if multiple or/and in statement, a list entry = parse_member(member, operator) if entry is not None: criteria['filters'].append(entry.copy()) config[section][section_name].append(criteria) return config
def save_png(self, output_folder=None, image_type="cleaned", title=None): """save an original or cleaned dicom as png to disk. Default image_format is "cleaned" and can be set to "original." If the image was already clean (not flagged) the cleaned image is just a copy of original """ if hasattr(self, image_type): png_file = self._get_clean_name(output_folder, "png") plt = self.get_figure(image_type=image_type, title=title) plt.savefig(png_file) plt.close() return png_file else: bot.warning("use detect() --> clean() before saving is possible.")
def save_dicom(self, output_folder=None, image_type="cleaned"): '''save a cleaned dicom to disk. We expose an option to save an original (change image_type to "original" to be consistent, although this is not incredibly useful given it would duplicate the original data. ''' # Having clean also means has dicom image if hasattr(self, image_type): dicom_name = self._get_clean_name(output_folder) dicom = read_file(self.dicom_file, force=True) dicom.PixelData = self.clean.tostring() dicom.save_as(dicom_name) return dicom_name else: bot.warning('use detect() --> clean() before saving is possible.')
def jitter_timestamp(dicom, field, value): '''if present, jitter a timestamp in dicom field "field" by number of days specified by "value" The value can be positive or negative. Parameters ========== dicom: the pydicom Dataset field: the field with the timestamp value: number of days to jitter by. Jitter bug! ''' if not isinstance(value, int): value = int(value) original = dicom.get(field, None) if original is not None: dcmvr = dicom.data_element(field).VR ''' DICOM Value Representation can be either DA (Date) DT (Timestamp), or something else, which is not supported. ''' if (dcmvr == 'DA'): ''' NEMA-compliant format for DICOM date is YYYYMMDD ''' new_value = get_timestamp(original, jitter_days=value, format='%Y%m%d') elif (dcmvr == 'DT'): ''' NEMA-compliant format for DICOM timestamp is YYYYMMDDHHMMSS.FFFFFF&ZZXX ''' new_value = get_timestamp(original, jitter_days=value, format='%Y%m%d%H%M%S.%f%z') else: # Do nothing and issue a warning. new_value = None bot.warning("JITTER not supported for %s with VR=%s" % (field, dcmvr)) if (new_value is not None and new_value != original): # Only update if there's something to update AND there's been change dicom = update_tag(dicom, field=field, value=new_value) return dicom
def _run_action(self, field, action, value=None): """perform_action (above) typically is called using a loaded deid, and _run_addition is typically done via an addition in a config Both result in a call to this function. If an action fails or is not done, None is returned, and the calling function should handle this. """ # Blank the value if action == "BLANK": self.blank_field(field) # Unlikely to be called from perform_action elif action == "ADD": self.add_field(field, value) # Code the value with something in the response elif action == "REPLACE": self.replace_field(field, value) # Code the value with something in the response elif action == "JITTER": value = parse_value(item=self.lookup, dicom=self.dicom, value=value, field=field) if value is not None: # Jitter the field by the supplied value jitter_timestamp(dicom=self.dicom, field=field.element.keyword, value=value) else: bot.warning("JITTER %s unsuccessful" % field) # elif "KEEP" --> Do nothing. Keep the original # Remove the field entirely elif action == "REMOVE": # If a value is defined, parse it (could be filter) do_removal = True if value != None: do_removal = parse_value(item=self.lookup, dicom=self.dicom, value=value, field=field) if do_removal == True: self.delete_field(field)
def _perform_action(dicom, field, action, value=None, item=None): '''_perform_action is the base function for performing an action. perform_action (above) typically is called using a loaded deid, and perform_addition is typically done via an addition in a config Both result in a call to this function. If an action fails or is not done, None is returned, and the calling function should handle this. ''' if action not in valid_actions: bot.warning('''%s in not a valid choice [%s]. Defaulting to blanked.''' % (action, ".".join(valid_actions))) action = "BLANK" if field in dicom and action != "ADD": # Blank the value if action == "BLANK": dicom = blank_tag(dicom, field) # Code the value with something in the response elif action == "REPLACE": newvalue = parse_value(item, value, field) if newvalue is not None: # If we make it here, do the replacement dicom = update_tag(dicom, field=field, value=newvalue) else: bot.warning("REPLACE %s unsuccessful" % field) elif action == "SCREEN": newvalue = parse_value(item, value, field) if newvalue is not None: # If we make it here, do the replacement dicom = update_tag(dicom, field=field, value=newvalue) bot.warning("SCREEN identified a hit in %s" % field) # Code the value with something in the response elif action == "JITTER": value = parse_value(item, value, field) if value is not None: # Jitter the field by the supplied value dicom = jitter_timestamp(dicom=dicom, field=field, value=value) else: bot.warning("JITTER %s unsuccessful" % field) # elif "KEEP" --> Do nothing. Keep the original # Remove the field entirely elif action == "REMOVE": dicom = remove_tag(dicom, field) elif action == "ADD": value = parse_value(item, value, field) if value is not None: dicom = add_tag(dicom, field, value, quiet=True) return dicom
def parse_value(item, value): '''parse_value will parse the value field of an action, either returning the string, or a variable looked up in the case of var:FieldName ''' # Does the user want a custom value? if re.search('[:]', value): value_type, value_option = value.split(':') if value_type.lower() == "var": # If selected variable not provided, skip if value_option not in item: return None return item[value_option] bot.warning('%s is not a valid value type, skipping.' % (value_type)) return None return value