Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: pipeline conversion #779

Merged
merged 42 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
396e168
Added utility functions to Result.hpp
JDuffeyBQ Sep 26, 2023
8b66646
Added IFilter::getDefaultArguments
JDuffeyBQ Sep 26, 2023
08b94cd
Added pipeline conversion functionality
JDuffeyBQ Sep 26, 2023
8d504e1
Added python script to generate SIMPL json conversion code
JDuffeyBQ Sep 26, 2023
db51ce1
Added convert option to nxrunner
JDuffeyBQ Sep 29, 2023
a0b6de7
Add more filter parameter converters
jmarquisbq Oct 4, 2023
57d4501
Add SIMPL json conversion for FindFeatureCentroidsFilter
jmarquisbq Oct 4, 2023
d059e7a
Add filter parameter converters
jmarquisbq Oct 4, 2023
09e9a75
Add SIMPL json conversion for FindShapesFilter
jmarquisbq Oct 4, 2023
96a8e97
Add filter parameter converter
jmarquisbq Oct 4, 2023
c4ec6e4
Add SIMPL json conversion for FindNeighborhoodsFilter
jmarquisbq Oct 4, 2023
3ce9f01
Add filter parameter converter
jmarquisbq Oct 4, 2023
ce0831b
Add SIMPL json conversion for FindEuclideanDistMapFilter
jmarquisbq Oct 4, 2023
ea130c4
Add SIMPL json conversion for FindSurfaceAreaToVolumeFilter
jmarquisbq Oct 4, 2023
3b80be8
Added utility functions to Result.hpp
JDuffeyBQ Sep 26, 2023
bad6c43
Add FindArrayStatisticsFilter
mmarineBlueQuartz Oct 10, 2023
ca90760
Batch add filters for SIMPL json conversion
mmarineBlueQuartz Oct 20, 2023
aded8a8
Batch json conversion
mmarineBlueQuartz Oct 25, 2023
8f2812c
Batch json conversion
mmarineBlueQuartz Oct 25, 2023
bdf4f3e
Batch json conversion
mmarineBlueQuartz Oct 25, 2023
a5bc8b2
Batch simpl json conversion
mmarineBlueQuartz Oct 25, 2023
640e9c3
Finished ITK conversion
mmarineBlueQuartz Oct 25, 2023
7977fb3
Batch json conversion
mmarineBlueQuartz Oct 26, 2023
cb420f9
Batch json conversion
mmarineBlueQuartz Oct 26, 2023
399cb1d
Batch json conversion
mmarineBlueQuartz Oct 26, 2023
53e5d4c
Batch json conversion
mmarineBlueQuartz Nov 3, 2023
c261921
Batch json conversion
mmarineBlueQuartz Nov 10, 2023
ee2fc2f
Fix rebase compile errors
mmarineBlueQuartz Nov 10, 2023
c6dabfb
Fix additional rebase compile errors
mmarineBlueQuartz Nov 13, 2023
62cbf9e
Create filter comments for errors
mmarineBlueQuartz Nov 21, 2023
c129c52
Avoid assigning null filter arguments
mmarineBlueQuartz Nov 21, 2023
6955800
Use default arguments on conversion error
mmarineBlueQuartz Nov 21, 2023
cf92b53
Fix compile error in ArrayCalculatorFilter
jmarquisbq Nov 21, 2023
6b74801
Fix InitializeData filters after rebase
jmarquisbq Nov 29, 2023
22b46ae
clang-format after rebase
jmarquisbq Nov 29, 2023
1d80548
Use placeholder (null IFilter) and continue converting when SIMPL con…
jmarquisbq Dec 5, 2023
e89dd91
Update NX Runner documentation
mmarineBlueQuartz Dec 5, 2023
22d90a9
Update placeholder filter warnings to errors in preflight/execute
jmarquisbq Dec 5, 2023
dfea262
Requested PR Changes
mmarineBlueQuartz Dec 15, 2023
2cec3de
Fix compile error
mmarineBlueQuartz Dec 15, 2023
a661f34
Fixed rebase error
mmarineBlueQuartz Dec 15, 2023
9bf7497
Add text to filter conversion error hinting to use for further actions
imikejackson Dec 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ set(COMPLEX_HDRS
${COMPLEX_SOURCE_DIR}/Utilities/SampleSurfaceMesh.hpp
${COMPLEX_SOURCE_DIR}/Utilities/KUtilities.hpp
${COMPLEX_SOURCE_DIR}/Utilities/MontageUtilities.hpp
${COMPLEX_SOURCE_DIR}/Utilities/SIMPLConversion.hpp

${COMPLEX_SOURCE_DIR}/Utilities/Math/GeometryMath.hpp
${COMPLEX_SOURCE_DIR}/Utilities/Math/MatrixMath.hpp
Expand Down Expand Up @@ -975,7 +976,7 @@ if(COMPLEX_BUILD_DOCS)
add_subdirectory(docs)
endif()

#------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Print Summary Information
#------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
include(${complex_SOURCE_DIR}/cmake/Summary.cmake)
239 changes: 239 additions & 0 deletions scripts/generate_simpl_conversion_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import json
import argparse
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Tuple

SEPERATOR_PARAMETER_TYPE: str = "SeparatorFilterParameter"

@dataclass
class SIMPLParameterInfo:
name: str
type: str

@staticmethod
def from_json(json_object):
return SIMPLParameterInfo(name=json_object['name'], type=json_object['type'])

@dataclass
class SIMPLFilterInfo:
name: str
uuid: str
parameters: List[SIMPLParameterInfo]

@staticmethod
def from_json(json_object):
parameters: List[SIMPLParameterInfo] = [SIMPLParameterInfo.from_json(param) for param in json_object['parameters']]
stripped_uuid = json_object['uuid'].strip('{}')
return SIMPLFilterInfo(name=json_object['name'], uuid=stripped_uuid, parameters=parameters)

def create_filter_conversion(simpl_filter: SIMPLFilterInfo, complex_filter_name: str) -> List[str]:
converter_code: List[str] = []
converter_code.append('\n')
converter_code.append('namespace\n')
converter_code.append('{\n')
converter_code.append('namespace SIMPL\n')
converter_code.append('{\n')

for param in simpl_filter.parameters:
if param.type == SEPERATOR_PARAMETER_TYPE:
continue
converter_code.append(f'constexpr StringLiteral k_{param.name}Key = "{param.name}";\n')

converter_code.append('} // namespace SIMPL\n')
converter_code.append('} // namespace\n')
converter_code.append('\n')
converter_code.append(f'Result<Arguments> {complex_filter_name}::FromSIMPLJson(const nlohmann::json& json)\n')
converter_code.append('{\n')
converter_code.append(' Arguments args = CreateDataArray().getDefaultArguments();\n')
converter_code.append('\n')
converter_code.append(' std::vector<Result<>> results;\n')
converter_code.append('\n')

for param in simpl_filter.parameters:
if param.type == SEPERATOR_PARAMETER_TYPE:
continue
converter_code.append(f' results.push_back(SIMPLConversion::ConvertParameter<SIMPLConversion::{param.type}Converter>(args, json, SIMPL::k_{param.name}Key, "@COMPLEX_PARAMETER_KEY@"));\n')

converter_code.append('\n')
converter_code.append(' Result<> conversionResult = MergeResults(std::move(results));\n')
converter_code.append('\n')
converter_code.append(' return ConvertResultTo<Arguments>(std::move(conversionResult), std::move(args));\n')
converter_code.append('}\n')
converter_code.append('\n')
return converter_code

def create_includes() -> List[str]:
lines: List[str] = []
lines.append('\n')
lines.append('#include "complex/Utilities/SIMPLConversion.hpp"\n')
lines.append('\n')
return lines

def create_function_decl() -> List[str]:
lines: List[str] = []
lines.append('\n')
lines.append(' /**\n')
lines.append(' * @brief Reads SIMPL json and converts it complex Arguments.\n')
lines.append(' * @param json\n')
lines.append(' * @return Result<Arguments>\n')
lines.append(' */\n')
lines.append(' static Result<Arguments> FromSIMPLJson(const nlohmann::json& json);\n')
lines.append('\n')
return lines

def find_last_include(lines: List[str]) -> int:
index = -1
for i, line in enumerate(lines):
if line.startswith('#include'):
index = i
return index

def find_last_parameter_key(lines: List[str]) -> int:
index = -1
for i, line in enumerate(lines):
if line.startswith(' static inline constexpr StringLiteral k_'):
index = i
return index

def find_mapping_line(lines: List[str], simpl_uuid: str) -> int:
for i, line in enumerate(lines):
if simpl_uuid in line:
return i
return -1

def read_mapping_file(filepath: Path) -> Dict[str, str]:
with open(filepath, 'r') as file:
file_content = file.read()
matches = re.findall(r'{complex::Uuid::FromString\("(.*?)"\).value\(\), {complex::FilterTraits<(.*?)>::uuid, {}}},', file_content)
return dict(matches)

def get_plugin_mapping_file_path_from_plugin_dir(plugin_dir: Path, plugin_name: str) -> Path:
return plugin_dir / f'src/{plugin_name}/{plugin_name}LegacyUUIDMapping.hpp'

def get_plugin_mapping_file_path_from_root_dir(complex_source_dir: Path, plugin_name: str) -> Path:
return get_plugin_mapping_file_path_from_plugin_dir(complex_source_dir / f'src/Plugins/{plugin_name}', plugin_name)

def get_plugin_mappings(complex_source_dir: Path) -> Dict[str, Dict[str, str]]:
mappings: Dict[str, Dict[str, str]] = {}
plugins_dir = complex_source_dir / 'src/Plugins'
ignored_plugins = ['TestOne', 'TestTwo']
for child in plugins_dir.iterdir():
if not child.is_dir():
continue
if child.name in ignored_plugins:
continue
plugin_name = child.name
mapping_file = get_plugin_mapping_file_path_from_plugin_dir(child, plugin_name)
plugin_mapping = read_mapping_file(mapping_file)
mappings[plugin_name] = plugin_mapping
return mappings

def find_filter(mappings: Dict[str, Dict[str, str]], filter_uuid: str) -> Tuple[str, str]:
for plugin_name, plugin_mapping in mappings.items():
if filter_uuid in plugin_mapping:
return (plugin_name, plugin_mapping[filter_uuid])
raise RuntimeError(f'{filter_uuid} not found')

def get_filter_base_path(complex_source_dir: Path, plugin_name: str, complex_filter: str) -> Path:
return complex_source_dir / f'src/Plugins/{plugin_name}/src/{plugin_name}/Filters/{complex_filter}'

def get_filter_hpp_path(complex_source_dir: Path, plugin_name: str, complex_filter: str) -> Path:
return get_filter_base_path(complex_source_dir, plugin_name, complex_filter).with_suffix('.hpp')

def get_filter_cpp_path(complex_source_dir: Path, plugin_name: str, complex_filter: str) -> Path:
return get_filter_base_path(complex_source_dir, plugin_name, complex_filter).with_suffix('.cpp')

def read_simpl_json(path: Path) -> Dict[str, SIMPLFilterInfo]:
with open(path, 'r') as file:
simpl_json = json.load(file)

simpl_filters: Dict[str, SIMPLFilterInfo] = {}
for plugin in simpl_json['plugins']:
for f in plugin['filters']:
filter_info = SIMPLFilterInfo.from_json(f)
simpl_filters[filter_info.uuid] = filter_info
return simpl_filters

def update_hpp_lines(lines: List[str]) -> None:
last_parameter_key_index = find_last_parameter_key(lines)

function_decl_lines = create_function_decl()
lines[last_parameter_key_index:last_parameter_key_index] = function_decl_lines

def update_cpp_lines(lines: List[str], simpl_filter_info: SIMPLFilterInfo, complex_filter_name: str) -> None:
last_include_index = find_last_include(lines)

include_lines = create_includes()

lines[last_include_index:last_include_index] = include_lines

filter_conversion_lines = create_filter_conversion(simpl_filter_info, complex_filter_name)

lines.extend(filter_conversion_lines)

def update_filter_hpp(complex_filter_path: Path) -> None:
with open(complex_filter_path, 'r') as input_file:
lines = input_file.readlines()

update_hpp_lines(lines)

with open(complex_filter_path, 'w') as output_file:
output_file.writelines(lines)

def update_filter_cpp(complex_filter_path: Path, simpl_filter_info: SIMPLFilterInfo, complex_filter_name: str) -> None:
with open(complex_filter_path, 'r') as input_file:
lines = input_file.readlines()

update_cpp_lines(lines, simpl_filter_info, complex_filter_name)

with open(complex_filter_path, 'w') as output_file:
output_file.writelines(lines)

def update_mapping_lines(lines: List[str], simpl_uuid: str, complex_filter_name: str) -> None:
index = find_mapping_line(lines, simpl_uuid)
lines[index] = lines[index].replace('{}', f'&{complex_filter_name}::FromSIMPLJson')

def update_mapping_file(mapping_file_path: Path, simpl_uuid: str, complex_filter_name: str) -> None:
with open(mapping_file_path, 'r') as input_file:
lines = input_file.readlines()

update_mapping_lines(lines, simpl_uuid, complex_filter_name)

with open(mapping_file_path, 'w') as output_file:
output_file.writelines(lines)

def generate_converter_code(complex_source_dir: Path, simpl_json_path: Path, simpl_filters: List[str]) -> None:
mappings = get_plugin_mappings(complex_source_dir)
simpl_filters_info = read_simpl_json(simpl_json_path)
for simpl_filter_uuid in simpl_filters:
if simpl_filter_uuid not in simpl_filters_info:
raise RuntimeError(f'SIMPL filter json does not contain {simpl_filter_uuid}')
plugin_name, complex_filter_name = find_filter(mappings, simpl_filter_uuid)
mapping_file_path = get_plugin_mapping_file_path_from_root_dir(complex_source_dir, plugin_name)
complex_filter_hpp_path = get_filter_hpp_path(complex_source_dir, plugin_name, complex_filter_name)
complex_filter_cpp_path = get_filter_cpp_path(complex_source_dir, plugin_name, complex_filter_name)
update_filter_hpp(complex_filter_hpp_path)
update_filter_cpp(complex_filter_cpp_path, simpl_filters_info[simpl_filter_uuid], complex_filter_name)
update_mapping_file(mapping_file_path, simpl_filter_uuid, complex_filter_name)

# e.g. python generate_simpl_conversion_code.py . --simpl-filters "53df5340-f632-598f-8a9b-802296b3a95c"
# simpl-json is assumed to be next to this file, but can be overriden

def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('complex_source_dir', type=Path)
parser.add_argument('--simpl-json', type=Path, default=Path('./simpl_filters.json'))
parser.add_argument('--simpl-filters', nargs='+')

args = parser.parse_args()

complex_source_dir: Path = args.complex_source_dir
simpl_json: Path = args.simpl_json
simpl_filters: List[str] = args.simpl_filters

generate_converter_code(complex_source_dir.absolute(), simpl_json.absolute(), simpl_filters)

if __name__ == '__main__':
main()
Loading
Loading