in python/de-identifier/research_pacs/de_identifier/dicom.py [0:0]
def _validate_and_adapt_config_file(self):
"""
Validate the content of the config file, and prepare it for the following computation like
translating DICOM query filters to JSONPath queries.
Labels: list List of labels
- Name: str Label name
DICOMQueryFilter: str [Optional] DICOM query filter similar to searching or
exporting DICOM instances. If you don't provide a query
or if the query is empty, the label matches all DICOM
instances
Categories: list List of categories. A category is a set of labels
- Name: str Category name
Labels: list List of labels associated with this category
ScopeToForward: dict List of labels or categories that should be forwarded to
the target Orthanc server
Labels: str or list
ExceptLabels: str or list
Categories: str or list
ExceptCategories: str or list
Transformations: list List of transformations to apply
- Scope: dict Scope to which the transformation specified in this item
should apply. Similar to "ScopeToForward"
[See below] See inline comments for the possible types of
transformations
"""
VALID_REUSE_VALUE = ('Always', 'SamePatient', 'SameStudy', 'SameSeries', 'SameInstance')
def label_exists(label_name):
for label in self._config['Labels']:
if label['Name'] == label_name:
return True
return False
def category_exists(category_name):
if not 'Categories' in self._config:
return False
for category in self._config['Categories']:
if category['Name'] == category_name:
return True
return False
def check_scope_rules(rules, path):
"""
Args:
rules (dict): Dict that can contain `Labels`, `ExceptLabels`, `Categories`,
`ExceptCategories` attributes
path (str): Path to this dict in the config file
"""
for rule_type in ('Labels', 'ExceptLabels', 'Categories', 'ExceptCategories'):
if rule_type in rules:
if isinstance(rules[rule_type], str):
rules[rule_type] = [rules[rule_type]]
assert isinstance(rules[rule_type], list), f'{path}["{rule_type}"] is not a string or a list of strings'
rpacs_v.check_list_item_type(rules[rule_type], str, f'{path}["{rule_type}"]')
for item in rules[rule_type]:
if rule_type in ('Labels', 'ExceptLabels'):
assert item == 'ALL' or label_exists(item), f'"{item}" is not a valid label. Make sure it exists in config["Labels"]'
else:
assert category_exists(item), f'"{item}" is not a valid category. Make sure it exists in config["Categories"]'
def check_tag_patterns_exist(element, path):
"""
Check if the element contains an attribute "TagPatterns" that is a path pattern or a list of
path patterns, and an optional "ExceptTagPatterns".
Args:
element (dict)
path (str)
"""
assert 'TagPatterns' in element, f'{path}["TagPatterns"] is missing'
element['TagPatterns'] = rpacs_v.check_or_form_list_of_str(element['TagPatterns'], f'{path}["TagPatterns"]')
for i_tag_pattern, tag_pattern in enumerate(element['TagPatterns']):
assert dicom_tpp.is_tag_path_pattern(tag_pattern), f'{path}["TagPatterns"][{i_tag_pattern}] is not a valid tag pattern'
if 'ExceptTagPatterns' in element:
element['ExceptTagPatterns'] = rpacs_v.check_or_form_list_of_str(element['ExceptTagPatterns'], f'{path}["ExceptTagPatterns"]')
for i_tag_pattern, tag_pattern in enumerate(element['ExceptTagPatterns']):
assert dicom_tpp.is_tag_path_pattern(tag_pattern), f'{path}["ExceptTagPatterns"][{i_tag_pattern}] is not a valid tag pattern'
else:
element['ExceptTagPatterns'] = []
# Labels
rpacs_v.check_dict_attribute_exists_and_type(self._config, 'Labels', list, 'config')
for i_label, label in rpacs_v.enumerate_list_and_check_item_type(self._config['Labels'], dict, 'config["Labels"]'):
rpacs_v.check_dict_attribute_exists_and_type(label, 'Name', str, f'config["Labels"][{i_label}]')
# Check that `DICOMQueryFilter` is valid, if it is specified and not empty. Translate and
# store the associated JSON Path query into `label['JSONPathQuery']`
if 'DICOMQueryFilter' in label and label['DICOMQueryFilter'] != '':
try:
label['JSONPathQuery'] = rpacs_dicom_json.translate_query_to_jsonpath(label['DICOMQueryFilter'])
except:
raise Exception(f'label["Labels"][{i_label}]["DICOMQueryFilter"] is not a valid query')
# Categories
if rpacs_v.check_dict_attribute_exists_and_type(self._config, 'Categories', list, 'config', optional=True) is True:
for i_category, category in rpacs_v.enumerate_list_and_check_item_type(self._config['Categories'], dict, 'config["Categories"]'):
rpacs_v.check_dict_attribute_exists_and_type(category, 'Name', str, f'config["Categories"][{i_category}]')
rpacs_v.check_dict_attribute_exists_and_type(category, 'Labels', list, f'config["Categories"][{i_category}]')
rpacs_v.check_list_item_type(category['Labels'], str, f'config["Categories"][{i_category}]["Labels"]')
# Scope to forward
rpacs_v.check_dict_attribute_exists_and_type(self._config, 'ScopeToForward', dict, 'config')
check_scope_rules(self._config['ScopeToForward'], 'config["ScopeToForward"]')
# Transformations
rpacs_v.check_dict_attribute_exists_and_type(self._config, 'Transformations', list, 'config')
for i_t, t in rpacs_v.enumerate_list_and_check_item_type(self._config['Transformations'], dict, 'config["Transformations"]'):
t_path = f'config["Transformations"][{i_t}]'
rpacs_v.check_dict_attribute_exists_and_type(t, 'Scope', dict, t_path)
check_scope_rules(t['Scope'], f'{t_path}["Scope"]')
# ShiftDateTime
# - TagPatterns: str or list List of UID tag patterns to shift
# ExceptTagPatterns: str or list Except this list of tag patterns
# ShiftBy: int Will shift by a random number of days (if Date)
# or seconds (if DateTime or Time) between
# `-ShiftBy` and `+ShiftBy`
# ReuseMapping: str [Optional] Scope of the mapping
if rpacs_v.check_dict_attribute_exists_and_type(t, 'ShiftDateTime', list, t_path, optional=True) is True:
for i_element, element in rpacs_v.enumerate_list_and_check_item_type(t['ShiftDateTime'], dict, f'{t_path}["ShiftDateTime"]'):
check_tag_patterns_exist(element, f'{t_path}["ShiftDateTime"][{i_element}]')
rpacs_v.check_dict_attribute_exists_and_type(element, 'ShiftBy', int, f'{t_path}["ShiftDateTime"]')
if rpacs_v.check_dict_attribute_exists_and_type(element, 'ReuseMapping', str, f'{t_path}["ShiftDateTime"]', optional=True) is True:
assert element['ReuseMapping'] in VALID_REUSE_VALUE, f'{t_path}["ShiftDateTime"][{i_element}]["ReuseMapping"] is invalid'
# RandomizeText
# - TagPatterns: str or list List of UID tag patterns to shift
# ExceptTagPatterns: str or list Except this list of tag patterns
# Split: str Split the element value on `Split` and randomize
# each item obtained separately
# IgnoreCase: bool Specified whether the original value must be
# lowercased before being randomized. Default is
# `False`.
# ReuseMapping: str [Optional] Scope of the mapping
if rpacs_v.check_dict_attribute_exists_and_type(t, 'RandomizeText', list, t_path, optional=True) is True:
for i_element, element in rpacs_v.enumerate_list_and_check_item_type(t['RandomizeText'], dict, f'{t_path}["RandomizeText"]'):
check_tag_patterns_exist(element, f'{t_path}["RandomizeText"][{i_element}]')
if rpacs_v.check_dict_attribute_exists_and_type(element, 'Split', str, f'{t_path}["RandomizeText"]', optional=True) is False:
element['Split'] = None
if rpacs_v.check_dict_attribute_exists_and_type(element, 'IgnoreCase', bool, f'{t_path}["RandomizeText"]', optional=True) is False:
element['IgnoreCase'] = False
if rpacs_v.check_dict_attribute_exists_and_type(element, 'ReuseMapping', str, f'{t_path}["RandomizeText"]', optional=True) is True:
assert element['ReuseMapping'] in VALID_REUSE_VALUE, f'{t_path}["RandomizeText"][{i_element}]["ReuseMapping"] is invalid'
# RandomizeUID:
# - TagPatterns: str or list List of UID tag patterns to randomize
# ExceptTagPatterns: str or list Except this list of tag patterns
# PrefixUID: str [Optional] UID prefix to use when creating the
# UID. Default is the pydicom root UID
if rpacs_v.check_dict_attribute_exists_and_type(t, 'RandomizeUID', list, t_path, optional=True) is True:
for i_element, element in rpacs_v.enumerate_list_and_check_item_type(t['RandomizeUID'], dict, f'{t_path}["RandomizeUID"]'):
check_tag_patterns_exist(element, f'{t_path}["RandomizeUID"][{i_element}]')
rpacs_v.check_dict_attribute_exists_and_type(element, 'Prefix', str, f'{t_path}["RandomizeUID"]', optional=True)
# AddTags
# - Tag: str Tag path
# VR: str Value Representation of the tag
# Value: str Value of the tag to create
# OverwriteIfExists If the tag already exists, set `True` to
# overwrite its value. Default is `False`
if rpacs_v.check_dict_attribute_exists_and_type(t, 'AddTags', list, t_path, optional=True) is True:
for i_element, element in rpacs_v.enumerate_list_and_check_item_type(t['AddTags'], dict, f'{t_path}["AddTags"]'):
rpacs_v.check_dict_attribute_exists_and_type(element, 'Tag', str, f'{t_path}["AddTags"]')
assert dicom_tp.is_tag_path(element['Tag']), f'{t_path}["AddTags"][{i_element}]["Tag"] is not a valid tag path'
rpacs_v.check_dict_attribute_exists_and_type(element, 'VR', str, f'{t_path}["AddTags"]')
rpacs_v.check_dict_attribute_exists_and_type(element, 'Value', str, f'{t_path}["AddTags"]')
if rpacs_v.check_dict_attribute_exists_and_type(element, 'OverwriteIfExists', bool, f'{t_path}["AddTags"]', optional=True) is False:
element['OverwriteIfExists'] = False
# RemoveBurnedInAnnotations:
# - Type: str OCR or Manual
# BoxCoordinates: list [Conditional] Provide a list of box coordinates. Each
# box coordinate is a 4-element list with integer (left,
# top, right, bottom)
if rpacs_v.check_dict_attribute_exists_and_type(t, 'RemoveBurnedInAnnotations', list, t_path, optional=True) is True:
for i_element, element in rpacs_v.enumerate_list_and_check_item_type(t['RemoveBurnedInAnnotations'], dict, f'{t_path}["RemoveBurnedInAnnotations"]'):
rpacs_v.check_dict_attribute_exists_and_type(element, 'Type', str, f'{t_path}["RemoveBurnedInAnnotations"][{i_element}]')
assert element['Type'] in ('OCR', 'Manual'), f'{t_path}["RemoveBurnedInAnnotations"][{i_element}]["Type"] must be equal to "OCR" or "Manual"'
if element['Type'] == 'Manual':
rpacs_v.check_dict_attribute_exists_and_type(element, 'BoxCoordinates', list, f'{t_path}["RemoveBurnedInAnnotations"][{i_element}]')
for i_box, box in rpacs_v.enumerate_list_and_check_item_type(element['BoxCoordinates'], list, f'{t_path}["RemoveBurnedInAnnotations"][{i_element}]["BoxCoordinates"]'):
rpacs_v.check_list_item_type(box, int, f'{t_path}["RemoveBurnedInAnnotations"][{i_element}]["BoxCoordinates"][{i_box}]')
assert len(box) == 4, f'{t_path}["RemoveBurnedInAnnotations"][{i_element}]["BoxCoordinates"][{i_box}] is not a 4-element list'
assert box[0] < box[2] and box[1] < box[3], f'{t_path}["RemoveBurnedInAnnotations"][{i_element}]["BoxCoordinates"][{i_box}] contains invalid coordinates'
# DeleteTags:
# - TagPatterns: str or list List of tag patterns to remove
# ExceptTagPatterns: str or list List of tag patterns to retain
# Action: str Remove or Empty
if rpacs_v.check_dict_attribute_exists_and_type(t, 'DeleteTags', list, t_path, optional=True) is True:
for i_element, element in rpacs_v.enumerate_list_and_check_item_type(t['DeleteTags'], dict, f'{t_path}["DeleteTags"]'):
check_tag_patterns_exist(element, f'{t_path}["DeleteTags"][{i_element}]')
rpacs_v.check_dict_attribute_exists_and_type(element, 'Action', str, f'{t_path}["DeleteTags"][{i_element}]')
assert element['Action'] in ('Remove', 'Empty'), f'{t_path}["DeleteTags"][{i_element}]["Action"] must be equal to "Remove" or "Empty"'
# Transcode: str Transfer syntax UID to which the de-identified DICOM
# file should be transcoded. If not provided, the
# de-identified DICOM file will use the same transfer
# syntax than the original DICOM file.
rpacs_v.check_dict_attribute_exists_and_type(t, 'Transcode', str, t_path, optional=True)