Skip to content

Commit

Permalink
Process "Current Frame" func group macros in the same way as the "Mul…
Browse files Browse the repository at this point in the history
…ti-frame" func group macros

- Refactor some modules to handle "Current Frame" fg macros differently from "Multi-frame" fg macros
- Update e2e and unit tests
- Fix wrong description parsing for macros (was calling a function designed for a different table type)
- Update standard JSON files
  • Loading branch information
russellkan committed May 29, 2020
1 parent 65d54fb commit 218eb12
Show file tree
Hide file tree
Showing 12 changed files with 1,102 additions and 1,680 deletions.
11 changes: 7 additions & 4 deletions dicom_standard/extract_ciod_func_group_macro_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
get_chapter_tables,
tables_to_json,
get_short_standard_link,
get_table_description,
table_to_dict,
)

Expand Down Expand Up @@ -49,13 +48,17 @@ def macro_table_to_dict(table: StringifiedTableListType) -> List[TableDictType]:
def get_table_with_metadata(table_with_tdiv: Tuple[List[TableDictType], Tag]) -> MetadataTableType:
table, tdiv = table_with_tdiv
clean_name = clean_macro_table_name(pr.table_name(tdiv))
table_description = get_table_description(tdiv)
clean_description = pl.clean_html(str(tdiv.find_previous('p')))
module_type = 'Multi-frame' if 'Multi-frame' in clean_description \
else 'Current Frame' if 'Current Frame' in clean_description \
else None
return {
'name': clean_name,
'macros': table,
'id': pl.create_slug(clean_name),
'description': str(table_description),
'linkToStandard': get_short_standard_link(tdiv)
'description': clean_description,
'linkToStandard': get_short_standard_link(tdiv),
'moduleType': module_type,
}


Expand Down
43 changes: 29 additions & 14 deletions dicom_standard/postprocess_integrate_func_group_macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
from operator import itemgetter

import dicom_standard.parse_lib as pl
from dicom_standard.process_modules import FUNC_GROUP_MODULE_ID
from dicom_standard.process_modules import MF_FUNC_GROUP_MODULE_ID, CF_FUNC_GROUP_MODULE_ID

SHARED_FUNC_GROUP_ID = 52009229
PER_FRAME_FUNC_GROUP_ID = 52009230
SHARED_FUNC_GROUP_ID = '52009229'
PER_FRAME_FUNC_GROUP_ID = '52009230'
CURRENT_FRAME_FUNC_GROUP_ID = '00060001'


def update_description(attribute, macro):
Expand All @@ -25,29 +26,35 @@ def update_description(attribute, macro):
def convert_macro_attr(macro_attr, macro, fg_attr_id):
attr = deepcopy(macro_attr)
macro_id = attr.pop('macroId')
attr['moduleId'] = f'{macro["ciodId"]}-{FUNC_GROUP_MODULE_ID}'
fg_module_id = CF_FUNC_GROUP_MODULE_ID if fg_attr_id == CURRENT_FRAME_FUNC_GROUP_ID else MF_FUNC_GROUP_MODULE_ID
attr['moduleId'] = f'{macro["ciodId"]}-{fg_module_id}'
new_path_prefix = f'{attr["moduleId"]}:{fg_attr_id}'
attr['path'] = attr['path'].replace(macro_id, new_path_prefix)
update_description(attr, macro)
return attr


def process_macro_attributes(macro_attrs, macro):
def process_mffg_macro_attributes(macro_attrs, macro):
attr_list = []
for macro_attr in macro_attrs:
attr_list.append(convert_macro_attr(macro_attr, macro, SHARED_FUNC_GROUP_ID))
attr_list.append(convert_macro_attr(macro_attr, macro, PER_FRAME_FUNC_GROUP_ID))
return attr_list


def process_mffg_attributes(ciods, mffg_attrs):
def process_cffg_macro_attributes(macro_attrs, macro):
return [convert_macro_attr(macro_attr, macro, CURRENT_FRAME_FUNC_GROUP_ID) for macro_attr in macro_attrs]


def process_fg_attributes(module_to_attr, ciods, fg_module_id):
fg_attrs = list(filter(lambda r: r['moduleId'] == fg_module_id, module_to_attr))
attr_list = []
for ciod in ciods:
module_id = f'{ciod}-{FUNC_GROUP_MODULE_ID}'
for mffg_attr in mffg_attrs:
attr = deepcopy(mffg_attr)
module_id = f'{ciod}-{fg_module_id}'
for fg_attr in fg_attrs:
attr = deepcopy(fg_attr)
attr['moduleId'] = module_id
attr['path'] = attr['path'].replace(FUNC_GROUP_MODULE_ID, module_id)
attr['path'] = attr['path'].replace(fg_module_id, module_id)
attr_list.append(attr)
return attr_list

Expand All @@ -58,11 +65,19 @@ def process_ciod_specific_attributes(module_to_attr, macros, ciod_to_macro, macr
for rel in ciod_to_macro:
rel['macroName'] = macro_dict[rel['macroId']]['name']
macro_attrs = list(filter(lambda r: r['macroId'] == rel['macroId'], macro_to_attr))
processed_macro_attrs = process_macro_attributes(macro_attrs, rel)
module_type = rel['moduleType']
if module_type == 'Multi-frame':
processed_macro_attrs = process_mffg_macro_attributes(macro_attrs, rel)
elif module_type == 'Current Frame':
processed_macro_attrs = process_cffg_macro_attributes(macro_attrs, rel)
else:
raise Exception(f'Module type property should be either "Multi-frame" or "Current Frame" but is {module_type}')
ciod_specific_attrs += processed_macro_attrs
mffg_attrs = list(filter(lambda r: r['moduleId'] == FUNC_GROUP_MODULE_ID, module_to_attr))
ciods_with_macros = list(set([rel['ciodId'] for rel in ciod_to_macro]))
ciod_specific_attrs += process_mffg_attributes(ciods_with_macros, mffg_attrs)
# Create CIOD-specific versions of each non-macro attribute within the relevant modules
ciods_with_mffg_macros = list(set([rel['ciodId'] for rel in ciod_to_macro if rel['moduleType'] == 'Multi-frame']))
ciod_specific_attrs += process_fg_attributes(module_to_attr, ciods_with_mffg_macros, MF_FUNC_GROUP_MODULE_ID)
ciods_with_cffg_macros = list(set([rel['ciodId'] for rel in ciod_to_macro if rel['moduleType'] == 'Current Frame']))
ciod_specific_attrs += process_fg_attributes(module_to_attr, ciods_with_cffg_macros, CF_FUNC_GROUP_MODULE_ID)
return ciod_specific_attrs


Expand Down
8 changes: 5 additions & 3 deletions dicom_standard/process_ciod_func_group_macro_relationship.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,20 @@ def define_all_relationships(ciod_macro_list):
for table in ciod_macro_list:
ciod = table['name']
macros = table['macros']
all_relationships.extend([define_ciod_macro_relationship(ciod, macro)
module_type = table['moduleType']
all_relationships.extend([define_ciod_macro_relationship(ciod, macro, module_type)
for macro in macros])
return all_relationships


def define_ciod_macro_relationship(ciod, macro):
def define_ciod_macro_relationship(ciod, macro, module_type):
usage, conditional_statement = expand_conditional_statement(macro['usage'])
return {
"ciodId": pl.create_slug(ciod),
"macroId": pl.create_slug(clean_macro_name(pl.text_from_html_string(macro['macro']))),
"usage": usage,
"conditionalStatement": conditional_statement
"conditionalStatement": conditional_statement,
"moduleType": module_type,
}


Expand Down
8 changes: 6 additions & 2 deletions dicom_standard/process_ciod_module_relationship.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys

from dicom_standard import parse_lib as pl
from dicom_standard.process_modules import MF_FUNC_GROUP_MODULE_ID, CF_FUNC_GROUP_MODULE_ID


def define_all_relationships(ciod_module_list):
Expand Down Expand Up @@ -42,8 +43,11 @@ def define_ciod_module_relationship(ciod, module):
information_entity = 'Image'
ciod_id = pl.create_slug(ciod)
module_id = pl.create_slug(pl.text_from_html_string(module['module']))
# Add CIOD ID to differentiate "Multi-Frame Functional Group" modules for different CIODs
if module_id == 'multi-frame-functional-groups':
# Create CIOD-specific "Multi-frame Functional Group" module IDs
if module_id == MF_FUNC_GROUP_MODULE_ID:
module_id = f'{ciod_id}-{module_id}'
# Create CIOD-specific "Current Frame Functional Group" module IDs
if module_id == CF_FUNC_GROUP_MODULE_ID:
module_id = f'{ciod_id}-{module_id}'
return {
"ciodId": ciod_id,
Expand Down
23 changes: 14 additions & 9 deletions dicom_standard/process_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from dicom_standard import parse_lib as pl
from dicom_standard.macro_utils import MetadataTableType

FUNC_GROUP_MODULE_ID = 'multi-frame-functional-groups'
MF_FUNC_GROUP_MODULE_ID = 'multi-frame-functional-groups'
CF_FUNC_GROUP_MODULE_ID = 'current-frame-functional-groups'


def modules_from_tables(tables):
Expand All @@ -23,11 +24,11 @@ def modules_from_tables(tables):
return modules


def create_ciod_specific_modules(ciods, mffg_module):
def create_ciod_specific_modules(ciods, module, module_id):
modules = []
for ciod in ciods:
ciod_specific_module = deepcopy(mffg_module)
ciod_specific_module['id'] = f'{ciod}-{FUNC_GROUP_MODULE_ID}'
ciod_specific_module = deepcopy(module)
ciod_specific_module['id'] = f'{ciod}-{module_id}'
modules.append(ciod_specific_module)
return modules

Expand All @@ -36,9 +37,13 @@ def create_ciod_specific_modules(ciods, mffg_module):
module_attr_tables = pl.read_json_data(sys.argv[1])
ciod_to_macro = cast(List[MetadataTableType], pl.read_json_data(sys.argv[2]))
modules = modules_from_tables(module_attr_tables)
ciods_with_macros = list(set([rel['ciodId'] for rel in ciod_to_macro]))
multi_frame_func_group_module = next(filter(lambda rel: rel['id'] == FUNC_GROUP_MODULE_ID, modules), None)
assert multi_frame_func_group_module is not None, f'Module ID "{FUNC_GROUP_MODULE_ID}" not found in modules.json'
ciod_specific_modules = create_ciod_specific_modules(ciods_with_macros, multi_frame_func_group_module)
sorted_modules = sorted(modules + ciod_specific_modules, key=itemgetter('id'))
ciods_with_mffg_macros = list(set([rel['ciodId'] for rel in ciod_to_macro if rel['moduleType'] == 'Multi-frame']))
ciods_with_cffg_macros = list(set([rel['ciodId'] for rel in ciod_to_macro if rel['moduleType'] == 'Current Frame']))
multi_frame_func_group_module = next(filter(lambda rel: rel['id'] == MF_FUNC_GROUP_MODULE_ID, modules), None)
assert multi_frame_func_group_module is not None, f'Module ID "{MF_FUNC_GROUP_MODULE_ID}" not found'
current_frame_func_group_module = next(filter(lambda rel: rel['id'] == CF_FUNC_GROUP_MODULE_ID, modules), None)
assert current_frame_func_group_module is not None, f'Module ID "{CF_FUNC_GROUP_MODULE_ID}" not found'
ciod_specific_mffg_modules = create_ciod_specific_modules(ciods_with_mffg_macros, multi_frame_func_group_module, MF_FUNC_GROUP_MODULE_ID)
ciod_specific_cffg_modules = create_ciod_specific_modules(ciods_with_cffg_macros, current_frame_func_group_module, CF_FUNC_GROUP_MODULE_ID)
sorted_modules = sorted(modules + ciod_specific_mffg_modules + ciod_specific_cffg_modules, key=itemgetter('id'))
pl.write_pretty_json(sorted_modules)
6 changes: 3 additions & 3 deletions dicom_standard/table_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ def get_short_standard_link(tdiv: Tag) -> str:
def get_table_description(tdiv: Tag) -> Optional[Tag]:
section = tdiv.parent.parent
# Some descriptions are children of 'h3' tags while others are children of 'h5' tags
description_title = section.find(['h3', 'h5'], class_='title')
assert description_title is not None, 'Table description not found.'
return description_title.parent.parent.parent.parent.p
section_header = section.find(['h3', 'h5'], class_='title')
assert section_header is not None, 'Section header not found.'
return section_header.parent.parent.parent.parent.p


def table_to_dict(table: TableListType, row_names: List[str], omit_empty: bool = False) -> List[TableDictType]:
Expand Down
Loading

0 comments on commit 218eb12

Please sign in to comment.