Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
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)
Ejemplo n.º 17
0
    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)
Ejemplo n.º 18
0
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
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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
Ejemplo n.º 22
0
    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)
Ejemplo n.º 23
0
    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)
Ejemplo n.º 24
0
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
Ejemplo n.º 25
0
 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.")
Ejemplo n.º 26
0
 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.')
Ejemplo n.º 27
0
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
Ejemplo n.º 28
0
    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)
Ejemplo n.º 29
0
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
Ejemplo n.º 30
0
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