Skip to content

Commit

Permalink
Merge pull request #35 from innolitics/16-functional-group-macros
Browse files Browse the repository at this point in the history
Process Functional Group Macros
  • Loading branch information
russellkan authored May 7, 2020
2 parents 9803855 + 65f38b7 commit ff9a32e
Show file tree
Hide file tree
Showing 11 changed files with 146,014 additions and 812 deletions.
14 changes: 9 additions & 5 deletions dicom_standard/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.SUFFIXES:)
.SUFFIXES:
.DELETE_ON_ERROR:

.PHONY: clean tests unittest endtoendtest updatestandard checkversions

Expand All @@ -21,8 +22,8 @@ relationship_tables: dist/ciod_to_modules.json dist/ciod_to_func_group_macros.js
dist/ciods.json: tmp/raw_ciod_module_tables.json
$(PYTHONPATH_PREFIX) python3 process_ciods.py $< > $@

dist/modules.json: tmp/preprocessed_modules_attributes.json
$(PYTHONPATH_PREFIX) python3 process_modules.py $< > $@
dist/modules.json: tmp/preprocessed_modules_attributes.json dist/ciod_to_func_group_macros.json
$(PYTHONPATH_PREFIX) python3 process_modules.py $^ > $@

dist/macros.json: tmp/preprocessed_macros_attributes.json
$(PYTHONPATH_PREFIX) python3 process_macros.py $< > $@
Expand All @@ -42,8 +43,8 @@ dist/ciod_to_modules.json: tmp/raw_ciod_module_tables.json
dist/ciod_to_func_group_macros.json: tmp/raw_ciod_func_group_macro_tables.json
$(PYTHONPATH_PREFIX) python3 process_ciod_func_group_macro_relationship.py $< > $@

dist/module_to_attributes.json: tmp/modules_attributes_updated_references.json
$(PYTHONPATH_PREFIX) python3 postprocess_merge_duplicate_nodes.py $< > $@
dist/module_to_attributes.json: tmp/module_to_attributes_no_duplicates.json dist/macros.json dist/ciod_to_func_group_macros.json dist/macro_to_attributes.json
$(PYTHONPATH_PREFIX) python3 postprocess_integrate_func_group_macros.py $^ > $@

dist/macro_to_attributes.json: tmp/macros_attributes_partial_references.json dist/references.json
$(PYTHONPATH_PREFIX) python3 postprocess_update_reference_links.py $^ > $@
Expand All @@ -52,6 +53,9 @@ dist/references.json: tmp/modules_attributes_partial_references.json tmp/raw_sec
$(PYTHONPATH_PREFIX) python3 postprocess_save_references.py $^ > $@


tmp/module_to_attributes_no_duplicates.json: tmp/modules_attributes_updated_references.json
$(PYTHONPATH_PREFIX) python3 postprocess_merge_duplicate_nodes.py $< > $@

tmp/modules_attributes_updated_references.json: tmp/modules_attributes_partial_references.json dist/references.json
$(PYTHONPATH_PREFIX) python3 postprocess_update_reference_links.py $^ > $@

Expand Down
15 changes: 15 additions & 0 deletions dicom_standard/extract_ciod_func_group_macro_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import List, Tuple
import sys
import re
from copy import deepcopy

from bs4 import Tag

Expand Down Expand Up @@ -58,8 +59,22 @@ def get_table_with_metadata(table_with_tdiv: Tuple[List[TableDictType], Tag]) ->
}


def add_enhanced_mr_color_image_table(table_data):
''' Standard workaround: The Enhanced MR Color Image IOD does not have its own set
of Functional Group Macros, but instead refers to the Enhanced MR Image Functional
Group Macros Table, so we duplicate that table object and modify the name
See http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_A.36.4.4.html
'''
enhanced_mr_image_fg_macros = next(filter(lambda t: t['name'] == 'Enhanced MR Image', table_data), None)
assert enhanced_mr_image_fg_macros is not None, 'Table with name "Enhanced MR Image" not found.'
new_table = deepcopy(enhanced_mr_image_fg_macros)
new_table['name'] = 'Enhanced MR Color Image'
table_data.append(new_table)


if __name__ == "__main__":
standard = pl.parse_html_file(sys.argv[1])
tables, tdivs = get_chapter_tables(standard, CHAPTER_ID, is_valid_macro_table)
parsed_table_data = tables_to_json(tables, tdivs, macro_table_to_dict, get_table_with_metadata)
add_enhanced_mr_color_image_table(parsed_table_data)
pl.write_pretty_json(parsed_table_data)
68 changes: 68 additions & 0 deletions dicom_standard/postprocess_integrate_func_group_macros.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'''
Add functional group macro attributes to module_to_attributes.json
'''
import sys
from copy import deepcopy

import dicom_standard.parse_lib as pl
from dicom_standard.process_modules import FUNC_GROUP_MODULE_ID

PER_FRAME_FUNC_GROUP_ID = 52009230


def update_description(attribute, macro):
note_header = '<h3>Note</h3>'
usage = f'<p>Part of the {macro["macroName"]} Functional Group Macro with usage: {macro["usage"]}</p>'
conditionalStatement = macro['conditionalStatement']
if conditionalStatement and not conditionalStatement.endswith('.'):
conditionalStatement += '.'
conditional = f'<p>{conditionalStatement}</p>' if conditionalStatement else ''
attribute['description'] += note_header + usage + conditional


def process_macro_attributes(macro_attrs, macro):
attr_list = []
for macro_attr in macro_attrs:
attr = deepcopy(macro_attr)
macro_id = attr.pop('macroId')
attr['moduleId'] = f'{macro["ciodId"]}-{FUNC_GROUP_MODULE_ID}'
new_path_prefix = f'{attr["moduleId"]}:{PER_FRAME_FUNC_GROUP_ID}'
attr['path'] = attr['path'].replace(macro_id, new_path_prefix)
update_description(attr, macro)
attr_list.append(attr)
return attr_list


def process_mffg_attributes(ciods, mffg_attrs):
attr_list = []
for ciod in ciods:
module_id = f'{ciod}-{FUNC_GROUP_MODULE_ID}'
for mffg_attr in mffg_attrs:
attr = deepcopy(mffg_attr)
attr['moduleId'] = module_id
attr['path'] = attr['path'].replace(FUNC_GROUP_MODULE_ID, module_id)
attr_list.append(attr)
return attr_list


def process_ciod_specific_attributes(module_to_attr, macros, ciod_to_macro, macro_to_attr):
ciod_specific_attrs = []
macro_dict = {macro['id']: macro for macro in macros}
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)
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)
return ciod_specific_attrs


if __name__ == '__main__':
module_to_attributes = pl.read_json_data(sys.argv[1])
macros = pl.read_json_data(sys.argv[2])
ciod_to_macro = pl.read_json_data(sys.argv[3])
macro_to_attributes = pl.read_json_data(sys.argv[4])
new_attributes = process_ciod_specific_attributes(module_to_attributes, macros, ciod_to_macro, macro_to_attributes)
pl.write_pretty_json(module_to_attributes + new_attributes)
5 changes: 1 addition & 4 deletions dicom_standard/preprocess_modules_with_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@


def key_tables_by_id(table_list: List[MetadataTableType]) -> Dict[str, MetadataTableType]:
dict_of_tables = {}
for table in table_list:
dict_of_tables[get_id_from_link(table['linkToStandard'])] = table
return dict_of_tables
return {get_id_from_link(table['linkToStandard']): table for table in table_list}


def expand_all_macros(module_attr_tables, macros):
Expand Down
9 changes: 7 additions & 2 deletions dicom_standard/process_ciod_module_relationship.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ def define_ciod_module_relationship(ciod, module):
and any(mod in module['module'] for mod in ['Real-Time Acquisition', 'Current Frame Functional Groups'])):
# Manually input missing field
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':
module_id = f'{ciod_id}-{module_id}'
return {
"ciodId": pl.create_slug(ciod),
"moduleId": pl.create_slug(pl.text_from_html_string(module['module'])),
"ciodId": ciod_id,
"moduleId": module_id,
"usage": usage,
"conditionalStatement": conditional_statement,
"informationEntity": information_entity,
Expand Down
21 changes: 20 additions & 1 deletion dicom_standard/process_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
Convert the processed module-attribute JSON data into a
normalized listing of all modules in the DICOM Standard.
'''
from typing import cast, List
import sys
from copy import deepcopy

from dicom_standard import parse_lib as pl
from dicom_standard.macro_utils import MetadataTableType

FUNC_GROUP_MODULE_ID = 'multi-frame-functional-groups'


def modules_from_tables(tables):
Expand All @@ -17,7 +22,21 @@ def modules_from_tables(tables):
return modules


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


if __name__ == '__main__':
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)
pl.write_pretty_json(modules)
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)
pl.write_pretty_json(modules + ciod_specific_modules)
174 changes: 174 additions & 0 deletions standard/ciod_to_func_group_macros.json
Original file line number Diff line number Diff line change
Expand Up @@ -2194,5 +2194,179 @@
"macroId":"real-world-value-mapping",
"usage":"U",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"pixel-measures",
"usage":"M",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"frame-content",
"usage":"M",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"plane-position-patient",
"usage":"M",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"plane-orientation-patient",
"usage":"M",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"referenced-image",
"usage":"C",
"conditionalStatement":"Required if the image or frame has been planned on another image or frame. May be present otherwise"
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"derivation-image",
"usage":"C",
"conditionalStatement":"Required if the image or frame has been derived from another SOP Instance."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"cardiac-synchronization",
"usage":"C",
"conditionalStatement":"Required if Cardiac Synchronization Technique (0018,9037) equals other than NONE and if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"frame-anatomy",
"usage":"M",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"pixel-value-transformation",
"usage":"C",
"conditionalStatement":"Required if Photometric Interpretation (0028,0004) is MONOCHROME2."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"frame-voi-lut",
"usage":"U",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"real-world-value-mapping",
"usage":"U",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"contrast-bolus-usage",
"usage":"C",
"conditionalStatement":"Required if Contrast/Bolus Agent Sequence (0018,0012) is used."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"respiratory-synchronization",
"usage":"C",
"conditionalStatement":"Required if Respiratory Motion Compensation Technique (0018,9170) equals other than NONE, REALTIME or BREATH_HOLD and if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-image-frame-type",
"usage":"M",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-timing-and-related-parameters",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-fov-geometry",
"usage":"C",
"conditionalStatement":"Required if Geometry of k-Space Traversal (0018,9032) equals RECTILINEAR and if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-echo",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-modifier",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-imaging-modifier",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-receive-coil",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-transmit-coil",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-diffusion",
"usage":"C",
"conditionalStatement":"Required if Acquisition Contrast (0008,9209) in any MR Image Frame Type Functional Group in the SOP Instance equals DIFFUSION and Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-averages",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-spatial-saturation",
"usage":"C",
"conditionalStatement":"Required if Spatial Pre-saturation (0018,9027) equals SLAB for any frame in the SOP Instance and Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-metabolite-map",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 3 equals METABOLITE_MAP. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-velocity-encoding",
"usage":"C",
"conditionalStatement":"Required if Phase Contrast (0018,9014) equals YES and Image Type (0008,0008) Value 1 is ORIGINAL or MIXED. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"mr-arterial-spin-labeling",
"usage":"C",
"conditionalStatement":"Required if Image Type (0008,0008) Value 3 is ASL. May be present otherwise."
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"functional-mr",
"usage":"U",
"conditionalStatement":null
},
{
"ciodId":"enhanced-mr-color-image",
"macroId":"temporal-position",
"usage":"U",
"conditionalStatement":null
}
]
Loading

0 comments on commit ff9a32e

Please sign in to comment.