From bbd74341349bdb1bbc1168def2d42b95d0b76184 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Thu, 26 Sep 2024 14:19:30 +0200 Subject: [PATCH 001/183] opengate_test refactored, almost complete, improve on checking/disabling of packages; local modules missing --- opengate/bin/opengate_tests.py | 294 +++++++++++++++++++++++++-------- 1 file changed, 223 insertions(+), 71 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index c4e5e9ca7..37e696a2b 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -7,8 +7,17 @@ import random import sys import json +import re +from pathlib import Path +import subprocess +from multiprocessing import Pool +#from functools import partial +from box import Box +import ast +import importlib.util +#import os -from opengate.exception import fatal, colored, color_ok, color_error +from opengate.exception import fatal, colored, color_ok, color_error, color_warning from opengate_core.testsDataSetup import check_tests_data_folder from opengate.bin.opengate_library_path import return_tests_path @@ -30,7 +39,21 @@ default=False, help="Start the last 10 tests and 1/4 of the others randomly", ) -def go(test_id, random_tests, no_log_on_fail): +@click.option( + "--processes_run", + "-p", + default = "legacy", + help="Start simulations in single process mode: 'legacy', 'sp' or multi process mode 'mp'" ) + +def go(test_id, random_tests, no_log_on_fail, processes_run): + files_to_run, files_to_ignore = get_files_to_run(test_id, random_tests) + files_to_run = select_files(files_to_run, test_id, random_tests) + files_to_run, deselected_count, all_missing_modules = filter_files_by_missing_modules(files_to_run) + run_test_cases(files_to_run, no_log_on_fail, processes_run) + print(f'In total {deselected_count} tests were not started, because the following modules are missing: {", ".join(all_missing_modules)}') + +def get_files_to_run(test_id, random_tests): + mypath = return_tests_path() print("Looking for tests in: " + str(mypath)) @@ -61,88 +84,185 @@ def go(test_id, random_tests, no_log_on_fail): "test045_speedup", # this is a binary (still work in progress) ] - onlyfiles = [f for f in os.listdir(str(mypath)) if (mypath / f).is_file()] - - files = [] - for f in onlyfiles: - if "wip" in f or "WIP" in f: - print(f"Ignoring: {f:<40} ") - continue - if "visu" in f: - continue - if "OLD" in f: - continue - if "old" in f: - continue - if "test" not in f: - continue - if ".py" not in f: - continue - if ".log" in f: - continue - if "all_tes" in f: - continue - if "_base" in f: - continue - if "_helpers" in f: - continue +# onlyfiles = [f for f in os.listdir(str(mypath)) if (mypath / f).is_file()] + mypath = Path(mypath) + all_file_paths = [file for file in mypath.glob('test[0-9]*.py') if file.is_file()] + # here we sort the paths + all_file_paths = sorted(all_file_paths) + + ignore_files_containing = ['wip','visu','old', '.log', 'all_tes', '_base', '_helpers'] + ignore_files_containing += ignored_tests + print(f"Going to ignore all file names that contain any of: {', '.join(ignore_files_containing)}") + files_to_run = [] + files_to_ignore = [] + + for f in all_file_paths: + eval_this_file = True + filename_for_pattern_search = str(f.name) + reason_to_ignore = '' + for string_to_ignore in ignore_files_containing: + if string_to_ignore.lower() in filename_for_pattern_search.lower(): + eval_this_file = False + reason_to_ignore = string_to_ignore + continue +# if not torch and filename_for_pattern_search in torch_tests: +## print(f"Ignoring: {f:<40} (Torch is not available) ") +# reason_to_ignore = 'Torch not avail' +# eval_this_file = False if os.name == "nt" and "_mt" in f: - continue - if f in ignored_tests: - continue - if not torch and f in torch_tests: - print(f"Ignoring: {f:<40} (Torch is not available) ") - continue - files.append(f) - - files = sorted(files) - dashboard_dict = {} - for file in files: - dashboard_dict[file] = [""] + eval_this_file = False + reason_to_ignore = 'mt & nt' + if eval_this_file: + files_to_run.append(str(f)) + else: + print(colored.stylize(f"Ignoring: {filename_for_pattern_search:<40} --> {reason_to_ignore}",color_warning), end = "\n") + files_to_ignore.append(str(f)) + print(f"Found {len(all_file_paths)} available test cases, of those {len(files_to_run)} files to run, and {len(files_to_ignore)} ignored.") + return files_to_run, files_to_ignore + +def select_files(files_to_run, test_id, random_tests): + pattern = re.compile(r"^test([0-9]+)") + if test_id != "all": test_id = int(test_id) files_new = [] - for f in files: - id = int(f[4:7]) - if id >= test_id: + for f in files_to_run: + match = pattern.match(str(Path(f).name)) + f_test_id = int(float(match.group(1))) + if f_test_id >= test_id: files_new.append(f) else: print(f"Ignoring: {f:<40} (< {test_id}) ") - files = files_new + files_to_run = files_new elif random_tests: - files_new = files[-10:] + files_new = files_to_run[-10:] prob = 0.25 - files = files_new + random.sample(files[:-10], int(prob * (len(files) - 10))) - files = sorted(files) + files = files_new + random.sample(files_to_run[:-10], int(prob * (len(files_to_run) - 10))) + files_to_run = sorted(files) + return files_to_run - print(f"Running {len(files)} tests") - print("-" * 70) +def get_imported_modules(filepath): + """ + Parse the given Python file and return a list of imported module names. + """ + imported_modules = set() + with open(filepath, 'r', encoding='utf-8') as file: + tree = ast.parse(file.read(), filename=filepath) + + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + imported_modules.add(alias.name.split('.')[0]) + elif isinstance(node, ast.ImportFrom): + imported_modules.add(node.module.split('.')[0]) + + return imported_modules +def is_module_installed(module_name): + """ + Check if a module is installed. + """ + spec = importlib.util.find_spec(module_name) + return spec is not None - failure = False +def filter_files_by_missing_modules(filepaths): + """ + Filter out files that have missing modules and return the valid files, + number of deselected files, and a set of all missing modules. + """ + valid_files = [] + deselected_count = 0 + all_missing_modules = set() # To track all missing modules + + for filepath in filepaths: + missing_modules = [] + imported_modules = get_imported_modules(filepath) + + for module in imported_modules: + if not is_module_installed(module): + missing_modules.append(module) + all_missing_modules.add(module) + + if not missing_modules: + valid_files.append(filepath) + else: + deselected_count += 1 + print(f"Missing modules in {filepath}: {', '.join(missing_modules)}") + + return valid_files, deselected_count, all_missing_modules - for f in files: - start = time.time() - print(f"Running: {f:<46} ", end="") - cmd = "python " + str(mypath / f) - log = str(mypath.parent / "log" / f) + ".log" +def run_one_test_case(f, processes_run, mypath): + start = time.time() + print(f"Running: {f:<46} ", end="") + cmd = "python " + str(mypath / f) + log = str(mypath.parent / "log" / f) + ".log" + if processes_run == 'legacy': r = os.system(f"{cmd} > {log} 2>&1") - # subprocess.run(cmd, stdout=f, shell=True, check=True) - if r == 0: - print(colored.stylize(" OK", color_ok), end="") - dashboard_dict[f] = [True] + shell_output = Box({'returncode':r,'log_fpath':log}) + + else: + shell_output = subprocess.run(f"{cmd} > {log} 2>&1" , shell=True, check=False, capture_output=True, text = True) + r = shell_output.returncode + if r == 0: + print(colored.stylize(" OK", color_ok), end="") + pass_fail_status = True + else: + if r == 2: + # this is probably a Ctrl+C, so we stop + fatal("Stopped by user") else: - if r == 2: - # this is probably a Ctrl+C, so we stop - fatal("Stopped by user") - else: - print(colored.stylize(" FAILED !", color_error), end="") - failure = True - if not no_log_on_fail: - os.system("cat " + log) - dashboard_dict[f] = [False] - end = time.time() - print(f" {end - start:5.1f} s {log:<65}") + print(colored.stylize(" FAILED !", color_error), end="") + + pass_fail_status = False + end = time.time() + print(f" {end - start:5.1f} s {log:<65}") + return shell_output + +def run_one_test_case_mp(f): + mypath = return_tests_path() + start = time.time() + print(f"Running: {f:<46} ", end="") + cmd = "python " + str(mypath / f) + log = str(mypath.parent / "log" / f) + ".log" + + shell_output = subprocess.run(f"{cmd} > {log} 2>&1" , shell=True, check=False, capture_output=True, text = True) + shell_output.log_fpath = log + r = shell_output.returncode + if r == 0: + print(colored.stylize(" OK", color_ok), end="\n") + else: + if r == 2: + # this is probably a Ctrl+C, so we stop + fatal("Stopped by user") + else: + print(colored.stylize(" FAILED !", color_error), end="\n") + end = time.time() + print(f" {end - start:5.1f} s {log:<65}") + return shell_output +def run_test_cases(files: list, no_log_on_fail: bool, processes_run:str): + mypath = return_tests_path() + print("Looking for tests in: " + str(mypath)) + print(f"Running {len(files)} tests") + print("-" * 70) + filenames = [str(Path(f).name) for f in files] + + failure = False + start = time.time() + if processes_run in ['legacy']: + run_single_case = lambda k: run_one_test_case(k, processes_run, mypath) + result_status_V = list(map(run_single_case, files)) + elif processes_run in ['sp']: + result_status_V = list(map(run_one_test_case_mp, files)) + else: + with Pool() as pool: + result_status_V = pool.map(run_one_test_case_mp, files) +# print('not implemented') + dashboard_dict = {k: [shell_output_k.returncode == 0] for k, shell_output_k in zip(files, result_status_V)} +# if not no_log_on_fail: +# for k, shell_output_k in zip(files, result_status_V): +# print(colored.stylize(f"{k} FAILED !", color_error), end="\n") +# print(shell_output_k.stdout) +# print('---------') path_output_dashboard = mypath / ".." / "output_dashboard" os.makedirs(path_output_dashboard, exist_ok=True) dashboard_output = ( @@ -157,8 +277,40 @@ def go(test_id, random_tests, no_log_on_fail): with open(path_output_dashboard / dashboard_output, "w") as fp: json.dump(dashboard_dict, fp, indent=4) print(not failure) - + tests_passed = [f for f in files if dashboard_dict[f][0]] + tests_failed = [f for f in files if not dashboard_dict[f][0]] + + n_passed = sum([k[0] for k in dashboard_dict.values()] ) # [k_i++ for v in dashboard_dict.values() if v][0] + n_failed = sum([not k[0] for k in dashboard_dict.values()] ) #[k_i++ for v in dashboard_dict.values() if not v] + + # Display the logs of the failed jobs: + for file, shell_output_k in zip(files, result_status_V): + if shell_output_k.returncode != 0 and not no_log_on_fail: + print(str(Path(file).name), colored.stylize(f": failed", color_error), end ="\n") + os.system("cat " + shell_output_k.log_fpath) + + print(f"Summary pass:{n_passed} of {len(files)} passed the tests:") + for k in tests_passed: + print(str(Path(k).name) , colored.stylize(f": passed", color_ok), end ="\n") + + print(f"Summary fail: {n_failed} of {len(files)} failed the tests:") + for k in tests_failed: + print(str(Path(k).name), colored.stylize(f": failed", color_error), end ="\n") + + end = time.time() + print(f"Evaluation took in total: {end - start:5.1f} s ") + if n_passed == len(files) and n_failed == 0: + print(colored.stylize(f'Yeahh, all tests passed!', color_ok)) + return 0 + else: + print(colored.stylize(f'Oh no, not all tests passed.',color_error)) + return 1 + # -------------------------------------------------------------------------- +#def main(): +# files, dashboard_dict = get_files_to_run() +# go(files, dashboard_dict) + if __name__ == "__main__": - go() + go() \ No newline at end of file From 2bebda33c8714d07a4500e7787f04272318ae253 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 21:40:47 +0200 Subject: [PATCH 002/183] Start implementing devtools module --- opengate/devtools.py | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 opengate/devtools.py diff --git a/opengate/devtools.py b/opengate/devtools.py new file mode 100644 index 000000000..11ff620c0 --- /dev/null +++ b/opengate/devtools.py @@ -0,0 +1,75 @@ +import inspect +import pkgutil +import importlib + +from .base import GateObject + +def check_classes_in_current_package(attribute_name, package_name=None, + attribute_type=None, sub_package_name=None, inherits_from=None): + """ + Checks for the presence of a certain attribute type (attribute, property, or method) + in all classes of the current package, optionally restricted to a sub-package. + + :param attribute_name: Name of the attribute to check. + :param attribute_type: Type of the attribute to check for (attribute, property, method). + :param sub_package_name: Name of the sub-package to restrict the check to (optional). + """ + if package_name is None: + # Get the current package's name + package_name = __package__ + + if not package_name: + raise RuntimeError("This script needs to be part of a package to work.") + + # If a sub-package is provided, use it as the base package + if sub_package_name: + package_name = f"{package_name}.{sub_package_name}" + + if inherits_from: + instance_of_module = importlib.import_module('.'.join(inherits_from.split('.')[:-1])) + instance_of_class_name = inherits_from.split('.')[-1] + instance_of_class = [c for k, c in inspect.getmembers(instance_of_module) if k == instance_of_class_name][0] + print(instance_of_class) + + # Import the target package (current or sub-package) + package = importlib.import_module(package_name) + + warnings = [] + # Iterate through all modules in the specified package + for _, module_name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + "."): + if not is_pkg: + module = importlib.import_module(module_name) + # Iterate through all members of the module + for name, obj in inspect.getmembers(module): + # Check if the object is a class + if inspect.isclass(obj): + if inherits_from is not None and instance_of_class not in obj.mro(): + continue + # Check if the class has the desired attribute + if hasattr(obj, attribute_name): + attribute = getattr(obj, attribute_name) + if attribute_type is not None: + found_attribute_type = get_attribute_type(attribute) + if found_attribute_type != attribute_type: + base_msg = (f"Class {obj.__name__} in module {module_name} " + f"has the attribute '{attribute_name}', " + "but it is not a ") + if found_attribute_type in ('method', 'property'): + warnings.append(base_msg + f'{attribute_type}.') + elif found_attribute_type == 'plain': + warnings.append(base_msg + 'plain attribute.') + else: + warnings.append( + f"Class {obj.__name__} in module {module_name} " + f"does NOT have the attribute '{attribute_name}'.") + return warnings + + +def get_attribute_type(attribute): + if callable(attribute): + return 'method' + elif isinstance(attribute, property): + return 'property' + else: + return 'plain' + From 687669b793005d2f5a057312a4ed58352bae43c5 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 21:42:37 +0200 Subject: [PATCH 003/183] Start implementing test079_check_initcpp_exists.py --- opengate/tests/src/test079_check_initcpp_exists.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100755 opengate/tests/src/test079_check_initcpp_exists.py diff --git a/opengate/tests/src/test079_check_initcpp_exists.py b/opengate/tests/src/test079_check_initcpp_exists.py new file mode 100755 index 000000000..693a1b65f --- /dev/null +++ b/opengate/tests/src/test079_check_initcpp_exists.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from opengate.devtools import check_classes_in_current_package +from opengate.base import GateObject +from opengate.exception import GateImplementationError + +if __name__ == "__main__": + warnings = check_classes_in_current_package('__initcpp__', package_name='opengate', + sub_package_name='actors', inherits_from='opengate.actors.base.ActorBase') + print(warnings) + if len(warnings): + raise GateImplementationError("Some GateObjects do not implement a '__finalize_init__' method: \n" + f"{warnings}") From bbbc95f028d6cfffd30eecd85964940f8e29cf6c Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sun, 29 Sep 2024 00:08:48 +0200 Subject: [PATCH 004/183] Update docstring and print out messages in devtools.py --- opengate/devtools.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index 11ff620c0..acb2d9870 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -11,15 +11,18 @@ def check_classes_in_current_package(attribute_name, package_name=None, in all classes of the current package, optionally restricted to a sub-package. :param attribute_name: Name of the attribute to check. - :param attribute_type: Type of the attribute to check for (attribute, property, method). + :param attribute_type: Type of the attribute to check for (plain, property, method). :param sub_package_name: Name of the sub-package to restrict the check to (optional). + :param inherits_from: Restrict the chck to classes that inherit from the class provided as inherits_from. + Needs to be a qualified name, e.g. inherits_from=opengate.actors.ActorBase """ if package_name is None: # Get the current package's name package_name = __package__ if not package_name: - raise RuntimeError("This script needs to be part of a package to work.") + raise RuntimeError("You need to either provide a package name or " + "this script needs to be part of a package to work.") # If a sub-package is provided, use it as the base package if sub_package_name: From a818528c07d3c40c6536be2310c2dd3088155b7e Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sun, 29 Sep 2024 00:09:23 +0200 Subject: [PATCH 005/183] Update test079_check_initcpp_exists.py --- opengate/tests/src/test079_check_initcpp_exists.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/opengate/tests/src/test079_check_initcpp_exists.py b/opengate/tests/src/test079_check_initcpp_exists.py index 693a1b65f..7ea425305 100755 --- a/opengate/tests/src/test079_check_initcpp_exists.py +++ b/opengate/tests/src/test079_check_initcpp_exists.py @@ -5,9 +5,11 @@ from opengate.exception import GateImplementationError if __name__ == "__main__": - warnings = check_classes_in_current_package('__initcpp__', package_name='opengate', - sub_package_name='actors', inherits_from='opengate.actors.base.ActorBase') + warnings = check_classes_in_current_package('__initcppp__', + package_name='opengate', + sub_package_name='actors', + inherits_from='opengate.actors.base.ActorBase') print(warnings) if len(warnings): - raise GateImplementationError("Some GateObjects do not implement a '__finalize_init__' method: \n" + raise GateImplementationError("Some GateObjects do not implement a '__initcpp__' method: \n" f"{warnings}") From bbe4dc95a5a6fc3d362b6f1c6d6af06003f8bd7e Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sun, 29 Sep 2024 00:16:58 +0200 Subject: [PATCH 006/183] Remove print out used for debugging --- opengate/devtools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index acb2d9870..a211cf570 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -32,7 +32,6 @@ def check_classes_in_current_package(attribute_name, package_name=None, instance_of_module = importlib.import_module('.'.join(inherits_from.split('.')[:-1])) instance_of_class_name = inherits_from.split('.')[-1] instance_of_class = [c for k, c in inspect.getmembers(instance_of_module) if k == instance_of_class_name][0] - print(instance_of_class) # Import the target package (current or sub-package) package = importlib.import_module(package_name) From 02c31c726c80348021b952a23ae386c56bb97587 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 22:19:56 +0000 Subject: [PATCH 007/183] [pre-commit.ci] Automatic python and c++ formatting --- opengate/devtools.py | 58 ++++++++++++------- .../tests/src/test079_check_initcpp_exists.py | 15 +++-- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index a211cf570..b76c5f918 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -4,8 +4,14 @@ from .base import GateObject -def check_classes_in_current_package(attribute_name, package_name=None, - attribute_type=None, sub_package_name=None, inherits_from=None): + +def check_classes_in_current_package( + attribute_name, + package_name=None, + attribute_type=None, + sub_package_name=None, + inherits_from=None, +): """ Checks for the presence of a certain attribute type (attribute, property, or method) in all classes of the current package, optionally restricted to a sub-package. @@ -21,24 +27,34 @@ def check_classes_in_current_package(attribute_name, package_name=None, package_name = __package__ if not package_name: - raise RuntimeError("You need to either provide a package name or " - "this script needs to be part of a package to work.") + raise RuntimeError( + "You need to either provide a package name or " + "this script needs to be part of a package to work." + ) # If a sub-package is provided, use it as the base package if sub_package_name: package_name = f"{package_name}.{sub_package_name}" if inherits_from: - instance_of_module = importlib.import_module('.'.join(inherits_from.split('.')[:-1])) - instance_of_class_name = inherits_from.split('.')[-1] - instance_of_class = [c for k, c in inspect.getmembers(instance_of_module) if k == instance_of_class_name][0] + instance_of_module = importlib.import_module( + ".".join(inherits_from.split(".")[:-1]) + ) + instance_of_class_name = inherits_from.split(".")[-1] + instance_of_class = [ + c + for k, c in inspect.getmembers(instance_of_module) + if k == instance_of_class_name + ][0] # Import the target package (current or sub-package) package = importlib.import_module(package_name) warnings = [] # Iterate through all modules in the specified package - for _, module_name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + "."): + for _, module_name, is_pkg in pkgutil.walk_packages( + package.__path__, package.__name__ + "." + ): if not is_pkg: module = importlib.import_module(module_name) # Iterate through all members of the module @@ -53,25 +69,27 @@ def check_classes_in_current_package(attribute_name, package_name=None, if attribute_type is not None: found_attribute_type = get_attribute_type(attribute) if found_attribute_type != attribute_type: - base_msg = (f"Class {obj.__name__} in module {module_name} " - f"has the attribute '{attribute_name}', " - "but it is not a ") - if found_attribute_type in ('method', 'property'): - warnings.append(base_msg + f'{attribute_type}.') - elif found_attribute_type == 'plain': - warnings.append(base_msg + 'plain attribute.') + base_msg = ( + f"Class {obj.__name__} in module {module_name} " + f"has the attribute '{attribute_name}', " + "but it is not a " + ) + if found_attribute_type in ("method", "property"): + warnings.append(base_msg + f"{attribute_type}.") + elif found_attribute_type == "plain": + warnings.append(base_msg + "plain attribute.") else: warnings.append( f"Class {obj.__name__} in module {module_name} " - f"does NOT have the attribute '{attribute_name}'.") + f"does NOT have the attribute '{attribute_name}'." + ) return warnings def get_attribute_type(attribute): if callable(attribute): - return 'method' + return "method" elif isinstance(attribute, property): - return 'property' + return "property" else: - return 'plain' - + return "plain" diff --git a/opengate/tests/src/test079_check_initcpp_exists.py b/opengate/tests/src/test079_check_initcpp_exists.py index 7ea425305..51715c600 100755 --- a/opengate/tests/src/test079_check_initcpp_exists.py +++ b/opengate/tests/src/test079_check_initcpp_exists.py @@ -5,11 +5,14 @@ from opengate.exception import GateImplementationError if __name__ == "__main__": - warnings = check_classes_in_current_package('__initcppp__', - package_name='opengate', - sub_package_name='actors', - inherits_from='opengate.actors.base.ActorBase') + warnings = check_classes_in_current_package( + "__initcppp__", + package_name="opengate", + sub_package_name="actors", + inherits_from="opengate.actors.base.ActorBase", + ) print(warnings) if len(warnings): - raise GateImplementationError("Some GateObjects do not implement a '__initcpp__' method: \n" - f"{warnings}") + raise GateImplementationError( + "Some GateObjects do not implement a '__initcpp__' method: \n" f"{warnings}" + ) From dcf5bfc3286a16c9812b580379b0ebd4145eaf79 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Mon, 30 Sep 2024 12:16:27 +0200 Subject: [PATCH 008/183] Extend tests.utility.test_ok to handle a list of exception as optional input --- opengate/tests/utility.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index 44ab35c22..c32854fb8 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -28,7 +28,7 @@ plt = LazyModuleLoader("matplotlib.pyplot") -def test_ok(is_ok=False): +def test_ok(is_ok=False, exceptions=None): if is_ok: s = "Great, tests are ok." s = "\n" + colored.stylize(s, color_ok) @@ -36,6 +36,14 @@ def test_ok(is_ok=False): # sys.exit(0) else: s = "Error during the tests !" + if exceptions is not None: + if isinstance(exceptions, str): + exceptions = [exceptions] + s += f"\nThe following exception" + if len(exceptions) > 1: + s += "s" + s += f" occurred:\n" + s += "\n".join([f"- {str(e)}" for e in exceptions]) s = "\n" + colored.stylize(s, color_error) print(s) sys.exit(-1) From 0b553acf5b12e2d6156dfefde4fe788f7007689b Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Mon, 30 Sep 2024 12:17:30 +0200 Subject: [PATCH 009/183] In devtools.py: split class crawling and class check; improve code --- opengate/devtools.py | 74 ++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index b76c5f918..eda47dfab 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -2,15 +2,47 @@ import pkgutil import importlib -from .base import GateObject +def get_attribute_type(attribute): + if callable(attribute): + return "method" + elif isinstance(attribute, property): + return "property" + else: + return "plain" + + +def check_if_class_has_attribute(cls, attribute_name=None, attribute_type=None): + """Check if the class has the desired attribute + """ + if attribute_name is None: + raise ValueError("kwarg 'attribute_name' is required. ") + warning = None + if hasattr(cls, attribute_name): + attribute = getattr(cls, attribute_name) + if attribute_type is not None: + found_attribute_type = get_attribute_type(attribute) + if found_attribute_type != attribute_type: + base_msg = ( + f"Class {cls.__name__} in module {cls.__module__} " + f"has the attribute '{attribute_name}', " + "but it is not a " + ) + if found_attribute_type in ("method", "property"): + warning = base_msg + f"{attribute_type}." + elif found_attribute_type == "plain": + warning = base_msg + "plain attribute." + else: + warning = (f"Class {cls.__name__} in module {cls.__module__} " + f"does NOT have the attribute '{attribute_name}'.") + return warning -def check_classes_in_current_package( - attribute_name, +def apply_class_check_to_package( + check_func, package_name=None, - attribute_type=None, sub_package_name=None, inherits_from=None, + func_kwargs=None ): """ Checks for the presence of a certain attribute type (attribute, property, or method) @@ -22,6 +54,8 @@ def check_classes_in_current_package( :param inherits_from: Restrict the chck to classes that inherit from the class provided as inherits_from. Needs to be a qualified name, e.g. inherits_from=opengate.actors.ActorBase """ + if func_kwargs is None: + func_kwargs = {} if package_name is None: # Get the current package's name package_name = __package__ @@ -63,33 +97,7 @@ def check_classes_in_current_package( if inspect.isclass(obj): if inherits_from is not None and instance_of_class not in obj.mro(): continue - # Check if the class has the desired attribute - if hasattr(obj, attribute_name): - attribute = getattr(obj, attribute_name) - if attribute_type is not None: - found_attribute_type = get_attribute_type(attribute) - if found_attribute_type != attribute_type: - base_msg = ( - f"Class {obj.__name__} in module {module_name} " - f"has the attribute '{attribute_name}', " - "but it is not a " - ) - if found_attribute_type in ("method", "property"): - warnings.append(base_msg + f"{attribute_type}.") - elif found_attribute_type == "plain": - warnings.append(base_msg + "plain attribute.") - else: - warnings.append( - f"Class {obj.__name__} in module {module_name} " - f"does NOT have the attribute '{attribute_name}'." - ) + w = check_func(obj, **func_kwargs) + if w is not None: + warnings.append(w) return warnings - - -def get_attribute_type(attribute): - if callable(attribute): - return "method" - elif isinstance(attribute, property): - return "property" - else: - return "plain" From a3b612988b246a971e0bdeb70c4c6d5e7374807d Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Mon, 30 Sep 2024 12:17:47 +0200 Subject: [PATCH 010/183] Update test079_check_initcpp_exists.py --- .../tests/src/test079_check_initcpp_exists.py | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/opengate/tests/src/test079_check_initcpp_exists.py b/opengate/tests/src/test079_check_initcpp_exists.py index 51715c600..ae0f7bc37 100755 --- a/opengate/tests/src/test079_check_initcpp_exists.py +++ b/opengate/tests/src/test079_check_initcpp_exists.py @@ -1,18 +1,46 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from opengate.devtools import check_classes_in_current_package +from opengate.devtools import check_if_class_has_attribute, apply_class_check_to_package from opengate.base import GateObject from opengate.exception import GateImplementationError +import opengate.tests.utility as utility if __name__ == "__main__": - warnings = check_classes_in_current_package( - "__initcppp__", + is_ok = True + exceptions = [] + + # check if all actors implement an __initcpp__ method as they should + attribute_name = "__initcpp__" + warnings = apply_class_check_to_package( + check_if_class_has_attribute, package_name="opengate", sub_package_name="actors", inherits_from="opengate.actors.base.ActorBase", + func_kwargs={'attribute_name': attribute_name, "attribute_type": "method"}, ) - print(warnings) if len(warnings): - raise GateImplementationError( - "Some GateObjects do not implement a '__initcpp__' method: \n" f"{warnings}" - ) + is_ok = False + s = "\n".join([f"{i}) {w}" for i, w in enumerate(warnings)]) + exceptions.append(GateImplementationError( + f"{len(warnings)} GateObjects do not implement a '{attribute_name}' method: \n{s}" + )) + + + # test the check function by performing a check that should fail! + warnings_test = apply_class_check_to_package( + check_if_class_has_attribute, + package_name="opengate", + sub_package_name="actors", + inherits_from="opengate.actors.base.ActorBase", + func_kwargs={'attribute_name': attribute_name, "attribute_type": "property"}, + ) + # so there should be warnings! + if len(warnings_test) == 0: + is_ok = False + exceptions.append(GateImplementationError("A class check should have issued a warning, but it has not. " + "The class check looked for actor classes " + "which do not implement an '__initcpp__ PROPERTY', and indeed " + "no actor class should really do that. ")) + + utility.test_ok(is_ok, exceptions=exceptions) + From e902887b892a60e6e73fe67fa6671d4a455740cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:19:03 +0000 Subject: [PATCH 011/183] [pre-commit.ci] Automatic python and c++ formatting --- opengate/devtools.py | 12 +++++---- .../tests/src/test079_check_initcpp_exists.py | 26 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index eda47dfab..df64e6f53 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -13,8 +13,7 @@ def get_attribute_type(attribute): def check_if_class_has_attribute(cls, attribute_name=None, attribute_type=None): - """Check if the class has the desired attribute - """ + """Check if the class has the desired attribute""" if attribute_name is None: raise ValueError("kwarg 'attribute_name' is required. ") warning = None @@ -33,16 +32,19 @@ def check_if_class_has_attribute(cls, attribute_name=None, attribute_type=None): elif found_attribute_type == "plain": warning = base_msg + "plain attribute." else: - warning = (f"Class {cls.__name__} in module {cls.__module__} " - f"does NOT have the attribute '{attribute_name}'.") + warning = ( + f"Class {cls.__name__} in module {cls.__module__} " + f"does NOT have the attribute '{attribute_name}'." + ) return warning + def apply_class_check_to_package( check_func, package_name=None, sub_package_name=None, inherits_from=None, - func_kwargs=None + func_kwargs=None, ): """ Checks for the presence of a certain attribute type (attribute, property, or method) diff --git a/opengate/tests/src/test079_check_initcpp_exists.py b/opengate/tests/src/test079_check_initcpp_exists.py index ae0f7bc37..2829098b2 100755 --- a/opengate/tests/src/test079_check_initcpp_exists.py +++ b/opengate/tests/src/test079_check_initcpp_exists.py @@ -16,15 +16,16 @@ package_name="opengate", sub_package_name="actors", inherits_from="opengate.actors.base.ActorBase", - func_kwargs={'attribute_name': attribute_name, "attribute_type": "method"}, + func_kwargs={"attribute_name": attribute_name, "attribute_type": "method"}, ) if len(warnings): is_ok = False s = "\n".join([f"{i}) {w}" for i, w in enumerate(warnings)]) - exceptions.append(GateImplementationError( - f"{len(warnings)} GateObjects do not implement a '{attribute_name}' method: \n{s}" - )) - + exceptions.append( + GateImplementationError( + f"{len(warnings)} GateObjects do not implement a '{attribute_name}' method: \n{s}" + ) + ) # test the check function by performing a check that should fail! warnings_test = apply_class_check_to_package( @@ -32,15 +33,18 @@ package_name="opengate", sub_package_name="actors", inherits_from="opengate.actors.base.ActorBase", - func_kwargs={'attribute_name': attribute_name, "attribute_type": "property"}, + func_kwargs={"attribute_name": attribute_name, "attribute_type": "property"}, ) # so there should be warnings! if len(warnings_test) == 0: is_ok = False - exceptions.append(GateImplementationError("A class check should have issued a warning, but it has not. " - "The class check looked for actor classes " - "which do not implement an '__initcpp__ PROPERTY', and indeed " - "no actor class should really do that. ")) + exceptions.append( + GateImplementationError( + "A class check should have issued a warning, but it has not. " + "The class check looked for actor classes " + "which do not implement an '__initcpp__ PROPERTY', and indeed " + "no actor class should really do that. " + ) + ) utility.test_ok(is_ok, exceptions=exceptions) - From 59953f60fd065402c285b43de61ba89bc5036eb3 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Tue, 1 Oct 2024 16:16:32 +0200 Subject: [PATCH 012/183] mp now default; added sp evaluation for the failed jobs in mp; added option to run the failed jobs from previous run --- opengate/bin/opengate_tests.py | 119 ++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 45 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 37e696a2b..8cf7d5e70 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -3,6 +3,7 @@ import os import time +from datetime import timedelta import click import random import sys @@ -42,16 +43,55 @@ @click.option( "--processes_run", "-p", - default = "legacy", + default = "mp", help="Start simulations in single process mode: 'legacy', 'sp' or multi process mode 'mp'" ) +@click.option( + "--run_previously_failed_jobs", + "-f", + default = False, + is_flag= True, + help="Run only the tests that failed in the previous evaluation." ) -def go(test_id, random_tests, no_log_on_fail, processes_run): - files_to_run, files_to_ignore = get_files_to_run(test_id, random_tests) - files_to_run = select_files(files_to_run, test_id, random_tests) - files_to_run, deselected_count, all_missing_modules = filter_files_by_missing_modules(files_to_run) - run_test_cases(files_to_run, no_log_on_fail, processes_run) - print(f'In total {deselected_count} tests were not started, because the following modules are missing: {", ".join(all_missing_modules)}') +def go(test_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs): + run_failed_mpjobs_in_sp = True + mypath = return_tests_path() # returns the path to the tests/src dir + test_dir_path = mypath.parent + path_output_dashboard = test_dir_path / "output_dashboard" + fpath_dashboard_output = path_output_dashboard / ( + "dashboard_output_" + + sys.platform + + "_" + + str(sys.version_info[0]) + + "." + + str(sys.version_info[1]) + + ".json" + ) + if not run_previously_failed_jobs: + files_to_run, files_to_ignore = get_files_to_run(test_id, random_tests) + files_to_run = select_files(files_to_run, test_id, random_tests) + else: + with open(fpath_dashboard_output, "r") as fp: + dashboard_dict_previously = json.load( fp) + files_to_run = [k for k,v in dashboard_dict_previously.items() if not v[0]] + avoid_tests_missing_modules = False # currently not solid enough yet + if avoid_tests_missing_modules: + files_to_run, deselected_count, all_missing_modules = filter_files_by_missing_modules(files_to_run) + print(f'In total {deselected_count} tests were not started, because the following modules are missing: {", ".join(all_missing_modules)}') + runs_status_info = run_test_cases(files_to_run, no_log_on_fail, processes_run) + + dashboard_dict, fail_status = status_summary_report(runs_status_info, files_to_run, no_log_on_fail, fpath_dashboard_output) + if run_failed_mpjobs_in_sp: + + previously_failed_files= [k for k,v in dashboard_dict.items() if not v[0]] + if fail_status and processes_run == 'mp': + print('Some files failed in multiprocessing, going to run the failed jobs in single processing mode:') + runs_status_info_sp = run_test_cases(previously_failed_files, no_log_on_fail, processes_run = 'sp') + dashboard_dict_sp, fail_status_sp = status_summary_report(runs_status_info_sp, previously_failed_files, no_log_on_fail, '') + for file, status_sp in dashboard_dict_sp.items(): + if status_sp[0] and not dashboard_dict[file][0]: + fname = Path(file).name + print(f'{fname}: failed in "multiprocessing" but succeeded in "single processing".') def get_files_to_run(test_id, random_tests): mypath = return_tests_path() @@ -83,8 +123,6 @@ def get_files_to_run(test_id, random_tests): ignored_tests = [ "test045_speedup", # this is a binary (still work in progress) ] - -# onlyfiles = [f for f in os.listdir(str(mypath)) if (mypath / f).is_file()] mypath = Path(mypath) all_file_paths = [file for file in mypath.glob('test[0-9]*.py') if file.is_file()] # here we sort the paths @@ -105,10 +143,9 @@ def get_files_to_run(test_id, random_tests): eval_this_file = False reason_to_ignore = string_to_ignore continue -# if not torch and filename_for_pattern_search in torch_tests: -## print(f"Ignoring: {f:<40} (Torch is not available) ") -# reason_to_ignore = 'Torch not avail' -# eval_this_file = False + if not torch and filename_for_pattern_search in torch_tests: + reason_to_ignore = 'Torch not avail' + eval_this_file = False if os.name == "nt" and "_mt" in f: eval_this_file = False reason_to_ignore = 'mt & nt' @@ -214,6 +251,7 @@ def run_one_test_case(f, processes_run, mypath): pass_fail_status = False end = time.time() + shell_output.run_time = start - end print(f" {end - start:5.1f} s {log:<65}") return shell_output @@ -237,6 +275,7 @@ def run_one_test_case_mp(f): else: print(colored.stylize(" FAILED !", color_error), end="\n") end = time.time() + shell_output.run_time = start - end print(f" {end - start:5.1f} s {log:<65}") return shell_output def run_test_cases(files: list, no_log_on_fail: bool, processes_run:str): @@ -244,39 +283,29 @@ def run_test_cases(files: list, no_log_on_fail: bool, processes_run:str): print("Looking for tests in: " + str(mypath)) print(f"Running {len(files)} tests") print("-" * 70) - filenames = [str(Path(f).name) for f in files] - - failure = False + start = time.time() if processes_run in ['legacy']: run_single_case = lambda k: run_one_test_case(k, processes_run, mypath) - result_status_V = list(map(run_single_case, files)) + runs_status_info = list(map(run_single_case, files)) elif processes_run in ['sp']: - result_status_V = list(map(run_one_test_case_mp, files)) + runs_status_info = list(map(run_one_test_case_mp, files)) else: with Pool() as pool: - result_status_V = pool.map(run_one_test_case_mp, files) -# print('not implemented') - dashboard_dict = {k: [shell_output_k.returncode == 0] for k, shell_output_k in zip(files, result_status_V)} -# if not no_log_on_fail: -# for k, shell_output_k in zip(files, result_status_V): -# print(colored.stylize(f"{k} FAILED !", color_error), end="\n") -# print(shell_output_k.stdout) -# print('---------') - path_output_dashboard = mypath / ".." / "output_dashboard" - os.makedirs(path_output_dashboard, exist_ok=True) - dashboard_output = ( - "dashboard_output_" - + sys.platform - + "_" - + str(sys.version_info[0]) - + "." - + str(sys.version_info[1]) - + ".json" - ) - with open(path_output_dashboard / dashboard_output, "w") as fp: - json.dump(dashboard_dict, fp, indent=4) - print(not failure) + runs_status_info = pool.map(run_one_test_case_mp, files) + + + end = time.time() + run_time = timedelta(seconds = end-start) + print(f"Evaluation took in total: {run_time.seconds /60 :5.1f} min ") + return runs_status_info +def status_summary_report(runs_status_info, files, no_log_on_fail, fpath_dashboard_output): + + dashboard_dict = {k: [shell_output_k.returncode == 0] for k, shell_output_k in zip(files, runs_status_info)} + if fpath_dashboard_output: + os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) + with open(fpath_dashboard_output, "w") as fp: + json.dump(dashboard_dict, fp, indent=4) tests_passed = [f for f in files if dashboard_dict[f][0]] tests_failed = [f for f in files if not dashboard_dict[f][0]] @@ -284,7 +313,7 @@ def run_test_cases(files: list, no_log_on_fail: bool, processes_run:str): n_failed = sum([not k[0] for k in dashboard_dict.values()] ) #[k_i++ for v in dashboard_dict.values() if not v] # Display the logs of the failed jobs: - for file, shell_output_k in zip(files, result_status_V): + for file, shell_output_k in zip(files, runs_status_info): if shell_output_k.returncode != 0 and not no_log_on_fail: print(str(Path(file).name), colored.stylize(f": failed", color_error), end ="\n") os.system("cat " + shell_output_k.log_fpath) @@ -297,14 +326,14 @@ def run_test_cases(files: list, no_log_on_fail: bool, processes_run:str): for k in tests_failed: print(str(Path(k).name), colored.stylize(f": failed", color_error), end ="\n") - end = time.time() - print(f"Evaluation took in total: {end - start:5.1f} s ") + fail_status = 0 if n_passed == len(files) and n_failed == 0: print(colored.stylize(f'Yeahh, all tests passed!', color_ok)) - return 0 + fail_status = 0 else: print(colored.stylize(f'Oh no, not all tests passed.',color_error)) - return 1 + fail_status = 1 + return dashboard_dict, fail_status # -------------------------------------------------------------------------- From e534520eb565cd6f501eae8ada34cbd7da12eb8f Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Tue, 1 Oct 2024 16:42:36 +0200 Subject: [PATCH 013/183] added nohup.out to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1ec8a5d0c..f4fde2850 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,5 @@ singles.root opengate/tests/src/geant4_out.txt failing_tests.txt opengate/tests/output_dashboard + +nohup.out From 9e9769df94ea2239a3cb4859c0fdb3f3489bc614 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 2 Oct 2024 11:30:27 +0200 Subject: [PATCH 014/183] renamed test_id to start_id; moved out save option to the main file --- opengate/bin/opengate_tests.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 8cf7d5e70..6d25cb7ec 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -26,7 +26,7 @@ @click.command(context_settings=CONTEXT_SETTINGS) -@click.option("--test_id", "-i", default="all", help="Start test from this number") +@click.option("--start_id", "-i", default="all", help="Start test from this number") @click.option( "--no_log_on_fail", default=False, @@ -52,7 +52,7 @@ is_flag= True, help="Run only the tests that failed in the previous evaluation." ) -def go(test_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs): +def go(start_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs): run_failed_mpjobs_in_sp = True mypath = return_tests_path() # returns the path to the tests/src dir test_dir_path = mypath.parent @@ -67,8 +67,8 @@ def go(test_id, random_tests, no_log_on_fail, processes_run, run_previously_fail + ".json" ) if not run_previously_failed_jobs: - files_to_run, files_to_ignore = get_files_to_run(test_id, random_tests) - files_to_run = select_files(files_to_run, test_id, random_tests) + files_to_run, files_to_ignore = get_files_to_run() + files_to_run = select_files(files_to_run, start_id, random_tests) else: with open(fpath_dashboard_output, "r") as fp: dashboard_dict_previously = json.load( fp) @@ -80,19 +80,24 @@ def go(test_id, random_tests, no_log_on_fail, processes_run, run_previously_fail runs_status_info = run_test_cases(files_to_run, no_log_on_fail, processes_run) - dashboard_dict, fail_status = status_summary_report(runs_status_info, files_to_run, no_log_on_fail, fpath_dashboard_output) + dashboard_dict, fail_status = status_summary_report(runs_status_info, files_to_run, no_log_on_fail) if run_failed_mpjobs_in_sp: - previously_failed_files= [k for k,v in dashboard_dict.items() if not v[0]] if fail_status and processes_run == 'mp': print('Some files failed in multiprocessing, going to run the failed jobs in single processing mode:') runs_status_info_sp = run_test_cases(previously_failed_files, no_log_on_fail, processes_run = 'sp') - dashboard_dict_sp, fail_status_sp = status_summary_report(runs_status_info_sp, previously_failed_files, no_log_on_fail, '') + dashboard_dict_sp, fail_status_sp = status_summary_report(runs_status_info_sp, previously_failed_files, no_log_on_fail) for file, status_sp in dashboard_dict_sp.items(): if status_sp[0] and not dashboard_dict[file][0]: fname = Path(file).name print(f'{fname}: failed in "multiprocessing" but succeeded in "single processing".') -def get_files_to_run(test_id, random_tests): + ## going to overwrite the status of multiprocessing with the sp result + dashboard_dict[file] = status_sp + if fpath_dashboard_output: + os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) + with open(fpath_dashboard_output, "w") as fp: + json.dump(dashboard_dict, fp, indent=4) +def get_files_to_run(): mypath = return_tests_path() print("Looking for tests in: " + str(mypath)) @@ -261,7 +266,7 @@ def run_one_test_case_mp(f): start = time.time() print(f"Running: {f:<46} ", end="") cmd = "python " + str(mypath / f) - log = str(mypath.parent / "log" / f) + ".log" + log = str(mypath.parent / "log" / Path(f).stem) + ".log" shell_output = subprocess.run(f"{cmd} > {log} 2>&1" , shell=True, check=False, capture_output=True, text = True) shell_output.log_fpath = log @@ -299,13 +304,10 @@ def run_test_cases(files: list, no_log_on_fail: bool, processes_run:str): run_time = timedelta(seconds = end-start) print(f"Evaluation took in total: {run_time.seconds /60 :5.1f} min ") return runs_status_info -def status_summary_report(runs_status_info, files, no_log_on_fail, fpath_dashboard_output): +def status_summary_report(runs_status_info, files, no_log_on_fail): dashboard_dict = {k: [shell_output_k.returncode == 0] for k, shell_output_k in zip(files, runs_status_info)} - if fpath_dashboard_output: - os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) - with open(fpath_dashboard_output, "w") as fp: - json.dump(dashboard_dict, fp, indent=4) + tests_passed = [f for f in files if dashboard_dict[f][0]] tests_failed = [f for f in files if not dashboard_dict[f][0]] From e960657aeefdd956ca27c33c14b021f86698c301 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:56:15 +0000 Subject: [PATCH 015/183] [pre-commit.ci] Automatic python and c++ formatting --- opengate/bin/opengate_tests.py | 239 +++++++++++++++++++++------------ 1 file changed, 154 insertions(+), 85 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 6d25cb7ec..16db0bdc6 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -12,11 +12,13 @@ from pathlib import Path import subprocess from multiprocessing import Pool -#from functools import partial + +# from functools import partial from box import Box import ast import importlib.util -#import os + +# import os from opengate.exception import fatal, colored, color_ok, color_error, color_warning from opengate_core.testsDataSetup import check_tests_data_folder @@ -43,20 +45,23 @@ @click.option( "--processes_run", "-p", - default = "mp", - help="Start simulations in single process mode: 'legacy', 'sp' or multi process mode 'mp'" ) + default="mp", + help="Start simulations in single process mode: 'legacy', 'sp' or multi process mode 'mp'", +) @click.option( "--run_previously_failed_jobs", "-f", - default = False, - is_flag= True, - help="Run only the tests that failed in the previous evaluation." ) - -def go(start_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs): + default=False, + is_flag=True, + help="Run only the tests that failed in the previous evaluation.", +) +def go( + start_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs +): run_failed_mpjobs_in_sp = True - mypath = return_tests_path() # returns the path to the tests/src dir + mypath = return_tests_path() # returns the path to the tests/src dir test_dir_path = mypath.parent - path_output_dashboard = test_dir_path / "output_dashboard" + path_output_dashboard = test_dir_path / "output_dashboard" fpath_dashboard_output = path_output_dashboard / ( "dashboard_output_" + sys.platform @@ -69,34 +74,50 @@ def go(start_id, random_tests, no_log_on_fail, processes_run, run_previously_fai if not run_previously_failed_jobs: files_to_run, files_to_ignore = get_files_to_run() files_to_run = select_files(files_to_run, start_id, random_tests) - else: + else: with open(fpath_dashboard_output, "r") as fp: - dashboard_dict_previously = json.load( fp) - files_to_run = [k for k,v in dashboard_dict_previously.items() if not v[0]] - avoid_tests_missing_modules = False # currently not solid enough yet + dashboard_dict_previously = json.load(fp) + files_to_run = [k for k, v in dashboard_dict_previously.items() if not v[0]] + avoid_tests_missing_modules = False # currently not solid enough yet if avoid_tests_missing_modules: - files_to_run, deselected_count, all_missing_modules = filter_files_by_missing_modules(files_to_run) - print(f'In total {deselected_count} tests were not started, because the following modules are missing: {", ".join(all_missing_modules)}') - + files_to_run, deselected_count, all_missing_modules = ( + filter_files_by_missing_modules(files_to_run) + ) + print( + f'In total {deselected_count} tests were not started, because the following modules are missing: {", ".join(all_missing_modules)}' + ) + runs_status_info = run_test_cases(files_to_run, no_log_on_fail, processes_run) - - dashboard_dict, fail_status = status_summary_report(runs_status_info, files_to_run, no_log_on_fail) + + dashboard_dict, fail_status = status_summary_report( + runs_status_info, files_to_run, no_log_on_fail + ) if run_failed_mpjobs_in_sp: - previously_failed_files= [k for k,v in dashboard_dict.items() if not v[0]] - if fail_status and processes_run == 'mp': - print('Some files failed in multiprocessing, going to run the failed jobs in single processing mode:') - runs_status_info_sp = run_test_cases(previously_failed_files, no_log_on_fail, processes_run = 'sp') - dashboard_dict_sp, fail_status_sp = status_summary_report(runs_status_info_sp, previously_failed_files, no_log_on_fail) + previously_failed_files = [k for k, v in dashboard_dict.items() if not v[0]] + if fail_status and processes_run == "mp": + print( + "Some files failed in multiprocessing, going to run the failed jobs in single processing mode:" + ) + runs_status_info_sp = run_test_cases( + previously_failed_files, no_log_on_fail, processes_run="sp" + ) + dashboard_dict_sp, fail_status_sp = status_summary_report( + runs_status_info_sp, previously_failed_files, no_log_on_fail + ) for file, status_sp in dashboard_dict_sp.items(): if status_sp[0] and not dashboard_dict[file][0]: - fname = Path(file).name - print(f'{fname}: failed in "multiprocessing" but succeeded in "single processing".') + fname = Path(file).name + print( + f'{fname}: failed in "multiprocessing" but succeeded in "single processing".' + ) ## going to overwrite the status of multiprocessing with the sp result dashboard_dict[file] = status_sp if fpath_dashboard_output: os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) with open(fpath_dashboard_output, "w") as fp: json.dump(dashboard_dict, fp, indent=4) + + def get_files_to_run(): mypath = return_tests_path() @@ -129,42 +150,61 @@ def get_files_to_run(): "test045_speedup", # this is a binary (still work in progress) ] mypath = Path(mypath) - all_file_paths = [file for file in mypath.glob('test[0-9]*.py') if file.is_file()] + all_file_paths = [file for file in mypath.glob("test[0-9]*.py") if file.is_file()] # here we sort the paths - all_file_paths = sorted(all_file_paths) - - ignore_files_containing = ['wip','visu','old', '.log', 'all_tes', '_base', '_helpers'] + all_file_paths = sorted(all_file_paths) + + ignore_files_containing = [ + "wip", + "visu", + "old", + ".log", + "all_tes", + "_base", + "_helpers", + ] ignore_files_containing += ignored_tests - print(f"Going to ignore all file names that contain any of: {', '.join(ignore_files_containing)}") + print( + f"Going to ignore all file names that contain any of: {', '.join(ignore_files_containing)}" + ) files_to_run = [] files_to_ignore = [] - + for f in all_file_paths: eval_this_file = True filename_for_pattern_search = str(f.name) - reason_to_ignore = '' + reason_to_ignore = "" for string_to_ignore in ignore_files_containing: if string_to_ignore.lower() in filename_for_pattern_search.lower(): eval_this_file = False reason_to_ignore = string_to_ignore continue if not torch and filename_for_pattern_search in torch_tests: - reason_to_ignore = 'Torch not avail' - eval_this_file = False + reason_to_ignore = "Torch not avail" + eval_this_file = False if os.name == "nt" and "_mt" in f: - eval_this_file = False - reason_to_ignore = 'mt & nt' + eval_this_file = False + reason_to_ignore = "mt & nt" if eval_this_file: files_to_run.append(str(f)) else: - print(colored.stylize(f"Ignoring: {filename_for_pattern_search:<40} --> {reason_to_ignore}",color_warning), end = "\n") + print( + colored.stylize( + f"Ignoring: {filename_for_pattern_search:<40} --> {reason_to_ignore}", + color_warning, + ), + end="\n", + ) files_to_ignore.append(str(f)) - print(f"Found {len(all_file_paths)} available test cases, of those {len(files_to_run)} files to run, and {len(files_to_ignore)} ignored.") + print( + f"Found {len(all_file_paths)} available test cases, of those {len(files_to_run)} files to run, and {len(files_to_ignore)} ignored." + ) return files_to_run, files_to_ignore - + + def select_files(files_to_run, test_id, random_tests): pattern = re.compile(r"^test([0-9]+)") - + if test_id != "all": test_id = int(test_id) files_new = [] @@ -179,26 +219,31 @@ def select_files(files_to_run, test_id, random_tests): elif random_tests: files_new = files_to_run[-10:] prob = 0.25 - files = files_new + random.sample(files_to_run[:-10], int(prob * (len(files_to_run) - 10))) + files = files_new + random.sample( + files_to_run[:-10], int(prob * (len(files_to_run) - 10)) + ) files_to_run = sorted(files) return files_to_run + def get_imported_modules(filepath): """ Parse the given Python file and return a list of imported module names. """ imported_modules = set() - with open(filepath, 'r', encoding='utf-8') as file: + with open(filepath, "r", encoding="utf-8") as file: tree = ast.parse(file.read(), filename=filepath) - + for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: - imported_modules.add(alias.name.split('.')[0]) + imported_modules.add(alias.name.split(".")[0]) elif isinstance(node, ast.ImportFrom): - imported_modules.add(node.module.split('.')[0]) - + imported_modules.add(node.module.split(".")[0]) + return imported_modules + + def is_module_installed(module_name): """ Check if a module is installed. @@ -206,9 +251,10 @@ def is_module_installed(module_name): spec = importlib.util.find_spec(module_name) return spec is not None + def filter_files_by_missing_modules(filepaths): """ - Filter out files that have missing modules and return the valid files, + Filter out files that have missing modules and return the valid files, number of deselected files, and a set of all missing modules. """ valid_files = [] @@ -218,32 +264,39 @@ def filter_files_by_missing_modules(filepaths): for filepath in filepaths: missing_modules = [] imported_modules = get_imported_modules(filepath) - + for module in imported_modules: if not is_module_installed(module): missing_modules.append(module) all_missing_modules.add(module) - + if not missing_modules: valid_files.append(filepath) else: deselected_count += 1 print(f"Missing modules in {filepath}: {', '.join(missing_modules)}") - + return valid_files, deselected_count, all_missing_modules + def run_one_test_case(f, processes_run, mypath): start = time.time() print(f"Running: {f:<46} ", end="") cmd = "python " + str(mypath / f) log = str(mypath.parent / "log" / f) + ".log" - if processes_run == 'legacy': + if processes_run == "legacy": r = os.system(f"{cmd} > {log} 2>&1") - shell_output = Box({'returncode':r,'log_fpath':log}) - + shell_output = Box({"returncode": r, "log_fpath": log}) + else: - shell_output = subprocess.run(f"{cmd} > {log} 2>&1" , shell=True, check=False, capture_output=True, text = True) - r = shell_output.returncode + shell_output = subprocess.run( + f"{cmd} > {log} 2>&1", + shell=True, + check=False, + capture_output=True, + text=True, + ) + r = shell_output.returncode if r == 0: print(colored.stylize(" OK", color_ok), end="") pass_fail_status = True @@ -253,7 +306,7 @@ def run_one_test_case(f, processes_run, mypath): fatal("Stopped by user") else: print(colored.stylize(" FAILED !", color_error), end="") - + pass_fail_status = False end = time.time() shell_output.run_time = start - end @@ -268,7 +321,9 @@ def run_one_test_case_mp(f): cmd = "python " + str(mypath / f) log = str(mypath.parent / "log" / Path(f).stem) + ".log" - shell_output = subprocess.run(f"{cmd} > {log} 2>&1" , shell=True, check=False, capture_output=True, text = True) + shell_output = subprocess.run( + f"{cmd} > {log} 2>&1", shell=True, check=False, capture_output=True, text=True + ) shell_output.log_fpath = log r = shell_output.returncode if r == 0: @@ -283,65 +338,79 @@ def run_one_test_case_mp(f): shell_output.run_time = start - end print(f" {end - start:5.1f} s {log:<65}") return shell_output -def run_test_cases(files: list, no_log_on_fail: bool, processes_run:str): + + +def run_test_cases(files: list, no_log_on_fail: bool, processes_run: str): mypath = return_tests_path() print("Looking for tests in: " + str(mypath)) print(f"Running {len(files)} tests") print("-" * 70) - + start = time.time() - if processes_run in ['legacy']: + if processes_run in ["legacy"]: run_single_case = lambda k: run_one_test_case(k, processes_run, mypath) - runs_status_info = list(map(run_single_case, files)) - elif processes_run in ['sp']: - runs_status_info = list(map(run_one_test_case_mp, files)) + runs_status_info = list(map(run_single_case, files)) + elif processes_run in ["sp"]: + runs_status_info = list(map(run_one_test_case_mp, files)) else: with Pool() as pool: - runs_status_info = pool.map(run_one_test_case_mp, files) + runs_status_info = pool.map(run_one_test_case_mp, files) - end = time.time() - run_time = timedelta(seconds = end-start) + run_time = timedelta(seconds=end - start) print(f"Evaluation took in total: {run_time.seconds /60 :5.1f} min ") return runs_status_info + + def status_summary_report(runs_status_info, files, no_log_on_fail): - - dashboard_dict = {k: [shell_output_k.returncode == 0] for k, shell_output_k in zip(files, runs_status_info)} + + dashboard_dict = { + k: [shell_output_k.returncode == 0] + for k, shell_output_k in zip(files, runs_status_info) + } tests_passed = [f for f in files if dashboard_dict[f][0]] tests_failed = [f for f in files if not dashboard_dict[f][0]] - - n_passed = sum([k[0] for k in dashboard_dict.values()] ) # [k_i++ for v in dashboard_dict.values() if v][0] - n_failed = sum([not k[0] for k in dashboard_dict.values()] ) #[k_i++ for v in dashboard_dict.values() if not v] - + + n_passed = sum( + [k[0] for k in dashboard_dict.values()] + ) # [k_i++ for v in dashboard_dict.values() if v][0] + n_failed = sum( + [not k[0] for k in dashboard_dict.values()] + ) # [k_i++ for v in dashboard_dict.values() if not v] + # Display the logs of the failed jobs: for file, shell_output_k in zip(files, runs_status_info): if shell_output_k.returncode != 0 and not no_log_on_fail: - print(str(Path(file).name), colored.stylize(f": failed", color_error), end ="\n") + print( + str(Path(file).name), + colored.stylize(f": failed", color_error), + end="\n", + ) os.system("cat " + shell_output_k.log_fpath) - + print(f"Summary pass:{n_passed} of {len(files)} passed the tests:") for k in tests_passed: - print(str(Path(k).name) , colored.stylize(f": passed", color_ok), end ="\n") - + print(str(Path(k).name), colored.stylize(f": passed", color_ok), end="\n") + print(f"Summary fail: {n_failed} of {len(files)} failed the tests:") for k in tests_failed: - print(str(Path(k).name), colored.stylize(f": failed", color_error), end ="\n") - + print(str(Path(k).name), colored.stylize(f": failed", color_error), end="\n") + fail_status = 0 if n_passed == len(files) and n_failed == 0: - print(colored.stylize(f'Yeahh, all tests passed!', color_ok)) + print(colored.stylize(f"Yeahh, all tests passed!", color_ok)) fail_status = 0 else: - print(colored.stylize(f'Oh no, not all tests passed.',color_error)) + print(colored.stylize(f"Oh no, not all tests passed.", color_error)) fail_status = 1 return dashboard_dict, fail_status - + # -------------------------------------------------------------------------- -#def main(): +# def main(): # files, dashboard_dict = get_files_to_run() # go(files, dashboard_dict) if __name__ == "__main__": - go() \ No newline at end of file + go() From 1a1a15f74630cf10cc31ed7d81c43ccffd41047e Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 2 Oct 2024 13:20:02 +0200 Subject: [PATCH 016/183] nicer print of logfilename --- opengate/bin/opengate_tests.py | 243 +++++++++++++++++++++------------ 1 file changed, 155 insertions(+), 88 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 6d25cb7ec..38a383327 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -12,11 +12,13 @@ from pathlib import Path import subprocess from multiprocessing import Pool -#from functools import partial + +# from functools import partial from box import Box import ast import importlib.util -#import os + +# import os from opengate.exception import fatal, colored, color_ok, color_error, color_warning from opengate_core.testsDataSetup import check_tests_data_folder @@ -43,20 +45,23 @@ @click.option( "--processes_run", "-p", - default = "mp", - help="Start simulations in single process mode: 'legacy', 'sp' or multi process mode 'mp'" ) + default="mp", + help="Start simulations in single process mode: 'legacy', 'sp' or multi process mode 'mp'", +) @click.option( "--run_previously_failed_jobs", "-f", - default = False, - is_flag= True, - help="Run only the tests that failed in the previous evaluation." ) - -def go(start_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs): + default=False, + is_flag=True, + help="Run only the tests that failed in the previous evaluation.", +) +def go( + start_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs +): run_failed_mpjobs_in_sp = True - mypath = return_tests_path() # returns the path to the tests/src dir + mypath = return_tests_path() # returns the path to the tests/src dir test_dir_path = mypath.parent - path_output_dashboard = test_dir_path / "output_dashboard" + path_output_dashboard = test_dir_path / "output_dashboard" fpath_dashboard_output = path_output_dashboard / ( "dashboard_output_" + sys.platform @@ -69,34 +74,50 @@ def go(start_id, random_tests, no_log_on_fail, processes_run, run_previously_fai if not run_previously_failed_jobs: files_to_run, files_to_ignore = get_files_to_run() files_to_run = select_files(files_to_run, start_id, random_tests) - else: + else: with open(fpath_dashboard_output, "r") as fp: - dashboard_dict_previously = json.load( fp) - files_to_run = [k for k,v in dashboard_dict_previously.items() if not v[0]] - avoid_tests_missing_modules = False # currently not solid enough yet + dashboard_dict_previously = json.load(fp) + files_to_run = [k for k, v in dashboard_dict_previously.items() if not v[0]] + avoid_tests_missing_modules = False # currently not solid enough yet if avoid_tests_missing_modules: - files_to_run, deselected_count, all_missing_modules = filter_files_by_missing_modules(files_to_run) - print(f'In total {deselected_count} tests were not started, because the following modules are missing: {", ".join(all_missing_modules)}') - + files_to_run, deselected_count, all_missing_modules = ( + filter_files_by_missing_modules(files_to_run) + ) + print( + f'In total {deselected_count} tests were not started, because the following modules are missing: {", ".join(all_missing_modules)}' + ) + runs_status_info = run_test_cases(files_to_run, no_log_on_fail, processes_run) - - dashboard_dict, fail_status = status_summary_report(runs_status_info, files_to_run, no_log_on_fail) + + dashboard_dict, fail_status = status_summary_report( + runs_status_info, files_to_run, no_log_on_fail + ) if run_failed_mpjobs_in_sp: - previously_failed_files= [k for k,v in dashboard_dict.items() if not v[0]] - if fail_status and processes_run == 'mp': - print('Some files failed in multiprocessing, going to run the failed jobs in single processing mode:') - runs_status_info_sp = run_test_cases(previously_failed_files, no_log_on_fail, processes_run = 'sp') - dashboard_dict_sp, fail_status_sp = status_summary_report(runs_status_info_sp, previously_failed_files, no_log_on_fail) + previously_failed_files = [k for k, v in dashboard_dict.items() if not v[0]] + if fail_status and processes_run == "mp": + print( + "Some files failed in multiprocessing, going to run the failed jobs in single processing mode:" + ) + runs_status_info_sp = run_test_cases( + previously_failed_files, no_log_on_fail, processes_run="sp" + ) + dashboard_dict_sp, fail_status_sp = status_summary_report( + runs_status_info_sp, previously_failed_files, no_log_on_fail + ) for file, status_sp in dashboard_dict_sp.items(): if status_sp[0] and not dashboard_dict[file][0]: - fname = Path(file).name - print(f'{fname}: failed in "multiprocessing" but succeeded in "single processing".') + fname = Path(file).name + print( + f'{fname}: failed in "multiprocessing" but succeeded in "single processing".' + ) ## going to overwrite the status of multiprocessing with the sp result dashboard_dict[file] = status_sp if fpath_dashboard_output: os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) with open(fpath_dashboard_output, "w") as fp: json.dump(dashboard_dict, fp, indent=4) + + def get_files_to_run(): mypath = return_tests_path() @@ -129,42 +150,61 @@ def get_files_to_run(): "test045_speedup", # this is a binary (still work in progress) ] mypath = Path(mypath) - all_file_paths = [file for file in mypath.glob('test[0-9]*.py') if file.is_file()] + all_file_paths = [file for file in mypath.glob("test[0-9]*.py") if file.is_file()] # here we sort the paths - all_file_paths = sorted(all_file_paths) - - ignore_files_containing = ['wip','visu','old', '.log', 'all_tes', '_base', '_helpers'] + all_file_paths = sorted(all_file_paths) + + ignore_files_containing = [ + "wip", + "visu", + "old", + ".log", + "all_tes", + "_base", + "_helpers", + ] ignore_files_containing += ignored_tests - print(f"Going to ignore all file names that contain any of: {', '.join(ignore_files_containing)}") + print( + f"Going to ignore all file names that contain any of: {', '.join(ignore_files_containing)}" + ) files_to_run = [] files_to_ignore = [] - + for f in all_file_paths: eval_this_file = True filename_for_pattern_search = str(f.name) - reason_to_ignore = '' + reason_to_ignore = "" for string_to_ignore in ignore_files_containing: if string_to_ignore.lower() in filename_for_pattern_search.lower(): eval_this_file = False reason_to_ignore = string_to_ignore continue if not torch and filename_for_pattern_search in torch_tests: - reason_to_ignore = 'Torch not avail' - eval_this_file = False + reason_to_ignore = "Torch not avail" + eval_this_file = False if os.name == "nt" and "_mt" in f: - eval_this_file = False - reason_to_ignore = 'mt & nt' + eval_this_file = False + reason_to_ignore = "mt & nt" if eval_this_file: files_to_run.append(str(f)) else: - print(colored.stylize(f"Ignoring: {filename_for_pattern_search:<40} --> {reason_to_ignore}",color_warning), end = "\n") + print( + colored.stylize( + f"Ignoring: {filename_for_pattern_search:<40} --> {reason_to_ignore}", + color_warning, + ), + end="\n", + ) files_to_ignore.append(str(f)) - print(f"Found {len(all_file_paths)} available test cases, of those {len(files_to_run)} files to run, and {len(files_to_ignore)} ignored.") + print( + f"Found {len(all_file_paths)} available test cases, of those {len(files_to_run)} files to run, and {len(files_to_ignore)} ignored." + ) return files_to_run, files_to_ignore - + + def select_files(files_to_run, test_id, random_tests): pattern = re.compile(r"^test([0-9]+)") - + if test_id != "all": test_id = int(test_id) files_new = [] @@ -179,26 +219,31 @@ def select_files(files_to_run, test_id, random_tests): elif random_tests: files_new = files_to_run[-10:] prob = 0.25 - files = files_new + random.sample(files_to_run[:-10], int(prob * (len(files_to_run) - 10))) + files = files_new + random.sample( + files_to_run[:-10], int(prob * (len(files_to_run) - 10)) + ) files_to_run = sorted(files) return files_to_run + def get_imported_modules(filepath): """ Parse the given Python file and return a list of imported module names. """ imported_modules = set() - with open(filepath, 'r', encoding='utf-8') as file: + with open(filepath, "r", encoding="utf-8") as file: tree = ast.parse(file.read(), filename=filepath) - + for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: - imported_modules.add(alias.name.split('.')[0]) + imported_modules.add(alias.name.split(".")[0]) elif isinstance(node, ast.ImportFrom): - imported_modules.add(node.module.split('.')[0]) - + imported_modules.add(node.module.split(".")[0]) + return imported_modules + + def is_module_installed(module_name): """ Check if a module is installed. @@ -206,9 +251,10 @@ def is_module_installed(module_name): spec = importlib.util.find_spec(module_name) return spec is not None + def filter_files_by_missing_modules(filepaths): """ - Filter out files that have missing modules and return the valid files, + Filter out files that have missing modules and return the valid files, number of deselected files, and a set of all missing modules. """ valid_files = [] @@ -218,43 +264,48 @@ def filter_files_by_missing_modules(filepaths): for filepath in filepaths: missing_modules = [] imported_modules = get_imported_modules(filepath) - + for module in imported_modules: if not is_module_installed(module): missing_modules.append(module) all_missing_modules.add(module) - + if not missing_modules: valid_files.append(filepath) else: deselected_count += 1 print(f"Missing modules in {filepath}: {', '.join(missing_modules)}") - + return valid_files, deselected_count, all_missing_modules + def run_one_test_case(f, processes_run, mypath): start = time.time() print(f"Running: {f:<46} ", end="") cmd = "python " + str(mypath / f) log = str(mypath.parent / "log" / f) + ".log" - if processes_run == 'legacy': + if processes_run == "legacy": r = os.system(f"{cmd} > {log} 2>&1") - shell_output = Box({'returncode':r,'log_fpath':log}) - + shell_output = Box({"returncode": r, "log_fpath": log}) + else: - shell_output = subprocess.run(f"{cmd} > {log} 2>&1" , shell=True, check=False, capture_output=True, text = True) - r = shell_output.returncode + shell_output = subprocess.run( + f"{cmd} > {log} 2>&1", + shell=True, + check=False, + capture_output=True, + text=True, + ) + r = shell_output.returncode if r == 0: print(colored.stylize(" OK", color_ok), end="") - pass_fail_status = True else: if r == 2: # this is probably a Ctrl+C, so we stop fatal("Stopped by user") else: print(colored.stylize(" FAILED !", color_error), end="") - - pass_fail_status = False + end = time.time() shell_output.run_time = start - end print(f" {end - start:5.1f} s {log:<65}") @@ -268,7 +319,9 @@ def run_one_test_case_mp(f): cmd = "python " + str(mypath / f) log = str(mypath.parent / "log" / Path(f).stem) + ".log" - shell_output = subprocess.run(f"{cmd} > {log} 2>&1" , shell=True, check=False, capture_output=True, text = True) + shell_output = subprocess.run( + f"{cmd} > {log} 2>&1", shell=True, check=False, capture_output=True, text=True + ) shell_output.log_fpath = log r = shell_output.returncode if r == 0: @@ -281,67 +334,81 @@ def run_one_test_case_mp(f): print(colored.stylize(" FAILED !", color_error), end="\n") end = time.time() shell_output.run_time = start - end - print(f" {end - start:5.1f} s {log:<65}") + print(f"Runtime: {end - start:5.1f} s {Path(log).name}") return shell_output -def run_test_cases(files: list, no_log_on_fail: bool, processes_run:str): + + +def run_test_cases(files: list, no_log_on_fail: bool, processes_run: str): mypath = return_tests_path() print("Looking for tests in: " + str(mypath)) print(f"Running {len(files)} tests") print("-" * 70) - + start = time.time() - if processes_run in ['legacy']: + if processes_run in ["legacy"]: run_single_case = lambda k: run_one_test_case(k, processes_run, mypath) - runs_status_info = list(map(run_single_case, files)) - elif processes_run in ['sp']: - runs_status_info = list(map(run_one_test_case_mp, files)) + runs_status_info = list(map(run_single_case, files)) + elif processes_run in ["sp"]: + runs_status_info = list(map(run_one_test_case_mp, files)) else: with Pool() as pool: - runs_status_info = pool.map(run_one_test_case_mp, files) + runs_status_info = pool.map(run_one_test_case_mp, files) - end = time.time() - run_time = timedelta(seconds = end-start) + run_time = timedelta(seconds=end - start) print(f"Evaluation took in total: {run_time.seconds /60 :5.1f} min ") return runs_status_info + + def status_summary_report(runs_status_info, files, no_log_on_fail): - - dashboard_dict = {k: [shell_output_k.returncode == 0] for k, shell_output_k in zip(files, runs_status_info)} + + dashboard_dict = { + k: [shell_output_k.returncode == 0] + for k, shell_output_k in zip(files, runs_status_info) + } tests_passed = [f for f in files if dashboard_dict[f][0]] tests_failed = [f for f in files if not dashboard_dict[f][0]] - - n_passed = sum([k[0] for k in dashboard_dict.values()] ) # [k_i++ for v in dashboard_dict.values() if v][0] - n_failed = sum([not k[0] for k in dashboard_dict.values()] ) #[k_i++ for v in dashboard_dict.values() if not v] - + + n_passed = sum( + [k[0] for k in dashboard_dict.values()] + ) # [k_i++ for v in dashboard_dict.values() if v][0] + n_failed = sum( + [not k[0] for k in dashboard_dict.values()] + ) # [k_i++ for v in dashboard_dict.values() if not v] + # Display the logs of the failed jobs: for file, shell_output_k in zip(files, runs_status_info): if shell_output_k.returncode != 0 and not no_log_on_fail: - print(str(Path(file).name), colored.stylize(f": failed", color_error), end ="\n") + print( + str(Path(file).name), + colored.stylize(f": failed", color_error), + end="\n", + ) os.system("cat " + shell_output_k.log_fpath) - + print(f"Summary pass:{n_passed} of {len(files)} passed the tests:") for k in tests_passed: - print(str(Path(k).name) , colored.stylize(f": passed", color_ok), end ="\n") - + print(str(Path(k).name), colored.stylize(f": passed", color_ok), end="\n") + print(f"Summary fail: {n_failed} of {len(files)} failed the tests:") for k in tests_failed: - print(str(Path(k).name), colored.stylize(f": failed", color_error), end ="\n") - + print(str(Path(k).name), colored.stylize(f": failed", color_error), end="\n") + fail_status = 0 if n_passed == len(files) and n_failed == 0: - print(colored.stylize(f'Yeahh, all tests passed!', color_ok)) + print(colored.stylize(f"Yeahh, all tests passed!", color_ok)) fail_status = 0 else: - print(colored.stylize(f'Oh no, not all tests passed.',color_error)) + print(colored.stylize(f"Oh no, not all tests passed.", color_error)) fail_status = 1 return dashboard_dict, fail_status - + # -------------------------------------------------------------------------- -#def main(): +# def main(): # files, dashboard_dict = get_files_to_run() # go(files, dashboard_dict) if __name__ == "__main__": - go() \ No newline at end of file + go() From 9bbcad29e734d6fde695cf34e11e868cd42c08e6 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Thu, 3 Oct 2024 07:47:38 +0200 Subject: [PATCH 017/183] run one test case before starting to trigger data download if needed --- opengate/bin/opengate_tests.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 38a383327..5d67c19b7 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -74,6 +74,7 @@ def go( if not run_previously_failed_jobs: files_to_run, files_to_ignore = get_files_to_run() files_to_run = select_files(files_to_run, start_id, random_tests) + download_data_at_first_run(files_to_run[0]) else: with open(fpath_dashboard_output, "r") as fp: dashboard_dict_previously = json.load(fp) @@ -280,6 +281,9 @@ def filter_files_by_missing_modules(filepaths): def run_one_test_case(f, processes_run, mypath): + """ + This function is obsolete if we don't neeed os.system(run_cmd) + """ start = time.time() print(f"Running: {f:<46} ", end="") cmd = "python " + str(mypath / f) @@ -312,6 +316,10 @@ def run_one_test_case(f, processes_run, mypath): return shell_output +def download_data_at_first_run(f): + run_one_test_case_mp(f) + + def run_one_test_case_mp(f): mypath = return_tests_path() start = time.time() From ee3353caee89abb2dbd61a6801378a967ba0eeb7 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Thu, 3 Oct 2024 16:30:30 +0200 Subject: [PATCH 018/183] filter files with dependencies on previous simulations and run them in two steps --- opengate/bin/opengate_tests.py | 188 +++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 78 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 72e082c0b..e63dec075 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -58,9 +58,9 @@ def go( start_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs ): - run_failed_mpjobs_in_sp = True - mypath = return_tests_path() # returns the path to the tests/src dir - test_dir_path = mypath.parent + run_failed_mpjobs_in_sp = False + path_tests_src = return_tests_path() # returns the path to the tests/src dir + test_dir_path = path_tests_src.parent path_output_dashboard = test_dir_path / "output_dashboard" fpath_dashboard_output = path_output_dashboard / ( "dashboard_output_" @@ -71,28 +71,44 @@ def go( + str(sys.version_info[1]) + ".json" ) + files_to_run_avail, files_to_ignore = get_files_to_run() if not run_previously_failed_jobs: - files_to_run, files_to_ignore = get_files_to_run() - files_to_run = select_files(files_to_run, start_id, random_tests) - download_data_at_first_run(files_to_run[0]) + files_to_run = select_files(files_to_run_avail, start_id, random_tests) + download_data_at_first_run(files_to_run_avail[0]) else: with open(fpath_dashboard_output, "r") as fp: dashboard_dict_previously = json.load(fp) files_to_run = [k for k, v in dashboard_dict_previously.items() if not v[0]] - avoid_tests_missing_modules = False # currently not solid enough yet - if avoid_tests_missing_modules: - files_to_run, deselected_count, all_missing_modules = ( - filter_files_by_missing_modules(files_to_run) - ) + + files_to_run_part1, files_to_run_part2_depending_on_part1 = ( + filter_files_with_dependencies(files_to_run, path_tests_src) + ) + if len(files_to_run_part2_depending_on_part1) > 0: print( - f'In total {deselected_count} tests were not started, because the following modules are missing: {", ".join(all_missing_modules)}' + f"Found test cases with mutual dependencies, going to split evaluation into two sets. {len(files_to_run_part2_depending_on_part1)} tests will start right after first eval round." ) - - runs_status_info = run_test_cases(files_to_run, no_log_on_fail, processes_run) + runs_status_info = run_test_cases(files_to_run_part1, no_log_on_fail, processes_run) dashboard_dict, fail_status = status_summary_report( - runs_status_info, files_to_run, no_log_on_fail + runs_status_info, files_to_run_part1, no_log_on_fail ) + + if len(files_to_run_part2_depending_on_part1) > 0: + print( + "Now starting evaluation of tests depending on results of previous tests:" + ) + runs_status_info_part2 = run_test_cases( + files_to_run_part2_depending_on_part1, no_log_on_fail, processes_run + ) + + dashboard_dict_part2, fail_status_part2 = status_summary_report( + runs_status_info_part2, + files_to_run_part2_depending_on_part1, + no_log_on_fail, + ) + dashboard_dict.update(dashboard_dict_part2) + fail_status = fail_status and fail_status_part2 + if run_failed_mpjobs_in_sp: previously_failed_files = [k for k, v in dashboard_dict.items() if not v[0]] if fail_status and processes_run == "mp": @@ -121,8 +137,8 @@ def go( def get_files_to_run(): - mypath = return_tests_path() - print("Looking for tests in: " + str(mypath)) + path_tests_src = return_tests_path() + print("Looking for tests in: " + str(path_tests_src)) if not check_tests_data_folder(): return False @@ -150,8 +166,10 @@ def get_files_to_run(): ignored_tests = [ "test045_speedup", # this is a binary (still work in progress) ] - mypath = Path(mypath) - all_file_paths = [file for file in mypath.glob("test[0-9]*.py") if file.is_file()] + path_tests_src = Path(path_tests_src) + all_file_paths = [ + file for file in path_tests_src.glob("test[0-9]*.py") if file.is_file() + ] # here we sort the paths all_file_paths = sorted(all_file_paths) @@ -227,67 +245,81 @@ def select_files(files_to_run, test_id, random_tests): return files_to_run -def get_imported_modules(filepath): - """ - Parse the given Python file and return a list of imported module names. - """ - imported_modules = set() - with open(filepath, "r", encoding="utf-8") as file: - tree = ast.parse(file.read(), filename=filepath) +def get_main_function_args(file_path): + with open(file_path, "r") as file: + tree = ast.parse(file.read(), filename=file_path) + # Find the 'main' function in the AST for node in ast.walk(tree): - if isinstance(node, ast.Import): - for alias in node.names: - imported_modules.add(alias.name.split(".")[0]) - elif isinstance(node, ast.ImportFrom): - imported_modules.add(node.module.split(".")[0]) - - return imported_modules - - -def is_module_installed(module_name): - """ - Check if a module is installed. - """ - spec = importlib.util.find_spec(module_name) - return spec is not None - - -def filter_files_by_missing_modules(filepaths): - """ - Filter out files that have missing modules and return the valid files, - number of deselected files, and a set of all missing modules. - """ - valid_files = [] - deselected_count = 0 - all_missing_modules = set() # To track all missing modules - - for filepath in filepaths: - missing_modules = [] - imported_modules = get_imported_modules(filepath) - - for module in imported_modules: - if not is_module_installed(module): - missing_modules.append(module) - all_missing_modules.add(module) - - if not missing_modules: - valid_files.append(filepath) - else: - deselected_count += 1 - print(f"Missing modules in {filepath}: {', '.join(missing_modules)}") + if isinstance(node, ast.FunctionDef) and node.name == "main": + # Extract the arguments of the main function + + args_info = {} + # Handle cases where some arguments have no defaults + num_defaults = len(node.args.defaults) + num_args = len(node.args.args) + non_default_args = num_args - num_defaults + for i, arg in enumerate(node.args.args): + arg_name = arg.arg + if i >= non_default_args: + # Match argument with its default value + default_value = node.args.defaults[i - non_default_args] + # default_value_str = ast.dump(default_value).value() + if isinstance(default_value, ast.Constant): + default_value_str = default_value.value + else: + default_value_str = None + else: + default_value_str = None + args_info[arg_name] = default_value_str + return args_info + + # Return None if no 'main' function is found + return None + + +def analyze_scripts(files): + files_dependence_on = {} + for file_path in files: + args = get_main_function_args(file_path) + file_depending_on = None + if args is not None and "dependency" in args: + file_depending_on = str(args["dependency"]) + print(file_depending_on) + + files_dependence_on[file_path] = file_depending_on + # print(f'{file_path} {args = }') + return files_dependence_on + + +def filter_files_with_dependencies(files_to_run, path_tests_src): + files_dependence_dict = analyze_scripts(files_to_run) + files_needed_for_other_tests = [ + str(path_tests_src / needed_file) + for file, needed_file in files_dependence_dict.items() + if needed_file + ] - return valid_files, deselected_count, all_missing_modules + files_to_run_part1 = [f for f in files_to_run if not files_dependence_dict[f]] + files_to_run_part1 += files_needed_for_other_tests + files_to_run_part1 = list(set(files_to_run_part1)) + files_to_run_part2_depending_on_part1 = [ + f for f in files_to_run if files_dependence_dict[f] + ] + # print(f'{files_to_run_part1 = }') + # print(f'{files_to_run_part2_depending_on_part1 = }') + # return 0 + return files_to_run_part1, files_to_run_part2_depending_on_part1 -def run_one_test_case(f, processes_run, mypath): +def run_one_test_case(f, processes_run, path_tests_src): """ This function is obsolete if we don't neeed os.system(run_cmd) """ start = time.time() print(f"Running: {f:<46} ", end="") - cmd = "python " + str(mypath / f) - log = str(mypath.parent / "log" / f) + ".log" + cmd = "python " + str(path_tests_src / f) + log = str(path_tests_src.parent / "log" / f) + ".log" if processes_run == "legacy": r = os.system(f"{cmd} > {log} 2>&1") shell_output = Box({"returncode": r, "log_fpath": log}) @@ -319,11 +351,11 @@ def download_data_at_first_run(f): def run_one_test_case_mp(f): - mypath = return_tests_path() + path_tests_src = return_tests_path() start = time.time() print(f"Running: {f:<46} ", end="") - cmd = "python " + str(mypath / f) - log = str(mypath.parent / "log" / Path(f).stem) + ".log" + cmd = "python " + str(path_tests_src / f) + log = str(path_tests_src.parent / "log" / Path(f).stem) + ".log" shell_output = subprocess.run( f"{cmd} > {log} 2>&1", shell=True, check=False, capture_output=True, text=True @@ -345,14 +377,14 @@ def run_one_test_case_mp(f): def run_test_cases(files: list, no_log_on_fail: bool, processes_run: str): - mypath = return_tests_path() - print("Looking for tests in: " + str(mypath)) + path_tests_src = return_tests_path() + print("Looking for tests in: " + str(path_tests_src)) print(f"Running {len(files)} tests") print("-" * 70) start = time.time() if processes_run in ["legacy"]: - run_single_case = lambda k: run_one_test_case(k, processes_run, mypath) + run_single_case = lambda k: run_one_test_case(k, processes_run, path_tests_src) runs_status_info = list(map(run_single_case, files)) elif processes_run in ["sp"]: runs_status_info = list(map(run_one_test_case_mp, files)) @@ -393,11 +425,11 @@ def status_summary_report(runs_status_info, files, no_log_on_fail): ) os.system("cat " + shell_output_k.log_fpath) - print(f"Summary pass:{n_passed} of {len(files)} passed the tests:") + print(f"Summary pass: {n_passed}/{len(files)} passed the tests:") for k in tests_passed: print(str(Path(k).name), colored.stylize(f": passed", color_ok), end="\n") - print(f"Summary fail: {n_failed} of {len(files)} failed the tests:") + print(f"Summary fail: {n_failed}/{len(files)} failed the tests:") for k in tests_failed: print(str(Path(k).name), colored.stylize(f": failed", color_error), end="\n") From 31a2db94be5b21c3c1fa8a8b0765a37750fa56c8 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Fri, 4 Oct 2024 12:04:34 +0200 Subject: [PATCH 019/183] explicitly declared dependency on other tests in tests --- opengate/bin/opengate_tests.py | 96 +++++++++---------- opengate/tests/src/test022_half_life.py | 19 ++-- opengate/tests/src/test022_half_life_mt.py | 14 ++- .../tests/src/test040_gan_phsp_pet_gan.py | 12 ++- .../tests/src/test053_phid_06_it_model.py | 9 +- .../tests/src/test053_phid_09_ar_model.py | 9 +- .../tests/src/test053_phid_11_all_model.py | 9 +- .../tests/src/test053_phid_12_all_model_mt.py | 9 +- opengate/tests/src/test069_sim_from_dict.py | 12 ++- .../src/test072_coinc_sorter_2keepAll.py | 12 ++- .../test072_coinc_sorter_2removeMultiples.py | 9 +- 11 files changed, 122 insertions(+), 88 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index e63dec075..49863d6c5 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -72,13 +72,16 @@ def go( + ".json" ) files_to_run_avail, files_to_ignore = get_files_to_run() + if not run_previously_failed_jobs: files_to_run = select_files(files_to_run_avail, start_id, random_tests) download_data_at_first_run(files_to_run_avail[0]) else: with open(fpath_dashboard_output, "r") as fp: dashboard_dict_previously = json.load(fp) - files_to_run = [k for k, v in dashboard_dict_previously.items() if not v[0]] + files_to_run = [ + k for k, v in dashboard_dict_previously.items() if v and not v[0] + ] files_to_run_part1, files_to_run_part2_depending_on_part1 = ( filter_files_with_dependencies(files_to_run, path_tests_src) @@ -89,10 +92,6 @@ def go( ) runs_status_info = run_test_cases(files_to_run_part1, no_log_on_fail, processes_run) - dashboard_dict, fail_status = status_summary_report( - runs_status_info, files_to_run_part1, no_log_on_fail - ) - if len(files_to_run_part2_depending_on_part1) > 0: print( "Now starting evaluation of tests depending on results of previous tests:" @@ -101,13 +100,16 @@ def go( files_to_run_part2_depending_on_part1, no_log_on_fail, processes_run ) - dashboard_dict_part2, fail_status_part2 = status_summary_report( - runs_status_info_part2, - files_to_run_part2_depending_on_part1, + dashboard_dict, fail_status_part2 = status_summary_report( + runs_status_info + runs_status_info_part2, + files_to_run_part1 + files_to_run_part2_depending_on_part1, no_log_on_fail, ) - dashboard_dict.update(dashboard_dict_part2) - fail_status = fail_status and fail_status_part2 + + else: + dashboard_dict, fail_status = status_summary_report( + runs_status_info, files_to_run_part1, no_log_on_fail + ) if run_failed_mpjobs_in_sp: previously_failed_files = [k for k, v in dashboard_dict.items() if not v[0]] @@ -129,10 +131,12 @@ def go( ) ## going to overwrite the status of multiprocessing with the sp result dashboard_dict[file] = status_sp + dashboard_dict_out = {k: [] for k in files_to_run_avail} + dashboard_dict_out.update(dashboard_dict) if fpath_dashboard_output: os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) with open(fpath_dashboard_output, "w") as fp: - json.dump(dashboard_dict, fp, indent=4) + json.dump(dashboard_dict_out, fp, indent=4) def get_files_to_run(): @@ -168,7 +172,7 @@ def get_files_to_run(): ] path_tests_src = Path(path_tests_src) all_file_paths = [ - file for file in path_tests_src.glob("test[0-9]*.py") if file.is_file() + file.name for file in path_tests_src.glob("test[0-9]*.py") if file.is_file() ] # here we sort the paths all_file_paths = sorted(all_file_paths) @@ -189,32 +193,31 @@ def get_files_to_run(): files_to_run = [] files_to_ignore = [] - for f in all_file_paths: + for filename in all_file_paths: eval_this_file = True - filename_for_pattern_search = str(f.name) reason_to_ignore = "" for string_to_ignore in ignore_files_containing: - if string_to_ignore.lower() in filename_for_pattern_search.lower(): + if string_to_ignore.lower() in filename.lower(): eval_this_file = False reason_to_ignore = string_to_ignore continue - if not torch and filename_for_pattern_search in torch_tests: + if not torch and filename in torch_tests: reason_to_ignore = "Torch not avail" eval_this_file = False - if os.name == "nt" and "_mt" in f: + if os.name == "nt" and "_mt" in filename: eval_this_file = False reason_to_ignore = "mt & nt" if eval_this_file: - files_to_run.append(str(f)) + files_to_run.append(filename) else: print( colored.stylize( - f"Ignoring: {filename_for_pattern_search:<40} --> {reason_to_ignore}", + f"Ignoring: {filename:<40} --> {reason_to_ignore}", color_warning, ), end="\n", ) - files_to_ignore.append(str(f)) + files_to_ignore.append(filename) print( f"Found {len(all_file_paths)} available test cases, of those {len(files_to_run)} files to run, and {len(files_to_ignore)} ignored." ) @@ -228,7 +231,7 @@ def select_files(files_to_run, test_id, random_tests): test_id = int(test_id) files_new = [] for f in files_to_run: - match = pattern.match(str(Path(f).name)) + match = pattern.match(f) f_test_id = int(float(match.group(1))) if f_test_id >= test_id: files_new.append(f) @@ -245,8 +248,8 @@ def select_files(files_to_run, test_id, random_tests): return files_to_run -def get_main_function_args(file_path): - with open(file_path, "r") as file: +def get_main_function_args(file_dir, file_path): + with open(file_dir / file_path, "r") as file: tree = ast.parse(file.read(), filename=file_path) # Find the 'main' function in the AST @@ -278,24 +281,21 @@ def get_main_function_args(file_path): return None -def analyze_scripts(files): +def analyze_scripts(file_dir, files): files_dependence_on = {} for file_path in files: - args = get_main_function_args(file_path) + args = get_main_function_args(file_dir, file_path) file_depending_on = None if args is not None and "dependency" in args: - file_depending_on = str(args["dependency"]) - print(file_depending_on) - + file_depending_on = args["dependency"] files_dependence_on[file_path] = file_depending_on - # print(f'{file_path} {args = }') return files_dependence_on def filter_files_with_dependencies(files_to_run, path_tests_src): - files_dependence_dict = analyze_scripts(files_to_run) + files_dependence_dict = analyze_scripts(path_tests_src, files_to_run) files_needed_for_other_tests = [ - str(path_tests_src / needed_file) + needed_file for file, needed_file in files_dependence_dict.items() if needed_file ] @@ -306,9 +306,6 @@ def filter_files_with_dependencies(files_to_run, path_tests_src): files_to_run_part2_depending_on_part1 = [ f for f in files_to_run if files_dependence_dict[f] ] - # print(f'{files_to_run_part1 = }') - # print(f'{files_to_run_part2_depending_on_part1 = }') - # return 0 return files_to_run_part1, files_to_run_part2_depending_on_part1 @@ -347,6 +344,7 @@ def run_one_test_case(f, processes_run, path_tests_src): def download_data_at_first_run(f): + print("Running one test case to trigger download of data if not available yet.") run_one_test_case_mp(f) @@ -401,52 +399,44 @@ def run_test_cases(files: list, no_log_on_fail: bool, processes_run: str): def status_summary_report(runs_status_info, files, no_log_on_fail): dashboard_dict = { - k: [shell_output_k.returncode == 0] + str(Path(k).name): [shell_output_k.returncode == 0] for k, shell_output_k in zip(files, runs_status_info) } tests_passed = [f for f in files if dashboard_dict[f][0]] tests_failed = [f for f in files if not dashboard_dict[f][0]] - n_passed = sum( - [k[0] for k in dashboard_dict.values()] - ) # [k_i++ for v in dashboard_dict.values() if v][0] - n_failed = sum( - [not k[0] for k in dashboard_dict.values()] - ) # [k_i++ for v in dashboard_dict.values() if not v] + n_passed = sum([k[0] for k in dashboard_dict.values()]) + n_failed = sum([not k[0] for k in dashboard_dict.values()]) # Display the logs of the failed jobs: for file, shell_output_k in zip(files, runs_status_info): if shell_output_k.returncode != 0 and not no_log_on_fail: print( str(Path(file).name), - colored.stylize(f": failed", color_error), + colored.stylize(": failed", color_error), end="\n", ) os.system("cat " + shell_output_k.log_fpath) print(f"Summary pass: {n_passed}/{len(files)} passed the tests:") for k in tests_passed: - print(str(Path(k).name), colored.stylize(f": passed", color_ok), end="\n") + print(str(Path(k).name), colored.stylize(": passed", color_ok), end="\n") - print(f"Summary fail: {n_failed}/{len(files)} failed the tests:") - for k in tests_failed: - print(str(Path(k).name), colored.stylize(f": failed", color_error), end="\n") + if n_failed > 0: + print(f"Summary fail: {n_failed}/{len(files)} failed the tests:") + for k in tests_failed: + print(str(Path(k).name), colored.stylize(": failed", color_error), end="\n") fail_status = 0 if n_passed == len(files) and n_failed == 0: - print(colored.stylize(f"Yeahh, all tests passed!", color_ok)) + print(colored.stylize("Yeahh, all tests passed!", color_ok)) fail_status = 0 else: - print(colored.stylize(f"Oh no, not all tests passed.", color_error)) + print(colored.stylize("Oh no, not all tests passed.", color_error)) fail_status = 1 return dashboard_dict, fail_status -# -------------------------------------------------------------------------- -# def main(): -# files, dashboard_dict = get_files_to_run() -# go(files, dashboard_dict) - if __name__ == "__main__": go() diff --git a/opengate/tests/src/test022_half_life.py b/opengate/tests/src/test022_half_life.py index cca716a81..a3d86e05d 100755 --- a/opengate/tests/src/test022_half_life.py +++ b/opengate/tests/src/test022_half_life.py @@ -6,22 +6,17 @@ import uproot import sys -if __name__ == "__main__": + +def test022_half_life(n_threads=1): paths = utility.get_default_test_paths(__file__, output_folder="test022") # create the simulation sim = gate.Simulation() - # multithread ? - argv = sys.argv - n = 1 - if len(argv) > 1: - n = int(argv[1]) - # main options sim.g4_verbose = False sim.visu = False - sim.number_of_threads = n + sim.number_of_threads = n_threads sim.random_seed = 12344321 sim.output_dir = paths.output print(sim) @@ -182,3 +177,11 @@ plt.savefig(fn) utility.test_ok(is_ok) + + +def main(): + test022_half_life(n_threads=1) + + +if __name__ == "__main__": + main() diff --git a/opengate/tests/src/test022_half_life_mt.py b/opengate/tests/src/test022_half_life_mt.py index 91d899a7a..da9dec4e1 100755 --- a/opengate/tests/src/test022_half_life_mt.py +++ b/opengate/tests/src/test022_half_life_mt.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import sys -import pathlib -import os +from opengate.tests.src import test022_half_life -if __name__ == "__main__": - pathFile = pathlib.Path(__file__).parent.resolve() - cmd = "python " + os.path.join(pathFile, "test022_half_life.py") + " 3" - r = os.system(cmd) +def main(): + test022_half_life.test022_half_life(n_threads=3) + - sys.exit(r) +if __name__ == "__main__": + main() diff --git a/opengate/tests/src/test040_gan_phsp_pet_gan.py b/opengate/tests/src/test040_gan_phsp_pet_gan.py index 30963a1ff..06b0b8c69 100755 --- a/opengate/tests/src/test040_gan_phsp_pet_gan.py +++ b/opengate/tests/src/test040_gan_phsp_pet_gan.py @@ -12,14 +12,18 @@ import opengate.contrib.phantoms.nemaiec as gate_iec from opengate.tests import utility -if __name__ == "__main__": +global all_cond + + +def main(dependency="test040_gan_phsp_pet_aref.py"): + global all_cond paths = utility.get_default_test_paths(__file__, "", "test040") # The test needs the output of test040_gan_phsp_pet_aref.py # If the output of test040_gan_phsp_pet_aref.py does not exist (eg: random test), create it if not os.path.isfile(paths.output / "test040_gan_phsp.root"): print("---------- Begin of test040_gan_phsp_pet_aref.py ----------") - subprocess.call(["python", paths.current / "test040_gan_phsp_pet_aref.py"]) + subprocess.call(["python", paths.current / dependency]) print("----------- End of test040_gan_phsp_pet_aref.py -----------") # create the simulation @@ -371,3 +375,7 @@ def gen_cond(n): # this is the end, my friend utility.test_ok(is_ok) + + +if __name__ == "__main__": + main() diff --git a/opengate/tests/src/test053_phid_06_it_model.py b/opengate/tests/src/test053_phid_06_it_model.py index 2d977df5c..74087987b 100755 --- a/opengate/tests/src/test053_phid_06_it_model.py +++ b/opengate/tests/src/test053_phid_06_it_model.py @@ -4,7 +4,8 @@ from test053_phid_helpers2 import * import opengate as gate -if __name__ == "__main__": + +def main(dependency="test053_phid_05_it_ref_mt.py"): paths = get_default_test_paths(__file__, "", output_folder="test053") # bi213 83 213 @@ -22,7 +23,7 @@ if os.name == "nt": test_ok(True) sys.exit(0) - cmd = "python " + str(paths.current / "test053_phid_05_it_ref_mt.py") + cmd = "python " + str(paths.current / dependency) r = os.system(cmd) sim = gate.Simulation() @@ -63,3 +64,7 @@ ) test_ok(is_ok) + + +if __name__ == "__main__": + main() diff --git a/opengate/tests/src/test053_phid_09_ar_model.py b/opengate/tests/src/test053_phid_09_ar_model.py index f17e8f15a..c4aa615b8 100755 --- a/opengate/tests/src/test053_phid_09_ar_model.py +++ b/opengate/tests/src/test053_phid_09_ar_model.py @@ -4,7 +4,8 @@ from test053_phid_helpers2 import * import opengate as gate -if __name__ == "__main__": + +def main(dependency="test053_phid_08_ar_ref_mt.py"): paths = get_default_test_paths(__file__, "", output_folder="test053") # bi213 83 213 @@ -26,7 +27,7 @@ if os.name == "nt": test_ok(True) sys.exit(0) - cmd = "python " + str(paths.current / "test053_phid_08_ar_ref_mt.py") + cmd = "python " + str(paths.current / dependency) r = os.system(cmd) sim = gate.Simulation() @@ -66,3 +67,7 @@ ) test_ok(is_ok) + + +if __name__ == "__main__": + main() diff --git a/opengate/tests/src/test053_phid_11_all_model.py b/opengate/tests/src/test053_phid_11_all_model.py index c132716a6..c90e665fd 100755 --- a/opengate/tests/src/test053_phid_11_all_model.py +++ b/opengate/tests/src/test053_phid_11_all_model.py @@ -3,7 +3,8 @@ from test053_phid_helpers2 import * -if __name__ == "__main__": + +def main(dependency="test053_phid_10_all_ref_mt.py"): paths = get_default_test_paths(__file__, "", output_folder="test053") # bi213 83 213 @@ -25,7 +26,7 @@ if os.name == "nt": test_ok(True) sys.exit(0) - cmd = "python " + str(paths.current / "test053_phid_10_all_ref_mt.py") + cmd = "python " + str(paths.current / dependency) r = os.system(cmd) sim = gate.Simulation() @@ -72,3 +73,7 @@ ) test_ok(is_ok) + + +if __name__ == "__main__": + main() diff --git a/opengate/tests/src/test053_phid_12_all_model_mt.py b/opengate/tests/src/test053_phid_12_all_model_mt.py index 55377d1fa..5de9bc195 100755 --- a/opengate/tests/src/test053_phid_12_all_model_mt.py +++ b/opengate/tests/src/test053_phid_12_all_model_mt.py @@ -2,7 +2,8 @@ # -*- coding: utf-8 -*- from test053_phid_helpers2 import * -if __name__ == "__main__": + +def main(dependency="test053_phid_10_all_ref_mt.py"): paths = get_default_test_paths(__file__, "", output_folder="test053") # bi213 83 213 @@ -24,7 +25,7 @@ if os.name == "nt": test_ok(True) sys.exit(0) - cmd = "python " + str(paths.current / "test053_phid_10_all_ref_mt.py") + cmd = "python " + str(paths.current / dependency) r = os.system(cmd) sim = gate.Simulation() @@ -71,3 +72,7 @@ ) test_ok(is_ok) + + +if __name__ == "__main__": + main() diff --git a/opengate/tests/src/test069_sim_from_dict.py b/opengate/tests/src/test069_sim_from_dict.py index c670547fc..1163fd41a 100755 --- a/opengate/tests/src/test069_sim_from_dict.py +++ b/opengate/tests/src/test069_sim_from_dict.py @@ -7,18 +7,18 @@ import os import subprocess + # This test is to be run after test069_sim_as_dict.py # It checks whether the simulation is recreated correctly from the JSON file, # but does not actually run any simulation. - -if __name__ == "__main__": +def main(a, b, c, dependency="test069_sim_as_dict.py"): pathFile = pathlib.Path(__file__).parent.resolve() paths = utility.get_default_test_paths(__file__) # the test069_sim_as_dict.py is needed first - if not os.path.isfile(paths.output / "simu_test069.json"): + if dependency and not os.path.isfile(paths.output / "simu_test069.json"): print(f"Running test069_sim_as_dict.py") - subprocess.call(["python", paths.current / "test069_sim_as_dict.py"]) + subprocess.call(["python", paths.current / dependency]) # create the simulation sim = gate.create_sim_from_json(paths.output / "test069" / "simu_test069.json") @@ -36,3 +36,7 @@ # If we make it until here without exception, the test is passed utility.test_ok(True) + + +if __name__ == "__main__": + main(1, 2, 3) diff --git a/opengate/tests/src/test072_coinc_sorter_2keepAll.py b/opengate/tests/src/test072_coinc_sorter_2keepAll.py index 4aeedd6f9..cf160d7e4 100755 --- a/opengate/tests/src/test072_coinc_sorter_2keepAll.py +++ b/opengate/tests/src/test072_coinc_sorter_2keepAll.py @@ -11,7 +11,9 @@ import subprocess import uproot -if __name__ == "__main__": + +def main(dependency="test072_coinc_sorter_1.py"): + # test paths paths = utility.get_default_test_paths( __file__, output_folder="test072_coinc_sorter" @@ -19,9 +21,9 @@ # The test needs the output of test072_coinc_sorter_1.py # If the output of test072_coinc_sorter_1.py does not exist, create it - if not os.path.isfile(paths.output / "test72_output_1.root"): + if dependency and not os.path.isfile(paths.output / "test72_output_1.root"): print("---------- Begin of test072_coinc_sorter_1.py ----------") - subprocess.call(["python", paths.current / "test072_coinc_sorter_1.py"]) + subprocess.call(["python", paths.current / dependency]) print("----------- End of test072_coinc_sorter_1.py -----------") # open root file @@ -61,3 +63,7 @@ is_ok = utility.check_diff_abs(int(nc), int(nc_ref), tolerance=nc_tol, txt="") utility.test_ok(is_ok) + + +if __name__ == "__main__": + main() diff --git a/opengate/tests/src/test072_coinc_sorter_2removeMultiples.py b/opengate/tests/src/test072_coinc_sorter_2removeMultiples.py index 2de778124..39b4c7220 100755 --- a/opengate/tests/src/test072_coinc_sorter_2removeMultiples.py +++ b/opengate/tests/src/test072_coinc_sorter_2removeMultiples.py @@ -11,7 +11,8 @@ import subprocess import os -if __name__ == "__main__": + +def main(dependency="test072_coinc_sorter_1.py"): # test paths paths = utility.get_default_test_paths( __file__, output_folder="test072_coinc_sorter" @@ -21,7 +22,7 @@ # If the output of test072_coinc_sorter_1.py does not exist, create it if not os.path.isfile(paths.output / "test72_output_1.root"): print("---------- Begin of test072_coinc_sorter_1.py ----------") - subprocess.call(["python", paths.current / "test072_coinc_sorter_1.py"]) + subprocess.call(["python", paths.current / dependency]) print("----------- End of test072_coinc_sorter_1.py -----------") # open root file @@ -60,3 +61,7 @@ is_ok = utility.check_diff_abs(int(nc), int(nc_ref), tolerance=nc_tol, txt="") utility.test_ok(is_ok) + + +if __name__ == "__main__": + main() From ac4d0cae00ec03152d315a84d4af34e52d4caac2 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Fri, 4 Oct 2024 12:29:49 +0200 Subject: [PATCH 020/183] added empty string in dasbhoard dict for consistency with opengate_tests_results --- opengate/bin/opengate_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 49863d6c5..faae4930e 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -131,7 +131,7 @@ def go( ) ## going to overwrite the status of multiprocessing with the sp result dashboard_dict[file] = status_sp - dashboard_dict_out = {k: [] for k in files_to_run_avail} + dashboard_dict_out = {k: [""] for k in files_to_run_avail} dashboard_dict_out.update(dashboard_dict) if fpath_dashboard_output: os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) From 5d9ad39e4c1a6d88d8a9a4923dc58ac1c7d20163 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Fri, 4 Oct 2024 16:36:01 +0200 Subject: [PATCH 021/183] added empty str to dashbord output and cleaned unused code --- opengate/bin/opengate_tests.py | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index faae4930e..10167aae4 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -58,7 +58,7 @@ def go( start_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs ): - run_failed_mpjobs_in_sp = False + path_tests_src = return_tests_path() # returns the path to the tests/src dir test_dir_path = path_tests_src.parent path_output_dashboard = test_dir_path / "output_dashboard" @@ -79,9 +79,7 @@ def go( else: with open(fpath_dashboard_output, "r") as fp: dashboard_dict_previously = json.load(fp) - files_to_run = [ - k for k, v in dashboard_dict_previously.items() if v and not v[0] - ] + files_to_run = [k for k, v in dashboard_dict_previously.items() if not v[0]] files_to_run_part1, files_to_run_part2_depending_on_part1 = ( filter_files_with_dependencies(files_to_run, path_tests_src) @@ -111,26 +109,6 @@ def go( runs_status_info, files_to_run_part1, no_log_on_fail ) - if run_failed_mpjobs_in_sp: - previously_failed_files = [k for k, v in dashboard_dict.items() if not v[0]] - if fail_status and processes_run == "mp": - print( - "Some files failed in multiprocessing, going to run the failed jobs in single processing mode:" - ) - runs_status_info_sp = run_test_cases( - previously_failed_files, no_log_on_fail, processes_run="sp" - ) - dashboard_dict_sp, fail_status_sp = status_summary_report( - runs_status_info_sp, previously_failed_files, no_log_on_fail - ) - for file, status_sp in dashboard_dict_sp.items(): - if status_sp[0] and not dashboard_dict[file][0]: - fname = Path(file).name - print( - f'{fname}: failed in "multiprocessing" but succeeded in "single processing".' - ) - ## going to overwrite the status of multiprocessing with the sp result - dashboard_dict[file] = status_sp dashboard_dict_out = {k: [""] for k in files_to_run_avail} dashboard_dict_out.update(dashboard_dict) if fpath_dashboard_output: From 180bef0bf6ae288a1b5956bea4e5a2ec34120d75 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 7 Oct 2024 09:35:05 +0200 Subject: [PATCH 022/183] Remove debug print "loading opengate" Commented out initialization status prints and removed redundant "done" message to streamline the module's import process. --- opengate/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/opengate/__init__.py b/opengate/__init__.py index 53e30a72c..df806a5e4 100644 --- a/opengate/__init__.py +++ b/opengate/__init__.py @@ -1,8 +1,8 @@ # This file handles the way opengate is imported. +""" import colored import threading - print( colored.stylize( f"Importing opengate (thread " f"{threading.get_native_id()}) ... ", @@ -11,6 +11,8 @@ end="", flush=True, ) +print(colored.stylize("done", colored.fore("dark_gray"))) +""" # the following modules are imported respecting the package structure # they will be available via @@ -52,6 +54,3 @@ from opengate.managers import Simulation from opengate.managers import create_sim_from_json from opengate.utility import g4_units - - -print(colored.stylize("done", colored.fore("dark_gray"))) From 864bb298dcef66b0785d46642678a500929192a8 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Mon, 7 Oct 2024 11:46:41 +0200 Subject: [PATCH 023/183] fixed github workflow? --- opengate/bin/opengate_tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 10167aae4..b068de1f8 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -98,14 +98,14 @@ def go( files_to_run_part2_depending_on_part1, no_log_on_fail, processes_run ) - dashboard_dict, fail_status_part2 = status_summary_report( + dashboard_dict, failure = status_summary_report( runs_status_info + runs_status_info_part2, files_to_run_part1 + files_to_run_part2_depending_on_part1, no_log_on_fail, ) else: - dashboard_dict, fail_status = status_summary_report( + dashboard_dict, failure = status_summary_report( runs_status_info, files_to_run_part1, no_log_on_fail ) @@ -115,6 +115,7 @@ def go( os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) with open(fpath_dashboard_output, "w") as fp: json.dump(dashboard_dict_out, fp, indent=4) + print(not failure) def get_files_to_run(): From 30880af5ef8651016a79db6025bb3ce7dd94922f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:24:45 +0000 Subject: [PATCH 024/183] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0) - [github.com/pre-commit/mirrors-clang-format: v19.1.0 → v19.1.1](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.0...v19.1.1) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f6ca9dcce..632a1552b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,14 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v19.1.0 + rev: v19.1.1 hooks: - id: clang-format ci: From 698d7b9680a0e8eb0dfad8d83a322b4295e92fe2 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:43:52 +0200 Subject: [PATCH 025/183] Improve ActorOutputUsingDataItemContainer.get_active() --- opengate/actors/actoroutput.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index 011716734..54bd66757 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -504,6 +504,8 @@ def set_active(self, value, item=0): self.data_item_config[i]["active"] = bool(value) def get_active(self, item=0): + if item == 'any': + item = 'all' items = self._collect_item_identifiers(item) return any([self.data_item_config[k]["active"] is True for k in items]) From e6e413d565a1cf92011c4fbf4d58149e628dbcfe Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:44:13 +0200 Subject: [PATCH 026/183] Add FIXME --- opengate/actors/actoroutput.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index 54bd66757..aa31e2291 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -537,6 +537,7 @@ def get_item_suffix(self, item=0, **kwargs): ) else: try: + # FIXME: the .get() method implicitly defines a default value, but it should not. Is this a workaround? return self.data_item_config[item].get("suffix", str(item)) except KeyError: self._fatal_unknown_item(item) From a3f87f940e2ffbd1f44a6fa587b5483a0484abe1 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:47:10 +0200 Subject: [PATCH 027/183] Rename method copy_user_info to configure_like --- opengate/base.py | 2 +- opengate/contrib/phantoms/nemaiec.py | 4 ++-- opengate/geometry/volumes.py | 2 +- .../tests/src/test033_rotation_spect_aa_helpers.py | 2 +- opengate/tests/src/test059_tpsource_gantry_rot.py | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index 8f8870bfc..7f2d0a3d8 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -586,7 +586,7 @@ def release_g4_references(self): """Dummy implementation for inherited classes which do not implement this method.""" pass - def copy_user_info(self, other_obj): + def configure_like(self, other_obj): for k in self.user_info.keys(): if k not in ["name", "_name"]: try: diff --git a/opengate/contrib/phantoms/nemaiec.py b/opengate/contrib/phantoms/nemaiec.py index 0831c11c6..6da0cb930 100644 --- a/opengate/contrib/phantoms/nemaiec.py +++ b/opengate/contrib/phantoms/nemaiec.py @@ -97,7 +97,7 @@ def add_iec_body(simulation, name, thickness=0.0, thickness_z=0.0): bottom_right_shell = opengate.geometry.volumes.TubsVolume( name=f"{name}_bottom_right_shell" ) - bottom_right_shell.copy_user_info(bottom_left_shell) + bottom_right_shell.configure_like(bottom_left_shell) bottom_right_shell.sphi = 180 * deg bottom_right_shell.dphi = 90 * deg @@ -244,7 +244,7 @@ def add_iec_one_sphere( # capillary outer shell caps = sim.add_volume("Tubs", f"{name}_capillary_shell_{d}") - caps.copy_user_info(cap) + caps.configure_like(cap) caps.material = iec_plastic caps.rmax = cap_thick caps.rmin = cap.rmax diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index ee356b0b8..752a86c16 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -192,7 +192,7 @@ def __init__(self, *args, template=None, **kwargs): # except for the name of course if template is not None: # FIXME: consider using from_dictionary() - self.copy_user_info(template) + self.configure_like(template) # put back user infos which were explicitly passed as keyword argument for k in self.user_info.keys(): if k != "name" and k in kwargs: diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index a291da278..582aa340a 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -86,7 +86,7 @@ def create_test(sim, nb_thread=1): # source #2 source2 = sim.add_source("GenericSource", "source2") - # FIXME when source will be refactored, will possible to use copy_user_info + # FIXME when source will be refactored, will be possible to use configure_like source2.particle = "gamma" source2.energy.type = "mono" source2.energy.mono = 140.5 * keV diff --git a/opengate/tests/src/test059_tpsource_gantry_rot.py b/opengate/tests/src/test059_tpsource_gantry_rot.py index aeb83e62d..feba93172 100755 --- a/opengate/tests/src/test059_tpsource_gantry_rot.py +++ b/opengate/tests/src/test059_tpsource_gantry_rot.py @@ -92,16 +92,16 @@ ## ---- HBL Nozzle --- # FIXME : will change after volumes are refactored box_rot = sim.add_volume("Box", "box_rot") - box_rot.copy_user_info(box) + box_rot.configure_like(box) box_rot.rotation = Rotation.from_euler("y", -90, degrees=True).as_matrix() box_rot.translation = [1148.0, 0.0, 1000.0] nozzle_rot = sim.add_volume("Box", "nozzle_rot") - nozzle_rot.copy_user_info(nozzle) + nozzle_rot.configure_like(nozzle) nozzle_rot.mother = box_rot.name rashi_rot = sim.add_volume("Box", "rashi_rot") - rashi_rot.copy_user_info(rashi) + rashi_rot.configure_like(rashi) rashi_rot.mother = box_rot.name # ----------------------------------- @@ -115,7 +115,7 @@ # target 2 HBL phantom_rot = sim.add_volume("Box", "phantom_rot") - phantom_rot.copy_user_info(phantom) + phantom_rot.configure_like(phantom) phantom_rot.rotation = Rotation.from_euler("z", 90, degrees=True).as_matrix() phantom_rot.translation = [0.0, 0.0, 1000.0] @@ -129,7 +129,7 @@ dose.user_output.dose.active = True dose_rot = sim.add_actor("DoseActor", "doseInXYZ_rot") - dose_rot.copy_user_info(dose) + dose_rot.configure_like(dose) dose_rot.attached_to = phantom_rot.name dose_rot.output_filename = "testTPSganry_rot.mhd" From ee6567f2afdd866c41ddba682f12540cd216d4aa Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:47:33 +0200 Subject: [PATCH 028/183] Implement ActorBase.configure_like() --- opengate/actors/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opengate/actors/base.py b/opengate/actors/base.py index ad579ca81..d32a157ee 100644 --- a/opengate/actors/base.py +++ b/opengate/actors/base.py @@ -157,6 +157,12 @@ def __setstate__(self, state): # # output_filename is a property # self.known_attributes.add("output_filename") + def configure_like(self, other_obj): + super().configure_like(other_obj) + # also pick up the configuration of the user output + for k, v in self.user_output.items(): + v.configure_like(other_obj.user_output[k]) + def to_dictionary(self): d = super().to_dictionary() d["user_output"] = dict( From d1696f70ecc8bd16fc85cc587f291cc0c51f92bd Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:48:21 +0200 Subject: [PATCH 029/183] In SingleItkImageWithVariance: set variance to 0 where not possible to calculate it (denominator 0) --- opengate/actors/dataitems.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/actors/dataitems.py b/opengate/actors/dataitems.py index 5cc4a9600..f5eed4629 100644 --- a/opengate/actors/dataitems.py +++ b/opengate/actors/dataitems.py @@ -692,7 +692,7 @@ def get_variance_or_uncertainty(self, which_quantity): output_arr = np.divide( output_arr, value_array / number_of_samples, - out=np.ones_like(output_arr), + out=np.zeros_like(output_arr), where=value_array != 0, ) output_image = itk.image_view_from_array(output_arr) From f7b02e2514ff47baf47e01992c898f3b074f485a Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:52:41 +0200 Subject: [PATCH 030/183] Update DoseActor (python) to use an actor output with dose and dose_squared combined. --- opengate/actors/doseactors.py | 258 ++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 122 deletions(-) diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index 85c9d6f80..0bd60fc06 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -442,6 +442,7 @@ class DoseActor(VoxelDepositActor, g4.GateDoseActor): def __init__(self, *args, **kwargs): VoxelDepositActor.__init__(self, *args, **kwargs) + # **** EDEP **** # This creates a user output with two components: 0=edep, 1=edep_squared # additionally, it also provides variance, std, and uncertainty via dynamic properties self._add_user_output( @@ -471,29 +472,54 @@ def __init__(self, *args, **kwargs): item="uncertainty", ) + # **** DOSE **** self._add_user_output( - ActorOutputSingleImage, - "dose", + ActorOutputSingleImageWithVariance, + "dose_with_uncertainty", + automatically_generate_interface=False, ) - - self._add_user_output( - ActorOutputSingleImage, + # create an interface to item 0 of user output "dose_with_uncertainty" + # and make it available via a property 'dose' in this actor + self._add_interface_to_user_output( + UserInterfaceToActorOutputImage, "dose_with_uncertainty", "dose", item=0 + ) + # create an interface to item 1 of user output "dose_with_uncertainty" + # and make it available via a property 'dose_squared' in this actor + self._add_interface_to_user_output( + UserInterfaceToActorOutputImage, + "dose_with_uncertainty", + "dose_squared", + item=1, + ) + self._add_interface_to_user_output( + UserInterfaceToActorOutputImage, + "dose_with_uncertainty", "dose_uncertainty", + item="uncertainty", ) + # set the defaults for the user output of this actor self._add_user_output(ActorOutputSingleMeanImage, "density") + self._add_user_output(ActorOutputSingleImage, "counts") + self.user_output.dose_with_uncertainty.set_active(False, item='all') + self.user_output.density.set_active(False) + self.user_output.counts.set_active(False) - self.user_output.dose.set_active(False) - self.user_output.dose_uncertainty.set_active(False) - + # item suffix is used when the filename is auto-generated or + # when the user sets one filename per actor self.user_output.edep_with_uncertainty.set_item_suffix("edep", item=0) self.user_output.edep_with_uncertainty.set_item_suffix("edep_squared", item=1) self.user_output.edep_with_uncertainty.set_item_suffix( "edep_uncertainty", item="uncertainty" ) - self.user_output.dose.set_item_suffix("dose") - self.user_output.dose_uncertainty.set_item_suffix("dose_uncertainty") + self.user_output.dose_with_uncertainty.set_item_suffix("dose", item=0) + self.user_output.dose_with_uncertainty.set_item_suffix("dose_squared", item=1) + self.user_output.dose_with_uncertainty.set_item_suffix( + "dose_uncertainty", item="uncertainty" + ) + # The following 2 are single item output and item=0 is default self.user_output.density.set_item_suffix("density") + self.user_output.counts.set_item_suffix("counts") self.__initcpp__() @@ -510,56 +536,24 @@ def __initcpp__(self): } ) - def compute_dose_from_edep_img(self, input_image, density_image=None): - """ - * create mass image: - - from ct HU units, if dose actor attached to ImageVolume. - - from material density, if standard volume - * compute dose as edep_image / mass_image - """ - vol = self.attached_to_volume - voxel_volume = self.spacing[0] * self.spacing[1] * self.spacing[2] - Gy = g4_units.Gy - gcm3 = g4_units.g_cm3 - - if vol.volume_type == "ImageVolume": - if self.score_in == "water": - # for dose to water, divide by density of water and not density of material - scaled_image = scale_itk_image(input_image, 1 / (1.0 * gcm3)) - else: - density_image = vol.create_density_image() - if images_have_same_domain(input_image, density_image) is False: - density_image = resample_itk_image_like( - density_image, input_image, 0, linear=True - ) - scaled_image = divide_itk_images( - img1_numerator=input_image, - img2_denominator=density_image, - filterVal=0, - replaceFilteredVal=0, - ) - # divide by voxel volume and convert unit - scaled_image = scale_itk_image(scaled_image, 1 / (Gy * voxel_volume)) + def create_density_image_from_image_volume(self, deposit_image): + if self.attached_to_volume.volume_type != "ImageVolume": + fatal(f"Cannot calculate the density map from the ImageVolume " + f"because this actor is attached to a {self.attached_to_volume.volume_type}. ") - else: - if self.score_in == "water": - # for dose to water, divide by density of water and not density of material - scaled_image = scale_itk_image(input_image, 1 / (1.0 * gcm3)) - else: - # the dose actor is attached to a volume, we need the density image - # to be computed from the cpp side - if density_image is None: - fatal(f"A density image computed via the G4 simulation is needed.") - scaled_image = divide_itk_images( - img1_numerator=input_image, - img2_denominator=density_image, - filterVal=0, - replaceFilteredVal=0, - ) - # divide by voxel volume and convert unit - scaled_image = scale_itk_image(scaled_image, 1 / (Gy * voxel_volume)) + density_image = self.attached_to_volume.create_density_image() + if images_have_same_domain(deposit_image, density_image) is False: + density_image = resample_itk_image_like( + density_image, deposit_image, 0, linear=True + ) + return density_image - return scaled_image + @property + def _density_via_mc(self): + return (self.calculate_density_from == 'simulation' or + (self.calculate_density_from == 'auto' + and self.attached_to_volume.volume_type != "ImageVolume") + ) def initialize(self, *args): """ @@ -572,35 +566,52 @@ def initialize(self, *args): VoxelDepositActor.initialize(self) - # dose uncertainty relies on edep_uncertainty. Set active flag accordingly - if self.user_output.dose_uncertainty.get_active() is True: - self.user_output.dose.set_active(True) - self.user_output.edep_with_uncertainty.set_active(True, item="uncertainty") # Make sure the squared component (item 1) is active if any of the quantities relying on it are active if ( - self.user_output.edep_with_uncertainty.get_active( - item=("uncertainty", "std", "variance") - ) - is True + self.user_output.edep_with_uncertainty.get_active( + item=("uncertainty", "std", "variance") + ) + is True ): - self.user_output.edep_with_uncertainty.set_active( - True, item=1 - ) # activate squared component + # activate the squared component, but avoid writing it to disk + # because the user has not activated it and thus most likely does not want it + if not self.user_output.edep_with_uncertainty.get_active(item=1): + self.user_output.edep_with_uncertainty.set_write_to_disk(False, item=1) + self.user_output.edep_with_uncertainty.set_active( + True, item=1 + ) # activate squared component - # activate density if we need the dose and the DoseActor is not attached to a volume + # Make sure the squared component (item 1) is active if any of the quantities relying on it are active if ( - self.user_output.dose.get_active() is True - and self.attached_to_volume.volume_type != "ImageVolume" + self.user_output.dose_with_uncertainty.get_active( + item=("uncertainty", "std", "variance") + ) + is True ): - if not self.user_output.density.get_active(): - self.user_output.density.set_active(True) - self.user_output.density.set_write_to_disk(False) + # activate the squared component, but avoid writing it to disk + # because the user has not activated it and thus most likely does not want it + if not self.user_output.dose_with_uncertainty.get_active(item=1): + self.user_output.dose_with_uncertainty.set_write_to_disk(False, item=1) + self.user_output.dose_with_uncertainty.set_active( + True, item=1 + ) # activate squared component + + if self.user_output.density.get_active() is True: + # scoring density via MC implies scoring counts + if self._density_via_mc: + if not self.user_output.counts.get_active(): + self.user_output.counts.set_active(True) + self.user_output.counts.set_write_to_disk(False) self.InitializeUserInput(self.user_info) # C++ side - self.SetSquareFlag(self.user_output.edep_with_uncertainty.get_active(item=1)) - self.SetDensityFlag( - self.user_output.density.get_active() - ) # item=0 is the default + # Set the flags on C++ side so the C++ knows which quantities need to be scored + self.SetEdepSquaredFlag(self.user_output.edep_with_uncertainty.get_active(item=1)) + self.SetDoseFlag(self.user_output.dose_with_uncertainty.get_active(item=0)) + self.SetDoseSquaredFlag(self.user_output.dose_with_uncertainty.get_active(item=1)) + # item=0 is the default + self.SetCountsFlag(self.user_output.counts.get_active()) + self.SetDensityFlag(self.user_output.density.get_active() and self._density_via_mc) + # C++ side has a boolean toWaterFlag and self.score_in == "water" yields True/False self.SetToWaterFlag(self.score_in == "water") # Set the physical volume name on the C++ side @@ -613,15 +624,22 @@ def BeginOfRunActionMasterThread(self, run_index): "edep_with_uncertainty", run_index, self.cpp_edep_image, - self.cpp_square_image, + self.cpp_edep_squared_image, ) - if self.user_output.density.get_active(): + if self.user_output.dose_with_uncertainty.get_active(item='any'): + self.prepare_output_for_run("dose_with_uncertainty", run_index) + self.push_to_cpp_image("dose_with_uncertainty", run_index, self.cpp_dose_image, self.cpp_dose_squared_image) + + # density might be active, but the user might want to get it from the ImageVolume + # therefore, we also check for _density_via_mc + if self.user_output.density.get_active() and self._density_via_mc: self.prepare_output_for_run("density", run_index) self.push_to_cpp_image("density", run_index, self.cpp_density_image) - if self.user_output.dose_uncertainty.get_active(): - self.prepare_output_for_run("dose_uncertainty", run_index) + if self.user_output.counts.get_active(): + self.prepare_output_for_run("counts", run_index) + self.push_to_cpp_image("counts", run_index, self.cpp_counts_image) g4.GateDoseActor.BeginOfRunActionMasterThread(self, run_index) @@ -630,56 +648,52 @@ def EndOfRunActionMasterThread(self, run_index): "edep_with_uncertainty", run_index, self.cpp_edep_image, - self.cpp_square_image, + self.cpp_edep_squared_image, ) self._update_output_coordinate_system("edep_with_uncertainty", run_index) self.user_output.edep_with_uncertainty.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) - # density image - if self.user_output.density.get_active(): - self.fetch_from_cpp_image("density", run_index, self.cpp_density_image) - self._update_output_coordinate_system("density", run_index) - self.user_output.density.store_meta_data( - run_index, number_of_samples=self.NbOfEvent - ) - - # dose - if self.user_output.dose.get_active(): - edep_image = self.user_output.edep_with_uncertainty.get_data( - run_index, item=0 - ) - density_image = None - if self.user_output.density.get_active(): - density_image = self.user_output.density.get_data(run_index) - dose_image = self.compute_dose_from_edep_img(edep_image, density_image) - dose_image.CopyInformation(edep_image) - self.store_output_data( - "dose", + if self.user_output.dose_with_uncertainty.get_active(item='any'): + self.fetch_from_cpp_image( + "dose_with_uncertainty", run_index, - dose_image, + self.cpp_dose_image, + self.cpp_dose_squared_image, ) - self.user_output.dose.store_meta_data( + self._update_output_coordinate_system("dose_with_uncertainty", run_index) + self.user_output.dose_with_uncertainty.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) - - if self.user_output.dose_uncertainty.get_active() is True: - # scale by density - edep_uncertainty_image = self.user_output.edep_with_uncertainty.get_data( - run_index, item="uncertainty" - ) - density_image = None - if self.user_output.density.get_active(): - density_image = self.user_output.density.get_data(run_index) - dose_uncertainty_image = self.compute_dose_from_edep_img( - edep_uncertainty_image, density_image - ) - dose_uncertainty_image.CopyInformation(edep_uncertainty_image) - self.user_output.dose_uncertainty.store_data( - run_index, dose_uncertainty_image + # divide by voxel volume and scale to unit Gy + if self.user_output.dose_with_uncertainty.get_active(item=0): + self.user_output.dose_with_uncertainty.data_per_run[run_index].data[0] /= \ + (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) + if self.user_output.dose_with_uncertainty.get_active(item=1): + # in the squared component 1, the denominator needs to be squared + self.user_output.dose_with_uncertainty.data_per_run[run_index].data[1] /= ( + (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) ** 2) + + if self.user_output.counts.get_active(): + self.fetch_from_cpp_image("counts", run_index, self.cpp_counts_image) + self._update_output_coordinate_system("counts", run_index) + self.user_output.counts.store_meta_data( + run_index, number_of_samples=self.NbOfEvent ) - self.user_output.dose_uncertainty.store_meta_data( + + # density image + if self.user_output.density.get_active(): + if self._density_via_mc: + self.fetch_from_cpp_image("density", run_index, self.cpp_density_image) + self._update_output_coordinate_system("density", run_index) + self.user_output.density.data_per_run[run_index] /= self.user_output.counts.data_per_run[run_index] + else: + edep_image = self.user_output.edep_with_uncertainty.get_data( + run_index, item=0 + ) + self.user_output.density.store_data(run_index, self.create_density_image_from_image_volume(edep_image)) + self.user_output.density.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) From 0986054a1c30d976addd5fad7a3361132e5e814d Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:53:02 +0200 Subject: [PATCH 031/183] Update GateDoseActor (C++) to use an actor output with dose and dose_squared combined. --- .../opengate_lib/GateDoseActor.cpp | 217 +++++++++--------- .../opengate_lib/GateDoseActor.h | 66 ++++-- .../opengate_lib/pyGateDoseActor.cpp | 15 +- 3 files changed, 165 insertions(+), 133 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index bce36098d..0b1258cad 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -32,16 +32,7 @@ G4Mutex ComputeUncertaintyMutex = G4MUTEX_INITIALIZER; G4Mutex SetNbEventMutex = G4MUTEX_INITIALIZER; GateDoseActor::GateDoseActor(py::dict &user_info) - : GateVActor(user_info, true) { - // Action for this actor: during stepping - // fActions.insert("SteppingAction"); - // fActions.insert("BeginOfRunAction"); - // fActions.insert("EndOfRunAction"); - // fActions.insert("BeginOfEventAction"); - // fActions.insert("EndOfSimulationWorkerAction"); - // fActions.insert("EndSimulationAction"); - // fActions.insert("EndOfEventAction"); -} + : GateVActor(user_info, true) {} void GateDoseActor::InitializeUserInput(py::dict &user_info) { // IMPORTANT: call the base class method @@ -83,12 +74,21 @@ void GateDoseActor::InitializeCpp() { // (the size and allocation will be performed on the py side) cpp_edep_image = Image3DType::New(); - if (fSquareFlag) { - cpp_square_image = Image3DType::New(); + if (fDoseFlag) { + cpp_dose_image = Image3DType::New(); + } + if (fEdepSquaredFlag) { + cpp_edep_squared_image = Image3DType::New(); + } + if (fDoseSquaredFlag) { + cpp_dose_squared_image = Image3DType::New(); } if (fDensityFlag) { cpp_density_image = Image3DType::New(); } + if (fCountsFlag) { + cpp_counts_image = Image3DType::New(); + } } void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { @@ -105,40 +105,50 @@ void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { Image3DType::RegionType region = cpp_edep_image->GetLargestPossibleRegion(); size_edep = region.GetSize(); - if (fSquareFlag) { - AttachImageToVolume(cpp_square_image, fPhysicalVolumeName, + if (fEdepSquaredFlag) { + AttachImageToVolume(cpp_edep_squared_image, fPhysicalVolumeName, + fTranslation); + } + if (fDoseFlag) { + AttachImageToVolume(cpp_dose_image, fPhysicalVolumeName, + fTranslation); + } + if (fDoseSquaredFlag) { + AttachImageToVolume(cpp_dose_squared_image, fPhysicalVolumeName, fTranslation); } if (fDensityFlag) { AttachImageToVolume(cpp_density_image, fPhysicalVolumeName, fTranslation); } + if (fCountsFlag) { + AttachImageToVolume(cpp_counts_image, fPhysicalVolumeName, + fTranslation); + } +} + +void GateDoseActor::PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels) { + data.squared_worker_flatimg.resize(numberOfVoxels); + std::fill(data.squared_worker_flatimg.begin(), + data.squared_worker_flatimg.end(), 0.0); + data.lastid_worker_flatimg.resize(numberOfVoxels); + std::fill(data.lastid_worker_flatimg.begin(), data.lastid_worker_flatimg.end(), + 0); } void GateDoseActor::BeginOfRunAction(const G4Run *run) { - if (fSquareFlag) { - int N_voxels = size_edep[0] * size_edep[1] * size_edep[2]; - auto &l = fThreadLocalData.Get(); - l.edepSquared_worker_flatimg.resize(N_voxels); - std::fill(l.edepSquared_worker_flatimg.begin(), - l.edepSquared_worker_flatimg.end(), 0.0); - l.lastid_worker_flatimg.resize(N_voxels); - std::fill(l.lastid_worker_flatimg.begin(), l.lastid_worker_flatimg.end(), - 0); + int N_voxels = size_edep[0] * size_edep[1] * size_edep[2]; + if (fEdepSquaredFlag) { + PrepareLocalDataForRun(fThreadLocalDataEdep.Get(), N_voxels); + } + if (fDoseSquaredFlag) { + PrepareLocalDataForRun(fThreadLocalDataDose.Get(), N_voxels); } - - // if (fcpImageForThreadsFlag && (run->GetRunID() < 1)) { - // l.edep_worker_flatimg.resize(N_voxels); - // std::fill(l.edep_worker_flatimg.begin(), l.edep_worker_flatimg.end(), - // 0.0); - // } } void GateDoseActor::BeginOfEventAction(const G4Event *event) { G4AutoLock mutex(&SetNbEventMutex); NbOfEvent++; - // threadLocalT &data = fThreadLocalData.Get(); - // data.NbOfEvent_worker++; } void GateDoseActor::SteppingAction(G4Step *step) { @@ -180,6 +190,8 @@ void GateDoseActor::SteppingAction(G4Step *step) { // get edep in MeV (take weight into account) auto w = step->GetTrack()->GetWeight(); auto edep = step->GetTotalEnergyDeposit() / CLHEP::MeV * w; + double density; + double dose; if (fToWaterFlag) { auto *current_material = step->GetPreStepPoint()->GetMaterial(); @@ -195,7 +207,7 @@ void GateDoseActor::SteppingAction(G4Step *step) { auto energy = (energy1 + energy2) / 2; if (p == G4Gamma::Gamma()) p = G4Electron::Electron(); - auto &emc = fThreadLocalData.Get().emcalc; + auto &emc = fThreadLocalDataEdep.Get().emcalc; dedx_currstep = emc.ComputeTotalDEDX(energy, p, current_material, dedx_cut); dedx_water = emc.ComputeTotalDEDX(energy, p, water, dedx_cut); @@ -208,59 +220,38 @@ void GateDoseActor::SteppingAction(G4Step *step) { ImageAddValue(cpp_edep_image, index, edep); - if (fDensityFlag) { - // FIXME : not very efficient: should be computed once for all + if (fDensityFlag || fDoseFlag || fDoseSquaredFlag) { auto *current_material = step->GetPreStepPoint()->GetMaterial(); auto density = current_material->GetDensity(); - cpp_density_image->SetPixel(index, density); + if (fDensityFlag) { + ImageAddValue(cpp_density_image, index, density); + } + if (fDoseFlag || fDoseSquaredFlag) { + dose = edep / density; + if (fDoseFlag) { + ImageAddValue(cpp_dose_image, index, dose); + } + } } - if (fSquareFlag) { - auto &locald = fThreadLocalData.Get(); - G4AutoLock mutex(&SetPixelMutex); - - int index_flat = sub2ind(index); + if (fCountsFlag) { + ImageAddValue(cpp_counts_image, index, 1); + } + if (fEdepSquaredFlag || fDoseSquaredFlag) { auto event_id = G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID(); - auto previous_id = locald.lastid_worker_flatimg[index_flat]; - locald.lastid_worker_flatimg[index_flat] = event_id; - if (event_id == previous_id) { - // Same event: sum the deposited energy associated with this event ID - // and square once a new event ID is found (case below) - locald.edepSquared_worker_flatimg[index_flat] += edep; - } else { - // Different event : square deposited energy from the last event ID - // and start accumulating deposited energy for this new event ID - auto e = locald.edepSquared_worker_flatimg[index_flat]; - ImageAddValue(cpp_square_image, index, e * e); - // new temp value - locald.edepSquared_worker_flatimg[index_flat] = edep; + if (fEdepSquaredFlag) { +// G4AutoLock mutex(&SetPixelMutex); + ScoreSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image, edep, event_id, index); + } + if (fDoseSquaredFlag) { +// G4AutoLock mutex(&SetPixelMutex); + ScoreSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image, dose, event_id, index); } } } // else: outside of the image } -// void GateDoseActor::EndSimulationAction() { -// double planned_NbOfEvent_per_worker = double(NbOfEvent / (NbOfThreads)); -// if (fSTEofMeanFlag) { -// itk::ImageRegionIterator edep_iterator3D( -// cpp_edep_image, cpp_edep_image->GetLargestPossibleRegion()); -// for (edep_iterator3D.GoToBegin(); !edep_iterator3D.IsAtEnd(); -// ++edep_iterator3D) { -// -// Image3DType::IndexType index_f = edep_iterator3D.GetIndex(); -// Image3DType::PixelType pixelValue3D_perEvent = -// cpp_square_image->GetPixel(index_f); -// -// Image3DType::PixelType pixelValue_cpp = -// pixelValue3D_perEvent * planned_NbOfEvent_per_worker; -// cpp_square_image->SetPixel(index_f, pixelValue_cpp); -// // std::cout << "PixelValue end: " << pixelValue_cpp << std::endl; -// } -// } -//} - -// void GateDoseActor::EndOfEventAction(const G4Event *event) {} double GateDoseActor::ComputeMeanUncertainty() { G4AutoLock mutex(&ComputeUncertaintyMutex); @@ -269,11 +260,6 @@ double GateDoseActor::ComputeMeanUncertainty() { double mean_unc = 0.0; int n_voxel_unc = 0; double n = 2.0; - // if (fcpImageForThreadsFlag) { - // n = NbOfThreads; - // } else { - // n = NbOfEvent; - // } n = NbOfEvent; if (n < 2.0) { @@ -289,7 +275,7 @@ double GateDoseActor::ComputeMeanUncertainty() { if (val > max_edep * threshEdepPerc) { val /= n; n_voxel_unc++; - double val_squared_mean = cpp_square_image->GetPixel(index_f) / n; + double val_squared_mean = cpp_edep_squared_image->GetPixel(index_f) / n; double unc_i = (1.0 / (n - 1.0)) * (val_squared_mean - pow(val, 2)); if (unc_i < 0) { @@ -336,38 +322,48 @@ void GateDoseActor::ind2sub(int index_flat, Image3DType::IndexType &index3D) { void GateDoseActor::EndOfRunAction(const G4Run *run) { - if (fSquareFlag) { - // We need to flush the energy deposit from the last event ID of this run - // to cpp_square_image because it has only been accumulated in the - // SteppingAction It would be flushed to cpp_square_image in the - // SteppingAction of the next event ID, but we are at the end of the run. - threadLocalT &data = fThreadLocalData.Get(); - - G4AutoLock mutex(&SetWorkerEndRunMutex); - - itk::ImageRegionIterator iterator3D( - cpp_square_image, cpp_square_image->GetLargestPossibleRegion()); - for (iterator3D.GoToBegin(); !iterator3D.IsAtEnd(); ++iterator3D) { - Image3DType::IndexType index_f = iterator3D.GetIndex(); - Image3DType::PixelType pixelValue3D = - data.edepSquared_worker_flatimg[sub2ind(index_f)]; - ImageAddValue(cpp_square_image, index_f, - pixelValue3D * pixelValue3D); - } + if (fEdepSquaredFlag) { + GateDoseActor::FlushSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image); + } + if (fDoseSquaredFlag) { + GateDoseActor::FlushSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image); + } +} + +void GateDoseActor::ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, Image3DType::IndexType index) { + G4AutoLock mutex(&SetPixelMutex); + int index_flat = sub2ind(index); + auto previous_id = data.lastid_worker_flatimg[index_flat]; + data.lastid_worker_flatimg[index_flat] = event_id; + if (event_id == previous_id) { + // Same event: sum the deposited value associated with this event ID + // and square once a new event ID is found (case below) + data.squared_worker_flatimg[index_flat] += value; + } else { + // Different event : square deposited quantity from the last event ID + // and start accumulating deposited quantity for this new event ID + auto v = data.squared_worker_flatimg[index_flat]; + ImageAddValue(cpp_image, index, v * v); + // new temp value + data.squared_worker_flatimg[index_flat] = value; + } +} + +void GateDoseActor::FlushSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image) { +// G4AutoLock mutex(&SetWorkerEndRunMutex); + + itk::ImageRegionIterator iterator3D( + cpp_image, cpp_image->GetLargestPossibleRegion()); + for (iterator3D.GoToBegin(); !iterator3D.IsAtEnd(); ++iterator3D) { + Image3DType::IndexType index_f = iterator3D.GetIndex(); + Image3DType::PixelType pixelValue3D = + data.squared_worker_flatimg[sub2ind(index_f)]; + ImageAddValue(cpp_image, index_f, + pixelValue3D * pixelValue3D); } } int GateDoseActor::EndOfRunActionMasterThread(int run_id) { - // if (goalUncertainty != 0.0) { - // double unc = ComputeMeanUncertainty(); - // if (unc <= goalUncertainty) { - // return 1; - // } else { - // return 0; - // } - // } else { - // return 0; - // } return 0; } @@ -390,10 +386,5 @@ double GateDoseActor::GetMaxValueOfImage(Image3DType::Pointer imageP) { } } } - - // while (!pq.empty()) { - // std::cout << pq.top() << " "; - // pq.pop(); - // } return max; } diff --git a/core/opengate_core/opengate_lib/GateDoseActor.h b/core/opengate_core/opengate_lib/GateDoseActor.h index 82f399291..2a2a9e670 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.h +++ b/core/opengate_core/opengate_lib/GateDoseActor.h @@ -49,14 +49,26 @@ class GateDoseActor : public GateVActor { inline void SetToWaterFlag(const bool b) { fToWaterFlag = b; } - inline bool GetSquareFlag() const { return fSquareFlag; } + inline bool GetEdepSquaredFlag() const { return fEdepSquaredFlag; } - inline void SetSquareFlag(const bool b) { fSquareFlag = b; } + inline void SetEdepSquaredFlag(const bool b) { fEdepSquaredFlag = b; } inline void SetDensityFlag(const bool b) { fDensityFlag = b; } inline bool GetDensityFlag() const { return fDensityFlag; } + inline void SetDoseFlag(const bool b) { fDoseFlag = b; } + + inline bool GetDoseFlag() const { return fDoseFlag; } + + inline void SetDoseSquaredFlag(const bool b) { fDoseSquaredFlag = b; } + + inline bool GetDoseSquaredFlag() const { return fDoseSquaredFlag; } + + inline void SetCountsFlag(const bool b) { fCountsFlag = b; } + + inline bool GetCountsFlag() const { return fCountsFlag; } + inline std::string GetPhysicalVolumeName() const { return fPhysicalVolumeName; } @@ -75,16 +87,37 @@ class GateDoseActor : public GateVActor { // The image is accessible on py side (shared by all threads) Image3DType::Pointer cpp_edep_image; - // Image3DType::Pointer cpp_dose_image; - Image3DType::Pointer cpp_square_image; - Image3DType::SizeType size_edep{}; + Image3DType::Pointer cpp_edep_squared_image; + Image3DType::Pointer cpp_dose_image; + Image3DType::Pointer cpp_dose_squared_image; Image3DType::Pointer cpp_density_image; + Image3DType::Pointer cpp_counts_image; + Image3DType::SizeType size_edep{}; + + struct threadLocalT { + G4EmCalculator emcalc; + std::vector linear_worker_flatimg; + std::vector squared_worker_flatimg; + std::vector lastid_worker_flatimg; + // int NbOfEvent_worker = 0; + // Image3DType::IndexType index3D; + // int index_flat; + }; +// using ThreadLocalType = struct threadLocalT; + + void ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, Image3DType::IndexType index); + + void FlushSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image); + + void PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels); + + // // Option: indicate if we must compute uncertainty // bool fUncertaintyFlag; // Option: indicate if we must compute square - bool fSquareFlag{}; + bool fEdepSquaredFlag{}; // // Option: indicate if we must compute dose in Gray also // bool fDoseFlag; @@ -94,9 +127,16 @@ class GateDoseActor : public GateVActor { // Option: indicate we must convert to dose to water bool fToWaterFlag{}; - // Option: indicate the density is needed + // Option: Is dose to be scored? + bool fDoseFlag{}; + bool fDoseSquaredFlag{}; + + // Option: Is density to be scored? bool fDensityFlag{}; + // Option: Are counts to be scored + bool fCountsFlag{}; + // // Option: calculate dose in stepping action. If False, calc only edep and // // divide by mass at the end of the simulation, on py side // bool fOnFlyCalcFlag; @@ -123,16 +163,8 @@ class GateDoseActor : public GateVActor { std::string fHitType; protected: - struct threadLocalT { - G4EmCalculator emcalc; - std::vector edep_worker_flatimg; - std::vector edepSquared_worker_flatimg; - std::vector lastid_worker_flatimg; - // int NbOfEvent_worker = 0; - // Image3DType::IndexType index3D; - // int index_flat; - }; - G4Cache fThreadLocalData; + G4Cache fThreadLocalDataEdep; + G4Cache fThreadLocalDataDose; }; #endif // GateDoseActor_h diff --git a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp index 179d83f21..5d01fd046 100644 --- a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp @@ -37,18 +37,27 @@ void init_GateDoseActor(py::module &m) { &GateDoseActor::BeginOfRunActionMasterThread) .def("EndOfRunActionMasterThread", &GateDoseActor::EndOfRunActionMasterThread) - .def("GetSquareFlag", &GateDoseActor::GetSquareFlag) - .def("SetSquareFlag", &GateDoseActor::SetSquareFlag) + .def("GetEdepSquaredFlag", &GateDoseActor::GetEdepSquaredFlag) + .def("SetEdepSquaredFlag", &GateDoseActor::SetEdepSquaredFlag) + .def("GetDoseFlag", &GateDoseActor::GetDoseFlag) + .def("SetDoseFlag", &GateDoseActor::SetDoseFlag) + .def("GetDoseSquaredFlag", &GateDoseActor::GetDoseSquaredFlag) + .def("SetDoseSquaredFlag", &GateDoseActor::SetDoseSquaredFlag) .def("GetToWaterFlag", &GateDoseActor::GetToWaterFlag) .def("SetToWaterFlag", &GateDoseActor::SetToWaterFlag) .def("GetDensityFlag", &GateDoseActor::GetDensityFlag) .def("SetDensityFlag", &GateDoseActor::SetDensityFlag) + .def("GetCountsFlag", &GateDoseActor::GetCountsFlag) + .def("SetCountsFlag", &GateDoseActor::SetCountsFlag) .def("GetPhysicalVolumeName", &GateDoseActor::GetPhysicalVolumeName) .def("SetPhysicalVolumeName", &GateDoseActor::SetPhysicalVolumeName) .def_readwrite("NbOfEvent", &GateDoseActor::NbOfEvent) .def_readwrite("cpp_edep_image", &GateDoseActor::cpp_edep_image) + .def_readwrite("cpp_edep_squared_image", &GateDoseActor::cpp_edep_squared_image) + .def_readwrite("cpp_dose_image", &GateDoseActor::cpp_dose_image) + .def_readwrite("cpp_dose_squared_image", &GateDoseActor::cpp_dose_squared_image) .def_readwrite("cpp_density_image", &GateDoseActor::cpp_density_image) - .def_readwrite("cpp_square_image", &GateDoseActor::cpp_square_image) + .def_readwrite("cpp_counts_image", &GateDoseActor::cpp_counts_image) .def_readwrite("fPhysicalVolumeName", &GateDoseActor::fPhysicalVolumeName); } From 18343efc5880ce814db41aac2ef9fe0ff56d0dcb Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:54:17 +0200 Subject: [PATCH 032/183] Update kwarg ignore_value from 1 to 0 in calls to tests.utility.assert_images() --- opengate/tests/src/test008_dose_actor.py | 2 +- .../tests/src/test010_generic_source_angular_distribution.py | 2 +- opengate/tests/src/test030_dose_motion.py | 2 +- opengate/tests/src/test030_dose_motion_dynamic_param.py | 2 +- opengate/tests/src/test030_dose_motion_dynamic_param_custom.py | 2 +- opengate/tests/src/test041_dose_actor.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opengate/tests/src/test008_dose_actor.py b/opengate/tests/src/test008_dose_actor.py index 873f99445..b4f550612 100755 --- a/opengate/tests/src/test008_dose_actor.py +++ b/opengate/tests/src/test008_dose_actor.py @@ -117,7 +117,7 @@ dose.edep_uncertainty.get_output_path(), stat, tolerance=30, - ignore_value=1, + ignore_value=0, sum_tolerance=1, ) and is_ok diff --git a/opengate/tests/src/test010_generic_source_angular_distribution.py b/opengate/tests/src/test010_generic_source_angular_distribution.py index fbe538c91..5167c3a33 100755 --- a/opengate/tests/src/test010_generic_source_angular_distribution.py +++ b/opengate/tests/src/test010_generic_source_angular_distribution.py @@ -113,7 +113,7 @@ dose.edep_uncertainty.get_output_path(), stat, tolerance=30, - ignore_value=1, + ignore_value=0, sum_tolerance=1, ) and is_ok diff --git a/opengate/tests/src/test030_dose_motion.py b/opengate/tests/src/test030_dose_motion.py index 1e2f0eda2..c3d24dc2f 100755 --- a/opengate/tests/src/test030_dose_motion.py +++ b/opengate/tests/src/test030_dose_motion.py @@ -129,7 +129,7 @@ dose.edep_uncertainty.get_output_path(), stats, tolerance=15, - ignore_value=1, + ignore_value=0, ) ) and is_ok diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param.py b/opengate/tests/src/test030_dose_motion_dynamic_param.py index c4a398a9d..1fc7584d9 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param.py @@ -125,7 +125,7 @@ dose.edep_uncertainty.get_output_path(), stats, tolerance=15, - ignore_value=1, + ignore_value=0, ) and is_ok ) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py index b4602ad1f..8c6034804 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py @@ -202,7 +202,7 @@ def apply_change(self, run_id): dose.edep_uncertainty.get_output_path(), stats, tolerance=15, - ignore_value=1, + ignore_value=0, ) and is_ok ) diff --git a/opengate/tests/src/test041_dose_actor.py b/opengate/tests/src/test041_dose_actor.py index 96342a019..d3b374c08 100755 --- a/opengate/tests/src/test041_dose_actor.py +++ b/opengate/tests/src/test041_dose_actor.py @@ -119,7 +119,7 @@ dose_actor.edep_uncertainty.get_output_path(), stats, tolerance=30, - ignore_value=1, + ignore_value=0, ) and is_ok ) From 14648bd19ea7830af864500d34991ce4b8e11929 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:26:18 +0200 Subject: [PATCH 033/183] In SingleItkImageWithVariance.get_variance_or_uncertainty(): set variance to zero if not squared data is available --- opengate/actors/dataitems.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opengate/actors/dataitems.py b/opengate/actors/dataitems.py index f5eed4629..119b79698 100644 --- a/opengate/actors/dataitems.py +++ b/opengate/actors/dataitems.py @@ -675,9 +675,9 @@ def get_variance_or_uncertainty(self, which_quantity): elif self.data[1] is None or self.data[1].data is None: warning( "This data item does not contain squared values so no variance can be calculated. " - "The variance will be set to 1 everywhere. " + "The variance will be set to 0 everywhere. " ) - output_arr = np.ones_like(value_array) + output_arr = np.zeros_like(value_array) else: squared_value_array = np.asarray(self.data[1].data) output_arr = calculate_variance( From d40f74baec56bcfabd9919d7d9d0145f908ffb51 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:27:23 +0200 Subject: [PATCH 034/183] Improve ignora_value logic in assert_images() --- opengate/tests/utility.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index dae7ed5cf..7ad1d280d 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -305,7 +305,8 @@ def assert_images( filename2, stats=None, tolerance=0, - ignore_value=0, + ignore_value_data1=None, + ignore_value_data2=None, axis="z", fig_name=None, sum_tolerance=5, @@ -329,8 +330,23 @@ def assert_images( if scaleImageValuesFactor: data2 *= scaleImageValuesFactor - s1 = np.sum(data1) - s2 = np.sum(data2) + # do not consider pixels with a certain value + if ignore_value_data1 is None and ignore_value_data2 is None: + d1 = data1 + d2 = data2 + else: + if ignore_value_data1 is not None and ignore_value_data2 is not None: + mask = np.logical_or(data1 != ignore_value_data1, data2 != ignore_value_data2) + elif ignore_value_data1 is not None: + mask = data1 != ignore_value_data1 + else: + mask = data2 != ignore_value_data2 + d1 = data1[mask] + d2 = data2[mask] + + s1 = np.sum(d1) + s2 = np.sum(d2) + if s1 == 0 and s2 == 0: t = 0 else: @@ -342,10 +358,6 @@ def assert_images( print(f"Image1: {info1.size} {info1.spacing} {info1.origin} {ref_filename1}") print(f"Image2: {info2.size} {info2.spacing} {info2.origin} {filename2}") - # do not consider pixels with a value of zero (data2 is the reference) - d1 = data1[data2 != ignore_value] - d2 = data2[data2 != ignore_value] - # normalise by event if stats is not None: d1 = d1 / stats.counts.events From 5de9c6208622778afc58c68adb387be02d10b38f Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:28:01 +0200 Subject: [PATCH 035/183] Update tests and tolerances in test008_dose_actor.py --- opengate/tests/src/test008_dose_actor.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/opengate/tests/src/test008_dose_actor.py b/opengate/tests/src/test008_dose_actor.py index b4f550612..a6e6dfe02 100755 --- a/opengate/tests/src/test008_dose_actor.py +++ b/opengate/tests/src/test008_dose_actor.py @@ -99,28 +99,16 @@ print("\nDifference for EDEP") is_ok = ( - utility.assert_images( - ref_path / "output-Edep.mhd", - dose.edep.get_output_path(), - stat, - tolerance=13, - ignore_value=0, - sum_tolerance=1, - ) - and is_ok + utility.assert_images(ref_path / "output-Edep.mhd", dose.edep.get_output_path(), stat, tolerance=13, + ignore_value_data2=0, sum_tolerance=1.5) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - ref_path / "output-Edep-Uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), - stat, - tolerance=30, - ignore_value=0, - sum_tolerance=1, - ) - and is_ok + utility.assert_images(ref_path / "output-Edep-Uncertainty.mhd", dose.edep_uncertainty.get_output_path(), + stat, tolerance=30, ignore_value_data2=0, sum_tolerance=3) + and is_ok ) utility.test_ok(is_ok) From 8819ee183bfe83c520f15661a2b4e169e3c2d006 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:31:04 +0200 Subject: [PATCH 036/183] Update calls to assert_images: signature has changed --- ...010_generic_source_angular_distribution.py | 28 +++++--------- .../src/test010_generic_source_confine.py | 8 +--- opengate/tests/src/test012_mt_dose_actor.py | 10 ++--- opengate/tests/src/test015_iec_phantom_2.py | 11 +----- .../tests/src/test015_iec_phantom_3_mt.py | 6 +-- .../tests/src/test015_iec_phantom_5_wip.py | 11 +----- opengate/tests/src/test017_repeater.py | 11 ++---- opengate/tests/src/test020_profiling.py | 8 +--- opengate/tests/src/test021_voxel_source2.py | 12 ++---- opengate/tests/src/test023_filters.py | 18 ++------- .../tests/src/test023_filters_iec_phantom.py | 9 +---- .../tests/src/test023_filters_material.py | 9 +---- .../src/test028_ge_nm670_spect_2_helpers.py | 30 ++++----------- ...t028_ge_nm670_spect_4_acc_angle_helpers.py | 15 ++------ .../src/test029_volume_time_rotation_1.py | 13 ++----- .../test029_volume_time_rotation_1_process.py | 13 ++----- .../src/test029_volume_time_rotation_2.py | 13 ++----- opengate/tests/src/test030_dose_motion.py | 21 +++------- .../src/test030_dose_motion_dynamic_param.py | 22 +++-------- ...est030_dose_motion_dynamic_param_custom.py | 22 +++-------- .../src/test032_create_voxelized_volume.py | 9 +---- opengate/tests/src/test032_voxel_vs_volume.py | 9 +---- .../src/test032_voxelized_volume_source.py | 24 ++++-------- .../src/test033_rotation_spect_aa_helpers.py | 24 +++--------- opengate/tests/src/test034_gan_phsp_linac.py | 11 ++---- opengate/tests/src/test035_dose_rate.py | 11 ++---- opengate/tests/src/test036_proj_param_1.py | 13 ++----- .../src/test036_proj_param_2_with_index.py | 13 ++----- .../src/test038_gan_phsp_spect_gan_aa.py | 13 ++----- opengate/tests/src/test041_dose_actor.py | 34 +++++------------ .../test041_dose_actor_dose_to_water_mt.py | 10 +---- ...st041_dose_actor_mt_cp_images_n_threads.py | 10 +---- ...ose_actor_mt_standard_error_of_mean_WIP.py | 10 +---- opengate/tests/src/test042_gauss_gps.py | 38 ++++++------------- opengate/tests/src/test043_garf.py | 25 +++--------- opengate/tests/src/test043_garf_analog.py | 24 +++--------- opengate/tests/src/test043_garf_mt.py | 25 +++--------- opengate/tests/src/test044_pbs.py | 10 +---- opengate/tests/src/test044_pbs_rot_transl.py | 12 ++---- .../tests/src/test044_pbs_source_to_volume.py | 10 +---- opengate/tests/src/test044_pbs_unfocused.py | 11 ++---- .../tests/src/test047_gan_vox_source_cond.py | 12 ++---- .../src/test048_split_spect_projections.py | 12 ++---- .../tests/src/test050_let_actor_letd_mt.py | 12 ++---- .../test059_tpsource_flat_generation_flag.py | 25 ++++-------- .../tests/src/test059_tpsource_weights.py | 20 ++-------- .../test065_dose2water_from_edep2water_ct.py | 6 +-- .../tests/src/test065_dose_from_edep_ct.py | 6 +-- .../src/test065_dose_from_edep_volume.py | 6 +-- .../src/test067_cbct_fluence_actor_mt.py | 8 +--- opengate/tests/src/test073_helpers.py | 12 +----- 51 files changed, 193 insertions(+), 562 deletions(-) diff --git a/opengate/tests/src/test010_generic_source_angular_distribution.py b/opengate/tests/src/test010_generic_source_angular_distribution.py index 5167c3a33..5a6e16e5c 100755 --- a/opengate/tests/src/test010_generic_source_angular_distribution.py +++ b/opengate/tests/src/test010_generic_source_angular_distribution.py @@ -94,29 +94,19 @@ print("\nDifference for EDEP") is_ok = ( - utility.assert_images( - ref_path / "test010-generic_source_angular_distribution_edep_ref.mhd", - dose.edep.get_output_path(), - stat, - tolerance=13, - ignore_value=0, - sum_tolerance=1, - ) - and is_ok + utility.assert_images(ref_path / "test010-generic_source_angular_distribution_edep_ref.mhd", + dose.edep.get_output_path(), stat, tolerance=13, ignore_value_data2=0, + sum_tolerance=1) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - ref_path - / "test010-generic_source_angular_distribution_edep_uncertainty_ref.mhd", - dose.edep_uncertainty.get_output_path(), - stat, - tolerance=30, - ignore_value=0, - sum_tolerance=1, - ) - and is_ok + utility.assert_images(ref_path + / "test010-generic_source_angular_distribution_edep_uncertainty_ref.mhd", + dose.edep_uncertainty.get_output_path(), stat, tolerance=30, ignore_value_data2=0, + sum_tolerance=1) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source_confine.py b/opengate/tests/src/test010_generic_source_confine.py index 489dbc480..641974d91 100755 --- a/opengate/tests/src/test010_generic_source_confine.py +++ b/opengate/tests/src/test010_generic_source_confine.py @@ -122,11 +122,7 @@ # tests stats_ref = utility.read_stat_file(paths.output_ref / "test010_confine_stats.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.10) - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test010-2-edep.mhd", - dose_actor.edep.get_output_path(), - stats, - tolerance=59, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test010-2-edep.mhd", dose_actor.edep.get_output_path(), + stats, tolerance=59) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test012_mt_dose_actor.py b/opengate/tests/src/test012_mt_dose_actor.py index c904ab688..78a28ac8b 100755 --- a/opengate/tests/src/test012_mt_dose_actor.py +++ b/opengate/tests/src/test012_mt_dose_actor.py @@ -103,12 +103,8 @@ is_ok = utility.assert_stats(stat, stats_ref, 0.10) is_ok = ( - utility.assert_images( - paths.gate_output / "output-Edep.mhd", - dose_actor.edep.get_output_path(), - stat, - tolerance=45, - ) - and is_ok + utility.assert_images(paths.gate_output / "output-Edep.mhd", dose_actor.edep.get_output_path(), stat, + tolerance=45) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_2.py b/opengate/tests/src/test015_iec_phantom_2.py index 5f93ca810..8b1639d56 100755 --- a/opengate/tests/src/test015_iec_phantom_2.py +++ b/opengate/tests/src/test015_iec_phantom_2.py @@ -72,15 +72,8 @@ is_ok = utility.assert_stats(stats_actor, stats_ref, tolerance=0.03) # compare images - im_ok = utility.assert_images( - paths.output_ref / "test015_iec_2.mhd", - dose.edep.get_output_path(), - stats_actor, - axis="x", - tolerance=40, - ignore_value=0, - sum_tolerance=2, - ) + im_ok = utility.assert_images(paths.output_ref / "test015_iec_2.mhd", dose.edep.get_output_path(), stats_actor, + tolerance=40, ignore_value_data2=0, axis="x", sum_tolerance=2) is_ok = is_ok and im_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_3_mt.py b/opengate/tests/src/test015_iec_phantom_3_mt.py index 5f8109b0b..ad66251d7 100755 --- a/opengate/tests/src/test015_iec_phantom_3_mt.py +++ b/opengate/tests/src/test015_iec_phantom_3_mt.py @@ -78,11 +78,11 @@ paths.output_ref / "test015_iec_3.mhd", dose.edep.get_output_path(), stats, - axis="y", tolerance=80, - ignore_value=0, + ignore_value_data2=0, + axis="y", sum_tolerance=0.5, - sad_profile_tolerance=2, + sad_profile_tolerance=2 ) is_ok = is_ok and im_ok diff --git a/opengate/tests/src/test015_iec_phantom_5_wip.py b/opengate/tests/src/test015_iec_phantom_5_wip.py index af0853ede..55cdb3b02 100755 --- a/opengate/tests/src/test015_iec_phantom_5_wip.py +++ b/opengate/tests/src/test015_iec_phantom_5_wip.py @@ -79,15 +79,8 @@ # compare images f = paths.output / "test015_iec_2.mhd" - im_ok = utility.assert_images( - paths.output_ref / "test015_iec_2.mhd", - dose.output, - stats, - axis="x", - tolerance=40, - ignore_value=0, - sum_tolerance=2, - ) + im_ok = utility.assert_images(paths.output_ref / "test015_iec_2.mhd", dose.output, stats, tolerance=40, + ignore_value_data2=0, axis="x", sum_tolerance=2) is_ok = is_ok and im_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test017_repeater.py b/opengate/tests/src/test017_repeater.py index fd0afb173..59a8ec250 100755 --- a/opengate/tests/src/test017_repeater.py +++ b/opengate/tests/src/test017_repeater.py @@ -101,14 +101,9 @@ stats_ref = utility.read_stat_file(paths.output_ref / "test017-stats-ref.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.04) is_ok = ( - utility.assert_images( - paths.output_ref / "test017-edep-ref.mhd", - dose.edep.get_output_path(), - stats, - sum_tolerance=6, - tolerance=70, - ) - and is_ok + utility.assert_images(paths.output_ref / "test017-edep-ref.mhd", dose.edep.get_output_path(), stats, + tolerance=70, sum_tolerance=6) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test020_profiling.py b/opengate/tests/src/test020_profiling.py index 8574b60fa..af4b5e544 100755 --- a/opengate/tests/src/test020_profiling.py +++ b/opengate/tests/src/test020_profiling.py @@ -100,10 +100,6 @@ stats_ref = utility.read_stat_file(paths.gate / "output" / "stat_profiling.txt") stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, 0.1) - is_ok = is_ok and utility.assert_images( - paths.gate / "output" / "output_profiling-Edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=79, - ) + is_ok = is_ok and utility.assert_images(paths.gate / "output" / "output_profiling-Edep.mhd", + dose.edep.get_output_path(), stats, tolerance=79) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test021_voxel_source2.py b/opengate/tests/src/test021_voxel_source2.py index 00d94c167..f6bb050a9 100755 --- a/opengate/tests/src/test021_voxel_source2.py +++ b/opengate/tests/src/test021_voxel_source2.py @@ -147,15 +147,9 @@ is_ok = utility.assert_stats(stats, stats_ref, 0.1) and is_ok is_ok = ( - utility.assert_images( - paths.output_ref / "test021-edep_2.mhd", - dose.edep.get_output_path(), - stats, - tolerance=11, - ignore_value=0, - axis="y", - ) - and is_ok + utility.assert_images(paths.output_ref / "test021-edep_2.mhd", dose.edep.get_output_path(), stats, + tolerance=11, ignore_value_data2=0, axis="y") + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters.py b/opengate/tests/src/test023_filters.py index 62b8e8cd8..fd4e3cb84 100755 --- a/opengate/tests/src/test023_filters.py +++ b/opengate/tests/src/test023_filters.py @@ -101,21 +101,11 @@ is_ok = utility.assert_stats(stat, stats_ref, 0.8) print() - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test023-edep.mhd", - dose1.edep.get_output_path(), - stat, - tolerance=50, - sum_tolerance=4, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-edep.mhd", dose1.edep.get_output_path(), stat, + tolerance=50, sum_tolerance=4) print() - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test023-noe-edep.mhd", - dose2.edep.get_output_path(), - stat, - tolerance=40, - sum_tolerance=2, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-noe-edep.mhd", dose2.edep.get_output_path(), + stat, tolerance=40, sum_tolerance=2) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_iec_phantom.py b/opengate/tests/src/test023_filters_iec_phantom.py index 3e8044083..9a6dc20fc 100755 --- a/opengate/tests/src/test023_filters_iec_phantom.py +++ b/opengate/tests/src/test023_filters_iec_phantom.py @@ -81,12 +81,7 @@ f = paths.output_ref / "test023_stats_iec_phantom.txt" stats_ref = utility.read_stat_file(f) is_ok = utility.assert_stats(stat, stats_ref, 0.12) - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test023_iec_phantom.mhd", - dose.edep.get_output_path(), - stat, - sum_tolerance=28, - tolerance=102, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test023_iec_phantom.mhd", dose.edep.get_output_path(), + stat, tolerance=102, sum_tolerance=28) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_material.py b/opengate/tests/src/test023_filters_material.py index 7af904ed3..4863d4316 100755 --- a/opengate/tests/src/test023_filters_material.py +++ b/opengate/tests/src/test023_filters_material.py @@ -99,12 +99,7 @@ stats_ref = utility.read_stat_file(f2) is_ok = utility.assert_stats(stat2, stats_ref, 0.07) and is_ok - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test023-edep.mhd", - dose.edep.get_output_path(), - stat, - sum_tolerance=3, - tolerance=50, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-edep.mhd", dose.edep.get_output_path(), stat, + tolerance=50, sum_tolerance=3) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py index cfdde0d37..d0cd5b918 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py @@ -350,17 +350,10 @@ def test_spect_proj(sim, paths, proj, version="3"): img.SetOrigin(origin) itk.imwrite(img, str(paths.output / "proj028_offset.mhd")) is_ok = ( - utility.assert_images( - paths.gate_output / f"projection{version}.mhd", - paths.output / "proj028_offset.mhd", - stats, - tolerance=16, - ignore_value=0, - axis="y", - sum_tolerance=1.6, - fig_name=paths.output / f"proj028_{version}_offset.png", - ) - and is_ok + utility.assert_images(paths.gate_output / f"projection{version}.mhd", paths.output / "proj028_offset.mhd", + stats, tolerance=16, ignore_value_data2=0, axis="y", + fig_name=paths.output / f"proj028_{version}_offset.png", sum_tolerance=1.6) + and is_ok ) # compare images with Gate @@ -370,17 +363,10 @@ def test_spect_proj(sim, paths, proj, version="3"): print("Compare images (new spacing/origin") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images( - paths.output_ref / "proj028_ref.mhd", - paths.output / "proj028.mhd", - stats, - tolerance=14, - ignore_value=0, - axis="y", - sum_tolerance=1.5, - fig_name=paths.output / f"proj028_{version}_no_offset.png", - ) - and is_ok + utility.assert_images(paths.output_ref / "proj028_ref.mhd", paths.output / "proj028.mhd", stats, + tolerance=14, ignore_value_data2=0, axis="y", + fig_name=paths.output / f"proj028_{version}_no_offset.png", sum_tolerance=1.5) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py index 1c338c7c0..5f618f829 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py @@ -260,17 +260,10 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8): itk.imwrite(img, str(paths.output / "proj028_colli_offset.mhd")) # There are not enough event to make a proper comparison, so the tol is very high is_ok = ( - utility.assert_images( - paths.gate_output / "projection4.mhd", - paths.output / "proj028_colli_offset.mhd", - stats, - tolerance=85, - ignore_value=0, - axis="x", - sum_tolerance=sum_tolerance, - fig_name=str(paths.output / fig_name), - ) - and is_ok + utility.assert_images(paths.gate_output / "projection4.mhd", paths.output / "proj028_colli_offset.mhd", + stats, tolerance=85, ignore_value_data2=0, axis="x", + fig_name=str(paths.output / fig_name), sum_tolerance=sum_tolerance) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test029_volume_time_rotation_1.py b/opengate/tests/src/test029_volume_time_rotation_1.py index 31a341b15..b995b829e 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1.py +++ b/opengate/tests/src/test029_volume_time_rotation_1.py @@ -47,16 +47,9 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images( - paths.output_ref / "proj029.mhd", - proj_actor.get_output_path(), # get the path by asking the actor; better than hard-code the path - stats, - tolerance=59, - ignore_value=0, - axis="x", - sum_tolerance=2, - ) - and is_ok + utility.assert_images(paths.output_ref / "proj029.mhd", proj_actor.get_output_path(), stats, tolerance=59, + ignore_value_data2=0, axis="x", sum_tolerance=2) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_1_process.py b/opengate/tests/src/test029_volume_time_rotation_1_process.py index 13e3edd66..da3fdb50e 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1_process.py +++ b/opengate/tests/src/test029_volume_time_rotation_1_process.py @@ -37,16 +37,9 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images( - paths.output_ref / "proj029.mhd", - proj_actor.get_output_path(), # get the path by asking the actor; better than hard-code the path - stats, - tolerance=59, - ignore_value=0, - axis="x", - sum_tolerance=2, - ) - and is_ok + utility.assert_images(paths.output_ref / "proj029.mhd", proj_actor.get_output_path(), stats, tolerance=59, + ignore_value_data2=0, axis="x", sum_tolerance=2) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_2.py b/opengate/tests/src/test029_volume_time_rotation_2.py index d3a9b4ac0..18ec12457 100755 --- a/opengate/tests/src/test029_volume_time_rotation_2.py +++ b/opengate/tests/src/test029_volume_time_rotation_2.py @@ -47,16 +47,9 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images( - paths.output_ref / "proj029_scaled.mhd", - proj_actor.get_output_path(), # get the path by asking the actor; better than hard-code the path - stats, - tolerance=60, - ignore_value=0, - axis="x", - sum_tolerance=6, - ) - and is_ok + utility.assert_images(paths.output_ref / "proj029_scaled.mhd", proj_actor.get_output_path(), stats, + tolerance=60, ignore_value_data2=0, axis="x", sum_tolerance=6) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test030_dose_motion.py b/opengate/tests/src/test030_dose_motion.py index c3d24dc2f..71f47a51e 100755 --- a/opengate/tests/src/test030_dose_motion.py +++ b/opengate/tests/src/test030_dose_motion.py @@ -112,25 +112,16 @@ gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=30, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, + tolerance=30, ignore_value_data2=0) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), - stats, - tolerance=15, - ignore_value=0, - ) + utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), stats, tolerance=15, + ignore_value_data2=0) ) and is_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param.py b/opengate/tests/src/test030_dose_motion_dynamic_param.py index 1fc7584d9..5dfb1728d 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param.py @@ -108,26 +108,16 @@ print() gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=30, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, + tolerance=30, ignore_value_data2=0) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), - stats, - tolerance=15, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), stats, tolerance=15, ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py index 8c6034804..4f579b241 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py @@ -185,26 +185,16 @@ def apply_change(self, run_id): print() gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=30, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, + tolerance=30, ignore_value_data2=0) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), - stats, - tolerance=15, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), stats, tolerance=15, ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_create_voxelized_volume.py b/opengate/tests/src/test032_create_voxelized_volume.py index 47b74ec35..6dced7fad 100755 --- a/opengate/tests/src/test032_create_voxelized_volume.py +++ b/opengate/tests/src/test032_create_voxelized_volume.py @@ -64,13 +64,8 @@ # compare images gate.exception.warning("\nDifference with ref image") is_ok = ( - utility.assert_images( - paths.output_ref / "test032_iec.mhd", - path_to_image_1mm, - stats=None, - tolerance=0.01, - ) - and is_ok + utility.assert_images(paths.output_ref / "test032_iec.mhd", path_to_image_1mm, stats=None, tolerance=0.01) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_voxel_vs_volume.py b/opengate/tests/src/test032_voxel_vs_volume.py index 7e1f55af1..facd4fb2b 100755 --- a/opengate/tests/src/test032_voxel_vs_volume.py +++ b/opengate/tests/src/test032_voxel_vs_volume.py @@ -141,12 +141,7 @@ dose2 = sim.get_actor("dose2") # compare edep map - is_ok = utility.assert_images( - dose1.edep.get_output_path(), - dose2.edep.get_output_path(), - stats, - tolerance=87, - axis="x", - ) + is_ok = utility.assert_images(dose1.edep.get_output_path(), dose2.edep.get_output_path(), stats, tolerance=87, + axis="x") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_voxelized_volume_source.py b/opengate/tests/src/test032_voxelized_volume_source.py index 292b6c8d3..4f7c0518f 100755 --- a/opengate/tests/src/test032_voxelized_volume_source.py +++ b/opengate/tests/src/test032_voxelized_volume_source.py @@ -49,27 +49,19 @@ # compare images gate.exception.warning("\nDifference with ref image") is_ok = ( - utility.assert_images( - paths.output_ref / "iec_10mm.mhd", f1, stats=None, tolerance=0.001 - ) - and is_ok + utility.assert_images(paths.output_ref / "iec_10mm.mhd", f1, stats=None, tolerance=0.001) + and is_ok ) is_ok = ( - utility.assert_images( - paths.output_ref / "iec_source_10mm.mhd", f2, stats=None, tolerance=0.001 - ) - and is_ok + utility.assert_images(paths.output_ref / "iec_source_10mm.mhd", f2, stats=None, tolerance=0.001) + and is_ok ) is_ok = ( - utility.assert_images( - paths.output_ref / "iec_9mm.mhd", f3, stats=None, tolerance=0.001 - ) - and is_ok + utility.assert_images(paths.output_ref / "iec_9mm.mhd", f3, stats=None, tolerance=0.001) + and is_ok ) is_ok = ( - utility.assert_images( - paths.output_ref / "iec_source_9mm.mhd", f4, stats=None, tolerance=0.001 - ) - and is_ok + utility.assert_images(paths.output_ref / "iec_source_9mm.mhd", f4, stats=None, tolerance=0.001) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index 582aa340a..29a644a17 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -180,26 +180,14 @@ def evaluate_test(sim, sources, itol, ref_skipped): # compare edep map gate.exception.warning(f"Check images") is_ok = ( - utility.assert_images( - paths.output_ref / "test033_proj_1.mhd", - paths.output / "test033_proj_1.mhd", - stats, - tolerance=68, - axis="x", - sum_tolerance=itol, - ) - and is_ok + utility.assert_images(paths.output_ref / "test033_proj_1.mhd", paths.output / "test033_proj_1.mhd", stats, + tolerance=68, axis="x", sum_tolerance=itol) + and is_ok ) is_ok = ( - utility.assert_images( - paths.output_ref / "test033_proj_2.mhd", - paths.output / "test033_proj_2.mhd", - stats, - tolerance=68, - axis="x", - sum_tolerance=itol, - ) - and is_ok + utility.assert_images(paths.output_ref / "test033_proj_2.mhd", paths.output / "test033_proj_2.mhd", stats, + tolerance=68, axis="x", sum_tolerance=itol) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test034_gan_phsp_linac.py b/opengate/tests/src/test034_gan_phsp_linac.py index b63b3bf17..c17240b69 100755 --- a/opengate/tests/src/test034_gan_phsp_linac.py +++ b/opengate/tests/src/test034_gan_phsp_linac.py @@ -119,14 +119,9 @@ gate.exception.warning(f"Check dose") is_ok = ( - utility.assert_images( - paths.gate / "dose-Edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=58, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate / "dose-Edep.mhd", dose.edep.get_output_path(), stats, tolerance=58, + ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test035_dose_rate.py b/opengate/tests/src/test035_dose_rate.py index 26a281379..8be62b0cf 100755 --- a/opengate/tests/src/test035_dose_rate.py +++ b/opengate/tests/src/test035_dose_rate.py @@ -58,14 +58,9 @@ h = sim.get_actor("dose") print(h) is_ok = ( - utility.assert_images( - paths.output_ref / "edep.mhd", - h.edep.get_output_path(), - stats, - tolerance=15, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "edep.mhd", h.edep.get_output_path(), stats, tolerance=15, + ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test036_proj_param_1.py b/opengate/tests/src/test036_proj_param_1.py index 82450b199..c15403cbe 100755 --- a/opengate/tests/src/test036_proj_param_1.py +++ b/opengate/tests/src/test036_proj_param_1.py @@ -44,15 +44,8 @@ # test the output stats = sim.get_actor("Stats") - is_ok = utility.assert_images( - paths.output_ref / proj.output_filename, - proj.get_output_path(), - stats, - tolerance=38, - ignore_value=0, - axis="y", - sum_tolerance=1.5, - fig_name=paths.output / f"proj.png", - ) + is_ok = utility.assert_images(paths.output_ref / proj.output_filename, proj.get_output_path(), stats, tolerance=38, + ignore_value_data2=0, axis="y", fig_name=paths.output / f"proj.png", + sum_tolerance=1.5) utility.print_test(is_ok, f"Compare image proj:") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test036_proj_param_2_with_index.py b/opengate/tests/src/test036_proj_param_2_with_index.py index fa999108f..5b8bda5dd 100755 --- a/opengate/tests/src/test036_proj_param_2_with_index.py +++ b/opengate/tests/src/test036_proj_param_2_with_index.py @@ -31,15 +31,8 @@ # test the output stats = sim.get_actor("Stats") - is_ok = utility.assert_images( - paths.output_ref / "proj1.mha", - proj.get_output_path(), - stats, - tolerance=38, - ignore_value=0, - axis="y", - sum_tolerance=1.5, - fig_name=paths.output / f"proj_index.png", - ) + is_ok = utility.assert_images(paths.output_ref / "proj1.mha", proj.get_output_path(), stats, tolerance=38, + ignore_value_data2=0, axis="y", fig_name=paths.output / f"proj_index.png", + sum_tolerance=1.5) utility.print_test(is_ok, f"Compare image proj:") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py index 9185fe78c..e641f9436 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py @@ -64,15 +64,10 @@ # image is_ok = ( - utility.assert_images( - paths.output_ref / "test038_gan_aa_proj.mhd", - paths.output / "test038_gan_aa_proj.mhd", - stats, - tolerance=70, - axis="x", - sum_tolerance=2.75, - ) - and is_ok + utility.assert_images(paths.output_ref / "test038_gan_aa_proj.mhd", + paths.output / "test038_gan_aa_proj.mhd", stats, tolerance=70, axis="x", + sum_tolerance=2.75) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test041_dose_actor.py b/opengate/tests/src/test041_dose_actor.py index d3b374c08..655ec84a1 100755 --- a/opengate/tests/src/test041_dose_actor.py +++ b/opengate/tests/src/test041_dose_actor.py @@ -102,38 +102,24 @@ gate.exception.warning("\nDifference for EDEP") is_ok = ( - utility.assert_images( - paths.gate_output / "output2-Edep.mhd", - dose_actor.edep.get_output_path(), - stats, - tolerance=10, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate_output / "output2-Edep.mhd", dose_actor.edep.get_output_path(), stats, + tolerance=10, ignore_value_data2=0) + and is_ok ) gate.exception.warning("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - paths.gate_output / "output2-Edep-Uncertainty.mhd", - dose_actor.edep_uncertainty.get_output_path(), - stats, - tolerance=30, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate_output / "output2-Edep-Uncertainty.mhd", + dose_actor.edep_uncertainty.get_output_path(), stats, tolerance=30, + ignore_value_data2=0) + and is_ok ) gate.exception.warning("\nDifference for dose in Gray") is_ok = ( - utility.assert_images( - paths.gate_output / "output2-Dose.mhd", - dose_actor.dose.get_output_path(), - stats, - tolerance=10, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate_output / "output2-Dose.mhd", dose_actor.dose.get_output_path(), stats, + tolerance=10, ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py index 1f754b215..52a545618 100755 --- a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py +++ b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py @@ -150,14 +150,8 @@ # so we do not need to manually keep track of the paths here in the script # syntax: dose_actor.dose.get_output_path() - unused = utility.assert_images( - dose_actor_IDD_d.dose.get_output_path(), - dose_actor_IDD_d2w.dose.get_output_path(), - stats, - tolerance=100, - ignore_value=0, - axis="x", - ) + unused = utility.assert_images(dose_actor_IDD_d.dose.get_output_path(), dose_actor_IDD_d2w.dose.get_output_path(), + stats, tolerance=100, ignore_value_data2=0, axis="x") mSPR_40MeV = 1.268771331 # from PSTAR NIST tables, Feb 2023 mSPR_80MeV = 1.253197674 # from PSTAR NIST tables, Feb 2023 diff --git a/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py b/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py index 97a607bfe..be14e3015 100755 --- a/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py +++ b/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py @@ -107,14 +107,8 @@ def run_sim(N_events: int, N_threads: int, N_voxels: int, paths): def run_test(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stat): - unused = utility.assert_images( - doseFpath_IDD_singleImage, - doseFpath_IDD_NthreadImages, - stat, - tolerance=100, - ignore_value=0, - axis="x", - ) + unused = utility.assert_images(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stat, tolerance=100, + ignore_value_data2=0, axis="x") expected_ratio = 1.00 gate.exception.warning("Test ratio: edep in single image vs. Nthread image") is_ok = utility.assert_images_ratio( diff --git a/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py b/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py index bc0214697..664cda904 100755 --- a/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py +++ b/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py @@ -127,14 +127,8 @@ def run_sim(n_thr, c4_ref=None, paths=None): sim.get_actor(doseActorName_IDD_singleImage).get_output_path("edep_uncertainty") ) - unused = utility.assert_images( - doseFpath_IDD_singleImage, - doseFpath_IDD_NthreadImages, - stats, - tolerance=100, - ignore_value=0, - axis="x", - ) + unused = utility.assert_images(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stats, tolerance=100, + ignore_value_data2=0, axis="x") expected_ratio = 1.00 gate.exception.warning("Test ratio: dose / dose MT cp image for each trhead") is_ok = utility.assert_images_ratio( diff --git a/opengate/tests/src/test042_gauss_gps.py b/opengate/tests/src/test042_gauss_gps.py index 958cd4b92..35d15c152 100755 --- a/opengate/tests/src/test042_gauss_gps.py +++ b/opengate/tests/src/test042_gauss_gps.py @@ -124,42 +124,28 @@ print() gate.exception.warning("Difference for EDEP XZ") is_ok = ( - utility.assert_images( - paths.gate_output / "lateral_xz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInXZ").get_output_path("edep"), - stats, - tolerance=10, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate_output / "lateral_xz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInXZ").get_output_path("edep"), stats, tolerance=10, + ignore_value_data2=0) + and is_ok ) print() gate.exception.warning("Difference for EDEP XY") is_ok = ( - utility.assert_images( - paths.gate_output / "lateral_xy_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInXY").get_output_path("edep"), - stats, - tolerance=10, - ignore_value=0, - axis="y", - ) - and is_ok + utility.assert_images(paths.gate_output / "lateral_xy_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInXY").get_output_path("edep"), stats, tolerance=10, + ignore_value_data2=0, axis="y") + and is_ok ) print() gate.exception.warning("Difference for EDEP YZ") is_ok = ( - utility.assert_images( - paths.gate_output / "lateral_yz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInYZ").get_output_path("edep"), - stats, - tolerance=30, - ignore_value=0, - axis="y", - ) - and is_ok + utility.assert_images(paths.gate_output / "lateral_yz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInYZ").get_output_path("edep"), stats, tolerance=30, + ignore_value_data2=0, axis="y") + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test043_garf.py b/opengate/tests/src/test043_garf.py index fb092405e..e920fb817 100755 --- a/opengate/tests/src/test043_garf.py +++ b/opengate/tests/src/test043_garf.py @@ -127,30 +127,17 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog.mhd", - filename1, - stat, - tolerance=100, - ignore_value=0, - axis="x", - sum_tolerance=20, - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", filename1, stat, + tolerance=100, ignore_value_data2=0, axis="x", sum_tolerance=20) + and is_ok ) print() gate.exception.warning("Compare image to analog high statistics") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", - filename2, - stat, - tolerance=52, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", filename2, stat, + tolerance=52, ignore_value_data2=0, axis="x") + and is_ok ) print() diff --git a/opengate/tests/src/test043_garf_analog.py b/opengate/tests/src/test043_garf_analog.py index c007214fc..d1563e794 100755 --- a/opengate/tests/src/test043_garf_analog.py +++ b/opengate/tests/src/test043_garf_analog.py @@ -85,27 +85,15 @@ print() gate.exception.warning("Tests projection (old gate)") is_ok = ( - utility.assert_images( - test43.paths.gate_output / "projection_analog.mhd", - fn, - stats, - tolerance=75, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(test43.paths.gate_output / "projection_analog.mhd", fn, stats, tolerance=75, + ignore_value_data2=0, axis="x") + and is_ok ) print() gate.exception.warning("Tests projection (new)") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog.mhd", - proj.get_output_path(), - stats, - tolerance=80, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", proj.get_output_path(), + stats, tolerance=80, ignore_value_data2=0, axis="x") + and is_ok ) diff --git a/opengate/tests/src/test043_garf_mt.py b/opengate/tests/src/test043_garf_mt.py index 99f557f45..9c2b02eae 100755 --- a/opengate/tests/src/test043_garf_mt.py +++ b/opengate/tests/src/test043_garf_mt.py @@ -115,30 +115,17 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog.mhd", - filename1, - stats, - tolerance=100, - ignore_value=0, - axis="x", - sum_tolerance=20, - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", filename1, stats, + tolerance=100, ignore_value_data2=0, axis="x", sum_tolerance=20) + and is_ok ) print() gate.exception.warning("Compare image to analog high statistics") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", - filename2, - stats, - tolerance=52, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", filename2, stats, + tolerance=52, ignore_value_data2=0, axis="x") + and is_ok ) print() diff --git a/opengate/tests/src/test044_pbs.py b/opengate/tests/src/test044_pbs.py index ce32e4f85..f662a06d0 100755 --- a/opengate/tests/src/test044_pbs.py +++ b/opengate/tests/src/test044_pbs.py @@ -159,14 +159,8 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images( - ref_path / mhd_ref, - mhd_gate, - stats, - tolerance=50, - ignore_value=0, - ) - and is_ok + utility.assert_images(ref_path / mhd_ref, mhd_gate, stats, tolerance=50, ignore_value_data2=0) + and is_ok ) """EdepColorMap = utility.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = 'Plane_'+str(i)+'ColorMap.png' diff --git a/opengate/tests/src/test044_pbs_rot_transl.py b/opengate/tests/src/test044_pbs_rot_transl.py index 10eba703d..f516800eb 100755 --- a/opengate/tests/src/test044_pbs_rot_transl.py +++ b/opengate/tests/src/test044_pbs_rot_transl.py @@ -170,15 +170,9 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).edep.get_output_path() mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images( - ref_path / mhd_ref, - output_path / mhd_gate, - stat, - axis="x", - tolerance=50, - ignore_value=0, - ) - and is_ok + utility.assert_images(ref_path / mhd_ref, output_path / mhd_gate, stat, tolerance=50, + ignore_value_data2=0, axis="x") + and is_ok ) """ diff --git a/opengate/tests/src/test044_pbs_source_to_volume.py b/opengate/tests/src/test044_pbs_source_to_volume.py index 439f66724..e18a5c283 100755 --- a/opengate/tests/src/test044_pbs_source_to_volume.py +++ b/opengate/tests/src/test044_pbs_source_to_volume.py @@ -162,14 +162,8 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images( - ref_path / mhd_ref, - mhd_gate, - stat, - tolerance=50, - ignore_value=0, - ) - and is_ok + utility.assert_images(ref_path / mhd_ref, mhd_gate, stat, tolerance=50, ignore_value_data2=0) + and is_ok ) """EdepColorMap = utility.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = "Plane_" + str(i) + "ColorMap.png" diff --git a/opengate/tests/src/test044_pbs_unfocused.py b/opengate/tests/src/test044_pbs_unfocused.py index 2096030ee..ee58e3d0e 100755 --- a/opengate/tests/src/test044_pbs_unfocused.py +++ b/opengate/tests/src/test044_pbs_unfocused.py @@ -172,14 +172,9 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images( - ref_path / mhd_ref, - output_path / mhd_gate, - stats, - tolerance=50, - ignore_value=0, - ) - and is_ok + utility.assert_images(ref_path / mhd_ref, output_path / mhd_gate, stats, tolerance=50, + ignore_value_data2=0) + and is_ok ) """EdepColorMap = utlity.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = 'Plane_'+str(i)+'ColorMap.png' diff --git a/opengate/tests/src/test047_gan_vox_source_cond.py b/opengate/tests/src/test047_gan_vox_source_cond.py index de866f506..c6c49eb18 100755 --- a/opengate/tests/src/test047_gan_vox_source_cond.py +++ b/opengate/tests/src/test047_gan_vox_source_cond.py @@ -140,15 +140,9 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images( - paths.output_ref / "test047-edep.mhd", - dose.get_output_path("edep"), - stats, - tolerance=19, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(paths.output_ref / "test047-edep.mhd", dose.get_output_path("edep"), stats, + tolerance=19, ignore_value_data2=0, axis="x") + and is_ok ) print("Test with vv: ") diff --git a/opengate/tests/src/test048_split_spect_projections.py b/opengate/tests/src/test048_split_spect_projections.py index 0c0a132a9..c496523fe 100755 --- a/opengate/tests/src/test048_split_spect_projections.py +++ b/opengate/tests/src/test048_split_spect_projections.py @@ -40,15 +40,9 @@ is_ok = True for i in range(nb_ene): is_ok = ( - utility.assert_images( - paths.output_ref / f"t048_projection_{i}.mhd", - output_filenames[i], - None, - tolerance=1e-6, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(paths.output_ref / f"t048_projection_{i}.mhd", output_filenames[i], None, + tolerance=1e-6, ignore_value_data2=0, axis="x") + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test050_let_actor_letd_mt.py b/opengate/tests/src/test050_let_actor_letd_mt.py index b3ad085b5..20bde9437 100755 --- a/opengate/tests/src/test050_let_actor_letd_mt.py +++ b/opengate/tests/src/test050_let_actor_letd_mt.py @@ -165,15 +165,9 @@ fNameIDD = doseIDD.edep.output_filename if do_debug: - is_ok = utility.assert_images( - ref_path / fNameIDD, - doseIDD.edep.get_output_path(), - stats, - tolerance=100, - ignore_value=0, - axis="x", - scaleImageValuesFactor=numPartSimRef / numPartSimTest, - ) + is_ok = utility.assert_images(ref_path / fNameIDD, doseIDD.edep.get_output_path(), stats, tolerance=100, + ignore_value_data2=0, axis="x", + scaleImageValuesFactor=numPartSimRef / numPartSimTest) tests_pass = [] is_ok = utility.assert_filtered_imagesprofile1D( diff --git a/opengate/tests/src/test059_tpsource_flat_generation_flag.py b/opengate/tests/src/test059_tpsource_flat_generation_flag.py index 2eabdaca4..2dd2ac789 100755 --- a/opengate/tests/src/test059_tpsource_flat_generation_flag.py +++ b/opengate/tests/src/test059_tpsource_flat_generation_flag.py @@ -239,27 +239,18 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): # check that the dose output is the same print("--- Dose image spot 0 ---") test = ( - utility.assert_images( - output_path / dose_actors[0].get_output_path("edep"), - output_path / dose_actors[2].get_output_path("edep"), - stats, - tolerance=70, - ignore_value=0, - ) - and test + utility.assert_images(output_path / dose_actors[0].get_output_path("edep"), + output_path / dose_actors[2].get_output_path("edep"), stats, tolerance=70, + ignore_value_data2=0) + and test ) print("--- Dose image spot 1 ---") test = ( - utility.assert_images( - output_path / dose_actors[1].get_output_path("edep"), - output_path / dose_actors[3].get_output_path("edep"), - stats, - tolerance=70, - ignore_value=0, - sum_tolerance=5.2, - ) - and test + utility.assert_images(output_path / dose_actors[1].get_output_path("edep"), + output_path / dose_actors[3].get_output_path("edep"), stats, tolerance=70, + ignore_value_data2=0, sum_tolerance=5.2) + and test ) # check that output with flat distribution has better statistics for the spot with less particles diff --git a/opengate/tests/src/test059_tpsource_weights.py b/opengate/tests/src/test059_tpsource_weights.py index 2e61f3034..8f1680341 100755 --- a/opengate/tests/src/test059_tpsource_weights.py +++ b/opengate/tests/src/test059_tpsource_weights.py @@ -161,26 +161,14 @@ # check first spot test = ( - utility.assert_images( - ref_path / mhd_1, - output_path / mhd_1, - stats, - tolerance=70, - ignore_value=0, - ) - and test + utility.assert_images(ref_path / mhd_1, output_path / mhd_1, stats, tolerance=70, ignore_value_data2=0) + and test ) # check second spot test = ( - utility.assert_images( - ref_path / mhd_1, - output_path / mhd_1, - stats, - tolerance=70, - ignore_value=0, - ) - and test + utility.assert_images(ref_path / mhd_1, output_path / mhd_1, stats, tolerance=70, ignore_value_data2=0) + and test ) print(" --------------------------------------- ") # fig1 = utility.create_2D_Edep_colorMap(output_path / mhd_1, show=True) diff --git a/opengate/tests/src/test065_dose2water_from_edep2water_ct.py b/opengate/tests/src/test065_dose2water_from_edep2water_ct.py index 36d5f228f..929dd3802 100755 --- a/opengate/tests/src/test065_dose2water_from_edep2water_ct.py +++ b/opengate/tests/src/test065_dose2water_from_edep2water_ct.py @@ -172,10 +172,6 @@ # img_mhd_out = itk.imread(d_post_path) # img_mhd_ref = itk.imread(d_step_path) - ok = utility.assert_images( - d_step_path, - d_post_path, - tolerance=10, - ) + ok = utility.assert_images(d_step_path, d_post_path, tolerance=10) utility.test_ok(ok) diff --git a/opengate/tests/src/test065_dose_from_edep_ct.py b/opengate/tests/src/test065_dose_from_edep_ct.py index b21fd8a0c..0e2481c8f 100755 --- a/opengate/tests/src/test065_dose_from_edep_ct.py +++ b/opengate/tests/src/test065_dose_from_edep_ct.py @@ -170,10 +170,6 @@ # img_mhd_out = itk.imread(d_post_path) # img_mhd_ref = itk.imread(d_step_path) - ok = utility.assert_images( - d_step_path, - d_post_path, - tolerance=10, - ) + ok = utility.assert_images(d_step_path, d_post_path, tolerance=10) utility.test_ok(ok) diff --git a/opengate/tests/src/test065_dose_from_edep_volume.py b/opengate/tests/src/test065_dose_from_edep_volume.py index 7fc2b34c8..6ae83655f 100755 --- a/opengate/tests/src/test065_dose_from_edep_volume.py +++ b/opengate/tests/src/test065_dose_from_edep_volume.py @@ -150,10 +150,6 @@ # img_mhd_out = itk.imread(d_post_path) # img_mhd_ref = itk.imread(d_step_path) - ok = utility.assert_images( - d_step_path, - d_post_path, - tolerance=10, - ) + ok = utility.assert_images(d_step_path, d_post_path, tolerance=10) utility.test_ok(ok) diff --git a/opengate/tests/src/test067_cbct_fluence_actor_mt.py b/opengate/tests/src/test067_cbct_fluence_actor_mt.py index a05224812..abe24c73b 100755 --- a/opengate/tests/src/test067_cbct_fluence_actor_mt.py +++ b/opengate/tests/src/test067_cbct_fluence_actor_mt.py @@ -92,12 +92,6 @@ out_path = detector_actor.get_output_path() # check images - is_ok = utility.assert_images( - paths.gate_output / "detector.mhd", - out_path, - stats, - tolerance=44, - axis="y", - ) + is_ok = utility.assert_images(paths.gate_output / "detector.mhd", out_path, stats, tolerance=44, axis="y") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test073_helpers.py b/opengate/tests/src/test073_helpers.py index 00c412b6f..0ea9191af 100644 --- a/opengate/tests/src/test073_helpers.py +++ b/opengate/tests/src/test073_helpers.py @@ -170,16 +170,8 @@ def compare_proj_images(crystal, sim, stats, image_filename, path, n=1): img.SetOrigin(origin) itk.imwrite(img, f2) - is_ok = utility.assert_images( - fr, - f2, - stats, - tolerance=69, - ignore_value=0, - axis="y", - sum_tolerance=6, - fig_name=path / f"test073_test_{n}.png", - ) + is_ok = utility.assert_images(fr, f2, stats, tolerance=69, ignore_value_data2=0, axis="y", + fig_name=path / f"test073_test_{n}.png", sum_tolerance=6) return is_ok From 2e1bc083566e2d190d36a85b2f6a207dc5f669c9 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:32:20 +0200 Subject: [PATCH 037/183] Update calls to assert_images in inactive code: signature has changed --- opengate/tests/src/test044_pbs_weight.py | 2 +- opengate/tests/src/test059_tpsource_gantry_rot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opengate/tests/src/test044_pbs_weight.py b/opengate/tests/src/test044_pbs_weight.py index 184c4c18f..1c9359f87 100755 --- a/opengate/tests/src/test044_pbs_weight.py +++ b/opengate/tests/src/test044_pbs_weight.py @@ -184,7 +184,7 @@ # stat, # axis="x", # tolerance=50, - # ignore_value=0, + # ignore_value_data2=0, # ) fig1 = utility.create_2D_Edep_colorMap(output_path / mhd_1, show=False) fig2 = utility.create_2D_Edep_colorMap(output_path / mhd_2, show=False) diff --git a/opengate/tests/src/test059_tpsource_gantry_rot.py b/opengate/tests/src/test059_tpsource_gantry_rot.py index feba93172..a0ee45411 100755 --- a/opengate/tests/src/test059_tpsource_gantry_rot.py +++ b/opengate/tests/src/test059_tpsource_gantry_rot.py @@ -185,7 +185,7 @@ # dose_rot.output, # stat, # tolerance=50, - # ignore_value=0, + # ignore_value_data2=0, # ) ok = True From 4eb4e40ec48af328caea0ae7202f90546549f512 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 12:38:16 +0200 Subject: [PATCH 038/183] Optimize code in create_density_img() and let it return g/cm3 --- opengate/geometry/materials.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/opengate/geometry/materials.py b/opengate/geometry/materials.py index ce207a04a..9a8b5a226 100644 --- a/opengate/geometry/materials.py +++ b/opengate/geometry/materials.py @@ -303,24 +303,18 @@ def create_density_img(img_volume, material_database): Returns ------- rho : itk.Image - image of the same size and resolution of the ct. The voxel value is the density of the voxel. - Density is returned in G4 1/kg. + Image of the same size and resolution of the ct. The voxel value is the density of the voxel converted to g/cm3. """ - voxel_materials = img_volume.voxel_materials - ct_itk = img_volume.itk_image - act = itk.GetArrayFromImage(ct_itk) + act = itk.GetArrayFromImage(img_volume.itk_image) arho = np.zeros(act.shape, dtype=np.float32) - for material in voxel_materials: - *hu_interval, mat_name = material - hu0, hu1 = hu_interval - m = (act >= hu0) * (act < hu1) - density = material_database[mat_name].GetDensity() - arho[m] = density + for hu0, hu1, mat_name in img_volume.voxel_materials: + arho[(act >= hu0) * (act < hu1)] = material_database[mat_name].GetDensity() + arho *= (g4_units.cm3 / g4_units.g) rho = itk.GetImageFromArray(arho) - rho.CopyInformation(ct_itk) + rho.CopyInformation(img_volume.itk_image) return rho From e0e7a0361f52f78a17a746b3303e540d92d16b85 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 12:38:52 +0200 Subject: [PATCH 039/183] Remove density scoring via MC from DoseActor because it is incorrect and not needed --- .../opengate_lib/GateDoseActor.cpp | 20 ++------ .../opengate_lib/GateDoseActor.h | 7 --- .../opengate_lib/pyGateDoseActor.cpp | 2 - opengate/actors/doseactors.py | 50 ++++++++----------- 4 files changed, 25 insertions(+), 54 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index 0b1258cad..17d52516b 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -83,9 +83,6 @@ void GateDoseActor::InitializeCpp() { if (fDoseSquaredFlag) { cpp_dose_squared_image = Image3DType::New(); } - if (fDensityFlag) { - cpp_density_image = Image3DType::New(); - } if (fCountsFlag) { cpp_counts_image = Image3DType::New(); } @@ -117,10 +114,6 @@ void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { AttachImageToVolume(cpp_dose_squared_image, fPhysicalVolumeName, fTranslation); } - if (fDensityFlag) { - AttachImageToVolume(cpp_density_image, fPhysicalVolumeName, - fTranslation); - } if (fCountsFlag) { AttachImageToVolume(cpp_counts_image, fPhysicalVolumeName, fTranslation); @@ -220,17 +213,12 @@ void GateDoseActor::SteppingAction(G4Step *step) { ImageAddValue(cpp_edep_image, index, edep); - if (fDensityFlag || fDoseFlag || fDoseSquaredFlag) { + if (fDoseFlag || fDoseSquaredFlag) { auto *current_material = step->GetPreStepPoint()->GetMaterial(); auto density = current_material->GetDensity(); - if (fDensityFlag) { - ImageAddValue(cpp_density_image, index, density); - } - if (fDoseFlag || fDoseSquaredFlag) { - dose = edep / density; - if (fDoseFlag) { - ImageAddValue(cpp_dose_image, index, dose); - } + dose = edep / density; + if (fDoseFlag) { + ImageAddValue(cpp_dose_image, index, dose); } } diff --git a/core/opengate_core/opengate_lib/GateDoseActor.h b/core/opengate_core/opengate_lib/GateDoseActor.h index 2a2a9e670..d0a0a728f 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.h +++ b/core/opengate_core/opengate_lib/GateDoseActor.h @@ -53,10 +53,6 @@ class GateDoseActor : public GateVActor { inline void SetEdepSquaredFlag(const bool b) { fEdepSquaredFlag = b; } - inline void SetDensityFlag(const bool b) { fDensityFlag = b; } - - inline bool GetDensityFlag() const { return fDensityFlag; } - inline void SetDoseFlag(const bool b) { fDoseFlag = b; } inline bool GetDoseFlag() const { return fDoseFlag; } @@ -131,9 +127,6 @@ class GateDoseActor : public GateVActor { bool fDoseFlag{}; bool fDoseSquaredFlag{}; - // Option: Is density to be scored? - bool fDensityFlag{}; - // Option: Are counts to be scored bool fCountsFlag{}; diff --git a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp index 5d01fd046..3e3fdc262 100644 --- a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp @@ -45,8 +45,6 @@ void init_GateDoseActor(py::module &m) { .def("SetDoseSquaredFlag", &GateDoseActor::SetDoseSquaredFlag) .def("GetToWaterFlag", &GateDoseActor::GetToWaterFlag) .def("SetToWaterFlag", &GateDoseActor::SetToWaterFlag) - .def("GetDensityFlag", &GateDoseActor::GetDensityFlag) - .def("SetDensityFlag", &GateDoseActor::SetDensityFlag) .def("GetCountsFlag", &GateDoseActor::GetCountsFlag) .def("SetCountsFlag", &GateDoseActor::SetCountsFlag) .def("GetPhysicalVolumeName", &GateDoseActor::GetPhysicalVolumeName) diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index 0bd60fc06..219b7d02e 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -400,6 +400,20 @@ class DoseActor(VoxelDepositActor, g4.GateDoseActor): ), }, ), + # "calculate_density_from": ( + # "auto", + # { + # "doc": "How should density be calculated?\n" + # "'simulation': via scoring along with the rest of the quantities.\n" + # "'image': from the CT image, if the actor is attached to an ImageVolume.\n" + # "'auto' (default): Let GATE pick the right one for you. ", + # "allowed_values": ( + # "auto", + # "simulation", + # "image" + # ), + # }, + # ), "ste_of_mean": ( False, { @@ -548,13 +562,6 @@ def create_density_image_from_image_volume(self, deposit_image): ) return density_image - @property - def _density_via_mc(self): - return (self.calculate_density_from == 'simulation' or - (self.calculate_density_from == 'auto' - and self.attached_to_volume.volume_type != "ImageVolume") - ) - def initialize(self, *args): """ At the start of the run, the image is centered according to the coordinate system of @@ -596,12 +603,9 @@ def initialize(self, *args): True, item=1 ) # activate squared component - if self.user_output.density.get_active() is True: - # scoring density via MC implies scoring counts - if self._density_via_mc: - if not self.user_output.counts.get_active(): - self.user_output.counts.set_active(True) - self.user_output.counts.set_write_to_disk(False) + if self.user_output.density.get_active() is True and self.attached_to_volume.volume_type != 'ImageVolume': + fatal("The dose actor can only produce a density map if it is attached to an ImageVolume. " + f"This actor is attached to a {self.attached_to_volume.volume_type} volume. ") self.InitializeUserInput(self.user_info) # C++ side # Set the flags on C++ side so the C++ knows which quantities need to be scored @@ -610,7 +614,6 @@ def initialize(self, *args): self.SetDoseSquaredFlag(self.user_output.dose_with_uncertainty.get_active(item=1)) # item=0 is the default self.SetCountsFlag(self.user_output.counts.get_active()) - self.SetDensityFlag(self.user_output.density.get_active() and self._density_via_mc) # C++ side has a boolean toWaterFlag and self.score_in == "water" yields True/False self.SetToWaterFlag(self.score_in == "water") @@ -631,12 +634,6 @@ def BeginOfRunActionMasterThread(self, run_index): self.prepare_output_for_run("dose_with_uncertainty", run_index) self.push_to_cpp_image("dose_with_uncertainty", run_index, self.cpp_dose_image, self.cpp_dose_squared_image) - # density might be active, but the user might want to get it from the ImageVolume - # therefore, we also check for _density_via_mc - if self.user_output.density.get_active() and self._density_via_mc: - self.prepare_output_for_run("density", run_index) - self.push_to_cpp_image("density", run_index, self.cpp_density_image) - if self.user_output.counts.get_active(): self.prepare_output_for_run("counts", run_index) self.push_to_cpp_image("counts", run_index, self.cpp_counts_image) @@ -684,15 +681,10 @@ def EndOfRunActionMasterThread(self, run_index): # density image if self.user_output.density.get_active(): - if self._density_via_mc: - self.fetch_from_cpp_image("density", run_index, self.cpp_density_image) - self._update_output_coordinate_system("density", run_index) - self.user_output.density.data_per_run[run_index] /= self.user_output.counts.data_per_run[run_index] - else: - edep_image = self.user_output.edep_with_uncertainty.get_data( - run_index, item=0 - ) - self.user_output.density.store_data(run_index, self.create_density_image_from_image_volume(edep_image)) + edep_image = self.user_output.edep_with_uncertainty.get_data( + run_index, item=0 + ) + self.user_output.density.store_data(run_index, self.create_density_image_from_image_volume(edep_image)) self.user_output.density.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) From 2e13b48b72abfd2a94b031e8fef635fe6d0abff0 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 12:39:03 +0200 Subject: [PATCH 040/183] Update test008_dose_actor_2.py --- opengate/tests/src/test008_dose_actor_2.py | 75 +++++++++++----------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/opengate/tests/src/test008_dose_actor_2.py b/opengate/tests/src/test008_dose_actor_2.py index a0b210068..d76870be7 100755 --- a/opengate/tests/src/test008_dose_actor_2.py +++ b/opengate/tests/src/test008_dose_actor_2.py @@ -71,15 +71,17 @@ stats.output_filename = "stats.txt" # dose actor 1: depth edep - doseactor = sim.add_actor("DoseActor", "depth") - doseactor.attached_to = "patient" - doseactor.output_filename = "depth.mhd" - doseactor.spacing = [5 * mm, 5 * mm, 5 * mm] - doseactor.size = [50, 50, 50] - doseactor.output_coordinate_system = "attached_to_image" - doseactor.dose.active = True - doseactor.dose_uncertainty.active = True - doseactor.density.active = True + doseactor1 = sim.add_actor("DoseActor", "depth") + doseactor1.attached_to = "patient" + doseactor1.output_filename = "depth.mhd" + doseactor1.spacing = [5 * mm, 5 * mm, 5 * mm] + doseactor1.size = [50, 50, 50] + doseactor1.output_coordinate_system = "attached_to_image" + doseactor1.dose.active = True + doseactor1.edep_uncertainty.active = True + doseactor1.dose_uncertainty.active = True + doseactor1.dose_uncertainty.write_to_disk = True + doseactor1.density.active = True # run sim.run() @@ -88,43 +90,42 @@ print(stats) is_ok = True - print("\nDifference for EDEP") + print("\nDifference for Dose") is_ok = ( utility.assert_images( paths.output_ref / "depth_dose.mhd", - doseactor.dose.get_output_path(), + doseactor1.dose.get_output_path(), stats, tolerance=25, - ignore_value=0, + ignore_value_data2=0, sum_tolerance=1, ) and is_ok ) - print("\nDifference for uncertainty") - is_ok = ( - utility.assert_images( - paths.output_ref / "depth_dose_uncertainty.mhd", - doseactor.dose_uncertainty.get_output_path(), - stats, - tolerance=5, - ignore_value=1, - sum_tolerance=1, - ) - and is_ok - ) - - """print("\nDifference for density") - is_ok = ( - utility.assert_images( - paths.output_ref / "depth_density.mhd", - doseactor.density.get_output_path(), - stats, - tolerance=5, - ignore_value=1, - sum_tolerance=1, - ) - and is_ok - )""" + # print("\nDifference for dose uncertainty") + # is_ok = ( + # utility.assert_images( + # paths.output_ref / "depth_dose_uncertainty.mhd", + # doseactor.dose_uncertainty.get_output_path(), + # stats, + # tolerance=5, + # ignore_value_data2=0, + # sum_tolerance=1, + # ) + # and is_ok + # ) + + # print("\nDifference for density: calculated via simulation and from the CT image") + # is_ok = ( + # utility.assert_images( + # paths.output_ref / "depth_density.mhd", + # doseactor1.density.get_output_path(), + # stats, + # tolerance=5, + # sum_tolerance=1, + # ) + # and is_ok + # ) utility.test_ok(is_ok) From f783362e203fb681bcd04c1083caae1ea691960f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:55:30 +0000 Subject: [PATCH 041/183] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateDoseActor.cpp | 49 +++++++------ .../opengate_lib/GateDoseActor.h | 8 +-- .../opengate_lib/pyGateDoseActor.cpp | 6 +- opengate/actors/actoroutput.py | 4 +- opengate/actors/doseactors.py | 70 ++++++++++++------- opengate/geometry/materials.py | 2 +- opengate/tests/src/test008_dose_actor.py | 24 +++++-- ...010_generic_source_angular_distribution.py | 28 +++++--- .../src/test010_generic_source_confine.py | 8 ++- opengate/tests/src/test012_mt_dose_actor.py | 10 ++- opengate/tests/src/test015_iec_phantom_2.py | 11 ++- .../tests/src/test015_iec_phantom_3_mt.py | 2 +- .../tests/src/test015_iec_phantom_5_wip.py | 11 ++- opengate/tests/src/test017_repeater.py | 11 ++- opengate/tests/src/test020_profiling.py | 8 ++- opengate/tests/src/test021_voxel_source2.py | 12 +++- opengate/tests/src/test023_filters.py | 18 +++-- .../tests/src/test023_filters_iec_phantom.py | 9 ++- .../tests/src/test023_filters_material.py | 9 ++- .../src/test028_ge_nm670_spect_2_helpers.py | 30 +++++--- ...t028_ge_nm670_spect_4_acc_angle_helpers.py | 15 ++-- .../src/test029_volume_time_rotation_1.py | 13 +++- .../test029_volume_time_rotation_1_process.py | 13 +++- .../src/test029_volume_time_rotation_2.py | 13 +++- opengate/tests/src/test030_dose_motion.py | 21 ++++-- .../src/test030_dose_motion_dynamic_param.py | 22 ++++-- ...est030_dose_motion_dynamic_param_custom.py | 22 ++++-- .../src/test032_create_voxelized_volume.py | 9 ++- opengate/tests/src/test032_voxel_vs_volume.py | 9 ++- .../src/test032_voxelized_volume_source.py | 24 ++++--- .../src/test033_rotation_spect_aa_helpers.py | 24 +++++-- opengate/tests/src/test034_gan_phsp_linac.py | 11 ++- opengate/tests/src/test035_dose_rate.py | 11 ++- opengate/tests/src/test036_proj_param_1.py | 13 +++- .../src/test036_proj_param_2_with_index.py | 13 +++- .../src/test038_gan_phsp_spect_gan_aa.py | 13 ++-- opengate/tests/src/test041_dose_actor.py | 34 ++++++--- .../test041_dose_actor_dose_to_water_mt.py | 10 ++- ...st041_dose_actor_mt_cp_images_n_threads.py | 10 ++- ...ose_actor_mt_standard_error_of_mean_WIP.py | 10 ++- opengate/tests/src/test042_gauss_gps.py | 38 ++++++---- opengate/tests/src/test043_garf.py | 25 +++++-- opengate/tests/src/test043_garf_analog.py | 24 +++++-- opengate/tests/src/test043_garf_mt.py | 25 +++++-- opengate/tests/src/test044_pbs.py | 6 +- opengate/tests/src/test044_pbs_rot_transl.py | 12 +++- .../tests/src/test044_pbs_source_to_volume.py | 6 +- opengate/tests/src/test044_pbs_unfocused.py | 11 ++- .../tests/src/test047_gan_vox_source_cond.py | 12 +++- .../src/test048_split_spect_projections.py | 12 +++- .../tests/src/test050_let_actor_letd_mt.py | 12 +++- .../test059_tpsource_flat_generation_flag.py | 25 ++++--- .../tests/src/test059_tpsource_weights.py | 20 ++++-- .../src/test067_cbct_fluence_actor_mt.py | 4 +- opengate/tests/src/test073_helpers.py | 12 +++- opengate/tests/utility.py | 4 +- 56 files changed, 638 insertions(+), 250 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index 17d52516b..1d991205a 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -103,16 +103,16 @@ void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { size_edep = region.GetSize(); if (fEdepSquaredFlag) { - AttachImageToVolume(cpp_edep_squared_image, fPhysicalVolumeName, - fTranslation); + AttachImageToVolume(cpp_edep_squared_image, + fPhysicalVolumeName, fTranslation); } if (fDoseFlag) { AttachImageToVolume(cpp_dose_image, fPhysicalVolumeName, fTranslation); } if (fDoseSquaredFlag) { - AttachImageToVolume(cpp_dose_squared_image, fPhysicalVolumeName, - fTranslation); + AttachImageToVolume(cpp_dose_squared_image, + fPhysicalVolumeName, fTranslation); } if (fCountsFlag) { AttachImageToVolume(cpp_counts_image, fPhysicalVolumeName, @@ -120,13 +120,14 @@ void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { } } -void GateDoseActor::PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels) { +void GateDoseActor::PrepareLocalDataForRun(threadLocalT &data, + int numberOfVoxels) { data.squared_worker_flatimg.resize(numberOfVoxels); std::fill(data.squared_worker_flatimg.begin(), data.squared_worker_flatimg.end(), 0.0); data.lastid_worker_flatimg.resize(numberOfVoxels); - std::fill(data.lastid_worker_flatimg.begin(), data.lastid_worker_flatimg.end(), - 0); + std::fill(data.lastid_worker_flatimg.begin(), + data.lastid_worker_flatimg.end(), 0); } void GateDoseActor::BeginOfRunAction(const G4Run *run) { @@ -229,18 +230,19 @@ void GateDoseActor::SteppingAction(G4Step *step) { auto event_id = G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID(); if (fEdepSquaredFlag) { -// G4AutoLock mutex(&SetPixelMutex); - ScoreSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image, edep, event_id, index); + // G4AutoLock mutex(&SetPixelMutex); + ScoreSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image, + edep, event_id, index); } if (fDoseSquaredFlag) { -// G4AutoLock mutex(&SetPixelMutex); - ScoreSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image, dose, event_id, index); + // G4AutoLock mutex(&SetPixelMutex); + ScoreSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image, + dose, event_id, index); } } } // else: outside of the image } - double GateDoseActor::ComputeMeanUncertainty() { G4AutoLock mutex(&ComputeUncertaintyMutex); itk::ImageRegionIterator edep_iterator3D( @@ -311,14 +313,19 @@ void GateDoseActor::ind2sub(int index_flat, Image3DType::IndexType &index3D) { void GateDoseActor::EndOfRunAction(const G4Run *run) { if (fEdepSquaredFlag) { - GateDoseActor::FlushSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image); + GateDoseActor::FlushSquaredValue(fThreadLocalDataEdep.Get(), + cpp_edep_squared_image); } if (fDoseSquaredFlag) { - GateDoseActor::FlushSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image); + GateDoseActor::FlushSquaredValue(fThreadLocalDataDose.Get(), + cpp_dose_squared_image); } } -void GateDoseActor::ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, Image3DType::IndexType index) { +void GateDoseActor::ScoreSquaredValue(threadLocalT &data, + Image3DType::Pointer cpp_image, + double value, int event_id, + Image3DType::IndexType index) { G4AutoLock mutex(&SetPixelMutex); int index_flat = sub2ind(index); auto previous_id = data.lastid_worker_flatimg[index_flat]; @@ -337,8 +344,9 @@ void GateDoseActor::ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer c } } -void GateDoseActor::FlushSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image) { -// G4AutoLock mutex(&SetWorkerEndRunMutex); +void GateDoseActor::FlushSquaredValue(threadLocalT &data, + Image3DType::Pointer cpp_image) { + // G4AutoLock mutex(&SetWorkerEndRunMutex); itk::ImageRegionIterator iterator3D( cpp_image, cpp_image->GetLargestPossibleRegion()); @@ -346,14 +354,11 @@ void GateDoseActor::FlushSquaredValue(threadLocalT &data, Image3DType::Pointer c Image3DType::IndexType index_f = iterator3D.GetIndex(); Image3DType::PixelType pixelValue3D = data.squared_worker_flatimg[sub2ind(index_f)]; - ImageAddValue(cpp_image, index_f, - pixelValue3D * pixelValue3D); + ImageAddValue(cpp_image, index_f, pixelValue3D * pixelValue3D); } } -int GateDoseActor::EndOfRunActionMasterThread(int run_id) { - return 0; -} +int GateDoseActor::EndOfRunActionMasterThread(int run_id) { return 0; } double GateDoseActor::GetMaxValueOfImage(Image3DType::Pointer imageP) { itk::ImageRegionIterator iterator3D( diff --git a/core/opengate_core/opengate_lib/GateDoseActor.h b/core/opengate_core/opengate_lib/GateDoseActor.h index d0a0a728f..098ee3816 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.h +++ b/core/opengate_core/opengate_lib/GateDoseActor.h @@ -99,16 +99,16 @@ class GateDoseActor : public GateVActor { // Image3DType::IndexType index3D; // int index_flat; }; -// using ThreadLocalType = struct threadLocalT; + // using ThreadLocalType = struct threadLocalT; - void ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, Image3DType::IndexType index); + void ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, + double value, int event_id, + Image3DType::IndexType index); void FlushSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image); void PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels); - - // // Option: indicate if we must compute uncertainty // bool fUncertaintyFlag; diff --git a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp index 3e3fdc262..7ce44b353 100644 --- a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp @@ -51,9 +51,11 @@ void init_GateDoseActor(py::module &m) { .def("SetPhysicalVolumeName", &GateDoseActor::SetPhysicalVolumeName) .def_readwrite("NbOfEvent", &GateDoseActor::NbOfEvent) .def_readwrite("cpp_edep_image", &GateDoseActor::cpp_edep_image) - .def_readwrite("cpp_edep_squared_image", &GateDoseActor::cpp_edep_squared_image) + .def_readwrite("cpp_edep_squared_image", + &GateDoseActor::cpp_edep_squared_image) .def_readwrite("cpp_dose_image", &GateDoseActor::cpp_dose_image) - .def_readwrite("cpp_dose_squared_image", &GateDoseActor::cpp_dose_squared_image) + .def_readwrite("cpp_dose_squared_image", + &GateDoseActor::cpp_dose_squared_image) .def_readwrite("cpp_density_image", &GateDoseActor::cpp_density_image) .def_readwrite("cpp_counts_image", &GateDoseActor::cpp_counts_image) .def_readwrite("fPhysicalVolumeName", diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index aa31e2291..96534481a 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -504,8 +504,8 @@ def set_active(self, value, item=0): self.data_item_config[i]["active"] = bool(value) def get_active(self, item=0): - if item == 'any': - item = 'all' + if item == "any": + item = "all" items = self._collect_item_identifiers(item) return any([self.data_item_config[k]["active"] is True for k in items]) diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index 219b7d02e..6ffae3ea3 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -515,7 +515,7 @@ def __init__(self, *args, **kwargs): # set the defaults for the user output of this actor self._add_user_output(ActorOutputSingleMeanImage, "density") self._add_user_output(ActorOutputSingleImage, "counts") - self.user_output.dose_with_uncertainty.set_active(False, item='all') + self.user_output.dose_with_uncertainty.set_active(False, item="all") self.user_output.density.set_active(False) self.user_output.counts.set_active(False) @@ -552,8 +552,10 @@ def __initcpp__(self): def create_density_image_from_image_volume(self, deposit_image): if self.attached_to_volume.volume_type != "ImageVolume": - fatal(f"Cannot calculate the density map from the ImageVolume " - f"because this actor is attached to a {self.attached_to_volume.volume_type}. ") + fatal( + f"Cannot calculate the density map from the ImageVolume " + f"because this actor is attached to a {self.attached_to_volume.volume_type}. " + ) density_image = self.attached_to_volume.create_density_image() if images_have_same_domain(deposit_image, density_image) is False: @@ -575,10 +577,10 @@ def initialize(self, *args): # Make sure the squared component (item 1) is active if any of the quantities relying on it are active if ( - self.user_output.edep_with_uncertainty.get_active( - item=("uncertainty", "std", "variance") - ) - is True + self.user_output.edep_with_uncertainty.get_active( + item=("uncertainty", "std", "variance") + ) + is True ): # activate the squared component, but avoid writing it to disk # because the user has not activated it and thus most likely does not want it @@ -590,10 +592,10 @@ def initialize(self, *args): # Make sure the squared component (item 1) is active if any of the quantities relying on it are active if ( - self.user_output.dose_with_uncertainty.get_active( - item=("uncertainty", "std", "variance") - ) - is True + self.user_output.dose_with_uncertainty.get_active( + item=("uncertainty", "std", "variance") + ) + is True ): # activate the squared component, but avoid writing it to disk # because the user has not activated it and thus most likely does not want it @@ -603,15 +605,24 @@ def initialize(self, *args): True, item=1 ) # activate squared component - if self.user_output.density.get_active() is True and self.attached_to_volume.volume_type != 'ImageVolume': - fatal("The dose actor can only produce a density map if it is attached to an ImageVolume. " - f"This actor is attached to a {self.attached_to_volume.volume_type} volume. ") + if ( + self.user_output.density.get_active() is True + and self.attached_to_volume.volume_type != "ImageVolume" + ): + fatal( + "The dose actor can only produce a density map if it is attached to an ImageVolume. " + f"This actor is attached to a {self.attached_to_volume.volume_type} volume. " + ) self.InitializeUserInput(self.user_info) # C++ side # Set the flags on C++ side so the C++ knows which quantities need to be scored - self.SetEdepSquaredFlag(self.user_output.edep_with_uncertainty.get_active(item=1)) + self.SetEdepSquaredFlag( + self.user_output.edep_with_uncertainty.get_active(item=1) + ) self.SetDoseFlag(self.user_output.dose_with_uncertainty.get_active(item=0)) - self.SetDoseSquaredFlag(self.user_output.dose_with_uncertainty.get_active(item=1)) + self.SetDoseSquaredFlag( + self.user_output.dose_with_uncertainty.get_active(item=1) + ) # item=0 is the default self.SetCountsFlag(self.user_output.counts.get_active()) # C++ side has a boolean toWaterFlag and self.score_in == "water" yields True/False @@ -630,9 +641,14 @@ def BeginOfRunActionMasterThread(self, run_index): self.cpp_edep_squared_image, ) - if self.user_output.dose_with_uncertainty.get_active(item='any'): + if self.user_output.dose_with_uncertainty.get_active(item="any"): self.prepare_output_for_run("dose_with_uncertainty", run_index) - self.push_to_cpp_image("dose_with_uncertainty", run_index, self.cpp_dose_image, self.cpp_dose_squared_image) + self.push_to_cpp_image( + "dose_with_uncertainty", + run_index, + self.cpp_dose_image, + self.cpp_dose_squared_image, + ) if self.user_output.counts.get_active(): self.prepare_output_for_run("counts", run_index) @@ -652,7 +668,7 @@ def EndOfRunActionMasterThread(self, run_index): run_index, number_of_samples=self.NbOfEvent ) - if self.user_output.dose_with_uncertainty.get_active(item='any'): + if self.user_output.dose_with_uncertainty.get_active(item="any"): self.fetch_from_cpp_image( "dose_with_uncertainty", run_index, @@ -665,12 +681,16 @@ def EndOfRunActionMasterThread(self, run_index): ) # divide by voxel volume and scale to unit Gy if self.user_output.dose_with_uncertainty.get_active(item=0): - self.user_output.dose_with_uncertainty.data_per_run[run_index].data[0] /= \ - (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) + self.user_output.dose_with_uncertainty.data_per_run[run_index].data[ + 0 + ] /= (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) if self.user_output.dose_with_uncertainty.get_active(item=1): # in the squared component 1, the denominator needs to be squared - self.user_output.dose_with_uncertainty.data_per_run[run_index].data[1] /= ( - (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) ** 2) + self.user_output.dose_with_uncertainty.data_per_run[run_index].data[ + 1 + ] /= ( + g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2] + ) ** 2 if self.user_output.counts.get_active(): self.fetch_from_cpp_image("counts", run_index, self.cpp_counts_image) @@ -684,7 +704,9 @@ def EndOfRunActionMasterThread(self, run_index): edep_image = self.user_output.edep_with_uncertainty.get_data( run_index, item=0 ) - self.user_output.density.store_data(run_index, self.create_density_image_from_image_volume(edep_image)) + self.user_output.density.store_data( + run_index, self.create_density_image_from_image_volume(edep_image) + ) self.user_output.density.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) diff --git a/opengate/geometry/materials.py b/opengate/geometry/materials.py index 9a8b5a226..cd4e3871f 100644 --- a/opengate/geometry/materials.py +++ b/opengate/geometry/materials.py @@ -312,7 +312,7 @@ def create_density_img(img_volume, material_database): for hu0, hu1, mat_name in img_volume.voxel_materials: arho[(act >= hu0) * (act < hu1)] = material_database[mat_name].GetDensity() - arho *= (g4_units.cm3 / g4_units.g) + arho *= g4_units.cm3 / g4_units.g rho = itk.GetImageFromArray(arho) rho.CopyInformation(img_volume.itk_image) diff --git a/opengate/tests/src/test008_dose_actor.py b/opengate/tests/src/test008_dose_actor.py index a6e6dfe02..cc8f6d161 100755 --- a/opengate/tests/src/test008_dose_actor.py +++ b/opengate/tests/src/test008_dose_actor.py @@ -99,16 +99,28 @@ print("\nDifference for EDEP") is_ok = ( - utility.assert_images(ref_path / "output-Edep.mhd", dose.edep.get_output_path(), stat, tolerance=13, - ignore_value_data2=0, sum_tolerance=1.5) - and is_ok + utility.assert_images( + ref_path / "output-Edep.mhd", + dose.edep.get_output_path(), + stat, + tolerance=13, + ignore_value_data2=0, + sum_tolerance=1.5, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(ref_path / "output-Edep-Uncertainty.mhd", dose.edep_uncertainty.get_output_path(), - stat, tolerance=30, ignore_value_data2=0, sum_tolerance=3) - and is_ok + utility.assert_images( + ref_path / "output-Edep-Uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), + stat, + tolerance=30, + ignore_value_data2=0, + sum_tolerance=3, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source_angular_distribution.py b/opengate/tests/src/test010_generic_source_angular_distribution.py index 5a6e16e5c..01ba93f9a 100755 --- a/opengate/tests/src/test010_generic_source_angular_distribution.py +++ b/opengate/tests/src/test010_generic_source_angular_distribution.py @@ -94,19 +94,29 @@ print("\nDifference for EDEP") is_ok = ( - utility.assert_images(ref_path / "test010-generic_source_angular_distribution_edep_ref.mhd", - dose.edep.get_output_path(), stat, tolerance=13, ignore_value_data2=0, - sum_tolerance=1) - and is_ok + utility.assert_images( + ref_path / "test010-generic_source_angular_distribution_edep_ref.mhd", + dose.edep.get_output_path(), + stat, + tolerance=13, + ignore_value_data2=0, + sum_tolerance=1, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(ref_path - / "test010-generic_source_angular_distribution_edep_uncertainty_ref.mhd", - dose.edep_uncertainty.get_output_path(), stat, tolerance=30, ignore_value_data2=0, - sum_tolerance=1) - and is_ok + utility.assert_images( + ref_path + / "test010-generic_source_angular_distribution_edep_uncertainty_ref.mhd", + dose.edep_uncertainty.get_output_path(), + stat, + tolerance=30, + ignore_value_data2=0, + sum_tolerance=1, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source_confine.py b/opengate/tests/src/test010_generic_source_confine.py index 641974d91..489dbc480 100755 --- a/opengate/tests/src/test010_generic_source_confine.py +++ b/opengate/tests/src/test010_generic_source_confine.py @@ -122,7 +122,11 @@ # tests stats_ref = utility.read_stat_file(paths.output_ref / "test010_confine_stats.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.10) - is_ok = is_ok and utility.assert_images(paths.output_ref / "test010-2-edep.mhd", dose_actor.edep.get_output_path(), - stats, tolerance=59) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test010-2-edep.mhd", + dose_actor.edep.get_output_path(), + stats, + tolerance=59, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test012_mt_dose_actor.py b/opengate/tests/src/test012_mt_dose_actor.py index 78a28ac8b..c904ab688 100755 --- a/opengate/tests/src/test012_mt_dose_actor.py +++ b/opengate/tests/src/test012_mt_dose_actor.py @@ -103,8 +103,12 @@ is_ok = utility.assert_stats(stat, stats_ref, 0.10) is_ok = ( - utility.assert_images(paths.gate_output / "output-Edep.mhd", dose_actor.edep.get_output_path(), stat, - tolerance=45) - and is_ok + utility.assert_images( + paths.gate_output / "output-Edep.mhd", + dose_actor.edep.get_output_path(), + stat, + tolerance=45, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_2.py b/opengate/tests/src/test015_iec_phantom_2.py index 8b1639d56..e33cfc158 100755 --- a/opengate/tests/src/test015_iec_phantom_2.py +++ b/opengate/tests/src/test015_iec_phantom_2.py @@ -72,8 +72,15 @@ is_ok = utility.assert_stats(stats_actor, stats_ref, tolerance=0.03) # compare images - im_ok = utility.assert_images(paths.output_ref / "test015_iec_2.mhd", dose.edep.get_output_path(), stats_actor, - tolerance=40, ignore_value_data2=0, axis="x", sum_tolerance=2) + im_ok = utility.assert_images( + paths.output_ref / "test015_iec_2.mhd", + dose.edep.get_output_path(), + stats_actor, + tolerance=40, + ignore_value_data2=0, + axis="x", + sum_tolerance=2, + ) is_ok = is_ok and im_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_3_mt.py b/opengate/tests/src/test015_iec_phantom_3_mt.py index ad66251d7..a1b1b4965 100755 --- a/opengate/tests/src/test015_iec_phantom_3_mt.py +++ b/opengate/tests/src/test015_iec_phantom_3_mt.py @@ -82,7 +82,7 @@ ignore_value_data2=0, axis="y", sum_tolerance=0.5, - sad_profile_tolerance=2 + sad_profile_tolerance=2, ) is_ok = is_ok and im_ok diff --git a/opengate/tests/src/test015_iec_phantom_5_wip.py b/opengate/tests/src/test015_iec_phantom_5_wip.py index 55cdb3b02..e24bc89d1 100755 --- a/opengate/tests/src/test015_iec_phantom_5_wip.py +++ b/opengate/tests/src/test015_iec_phantom_5_wip.py @@ -79,8 +79,15 @@ # compare images f = paths.output / "test015_iec_2.mhd" - im_ok = utility.assert_images(paths.output_ref / "test015_iec_2.mhd", dose.output, stats, tolerance=40, - ignore_value_data2=0, axis="x", sum_tolerance=2) + im_ok = utility.assert_images( + paths.output_ref / "test015_iec_2.mhd", + dose.output, + stats, + tolerance=40, + ignore_value_data2=0, + axis="x", + sum_tolerance=2, + ) is_ok = is_ok and im_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test017_repeater.py b/opengate/tests/src/test017_repeater.py index 59a8ec250..01c30aa49 100755 --- a/opengate/tests/src/test017_repeater.py +++ b/opengate/tests/src/test017_repeater.py @@ -101,9 +101,14 @@ stats_ref = utility.read_stat_file(paths.output_ref / "test017-stats-ref.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.04) is_ok = ( - utility.assert_images(paths.output_ref / "test017-edep-ref.mhd", dose.edep.get_output_path(), stats, - tolerance=70, sum_tolerance=6) - and is_ok + utility.assert_images( + paths.output_ref / "test017-edep-ref.mhd", + dose.edep.get_output_path(), + stats, + tolerance=70, + sum_tolerance=6, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test020_profiling.py b/opengate/tests/src/test020_profiling.py index af4b5e544..8574b60fa 100755 --- a/opengate/tests/src/test020_profiling.py +++ b/opengate/tests/src/test020_profiling.py @@ -100,6 +100,10 @@ stats_ref = utility.read_stat_file(paths.gate / "output" / "stat_profiling.txt") stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, 0.1) - is_ok = is_ok and utility.assert_images(paths.gate / "output" / "output_profiling-Edep.mhd", - dose.edep.get_output_path(), stats, tolerance=79) + is_ok = is_ok and utility.assert_images( + paths.gate / "output" / "output_profiling-Edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=79, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test021_voxel_source2.py b/opengate/tests/src/test021_voxel_source2.py index f6bb050a9..aabc3131c 100755 --- a/opengate/tests/src/test021_voxel_source2.py +++ b/opengate/tests/src/test021_voxel_source2.py @@ -147,9 +147,15 @@ is_ok = utility.assert_stats(stats, stats_ref, 0.1) and is_ok is_ok = ( - utility.assert_images(paths.output_ref / "test021-edep_2.mhd", dose.edep.get_output_path(), stats, - tolerance=11, ignore_value_data2=0, axis="y") - and is_ok + utility.assert_images( + paths.output_ref / "test021-edep_2.mhd", + dose.edep.get_output_path(), + stats, + tolerance=11, + ignore_value_data2=0, + axis="y", + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters.py b/opengate/tests/src/test023_filters.py index fd4e3cb84..62b8e8cd8 100755 --- a/opengate/tests/src/test023_filters.py +++ b/opengate/tests/src/test023_filters.py @@ -101,11 +101,21 @@ is_ok = utility.assert_stats(stat, stats_ref, 0.8) print() - is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-edep.mhd", dose1.edep.get_output_path(), stat, - tolerance=50, sum_tolerance=4) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test023-edep.mhd", + dose1.edep.get_output_path(), + stat, + tolerance=50, + sum_tolerance=4, + ) print() - is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-noe-edep.mhd", dose2.edep.get_output_path(), - stat, tolerance=40, sum_tolerance=2) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test023-noe-edep.mhd", + dose2.edep.get_output_path(), + stat, + tolerance=40, + sum_tolerance=2, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_iec_phantom.py b/opengate/tests/src/test023_filters_iec_phantom.py index 9a6dc20fc..9d4298d46 100755 --- a/opengate/tests/src/test023_filters_iec_phantom.py +++ b/opengate/tests/src/test023_filters_iec_phantom.py @@ -81,7 +81,12 @@ f = paths.output_ref / "test023_stats_iec_phantom.txt" stats_ref = utility.read_stat_file(f) is_ok = utility.assert_stats(stat, stats_ref, 0.12) - is_ok = is_ok and utility.assert_images(paths.output_ref / "test023_iec_phantom.mhd", dose.edep.get_output_path(), - stat, tolerance=102, sum_tolerance=28) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test023_iec_phantom.mhd", + dose.edep.get_output_path(), + stat, + tolerance=102, + sum_tolerance=28, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_material.py b/opengate/tests/src/test023_filters_material.py index 4863d4316..fbd427acd 100755 --- a/opengate/tests/src/test023_filters_material.py +++ b/opengate/tests/src/test023_filters_material.py @@ -99,7 +99,12 @@ stats_ref = utility.read_stat_file(f2) is_ok = utility.assert_stats(stat2, stats_ref, 0.07) and is_ok - is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-edep.mhd", dose.edep.get_output_path(), stat, - tolerance=50, sum_tolerance=3) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test023-edep.mhd", + dose.edep.get_output_path(), + stat, + tolerance=50, + sum_tolerance=3, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py index d0cd5b918..203d7c87d 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py @@ -350,10 +350,17 @@ def test_spect_proj(sim, paths, proj, version="3"): img.SetOrigin(origin) itk.imwrite(img, str(paths.output / "proj028_offset.mhd")) is_ok = ( - utility.assert_images(paths.gate_output / f"projection{version}.mhd", paths.output / "proj028_offset.mhd", - stats, tolerance=16, ignore_value_data2=0, axis="y", - fig_name=paths.output / f"proj028_{version}_offset.png", sum_tolerance=1.6) - and is_ok + utility.assert_images( + paths.gate_output / f"projection{version}.mhd", + paths.output / "proj028_offset.mhd", + stats, + tolerance=16, + ignore_value_data2=0, + axis="y", + fig_name=paths.output / f"proj028_{version}_offset.png", + sum_tolerance=1.6, + ) + and is_ok ) # compare images with Gate @@ -363,10 +370,17 @@ def test_spect_proj(sim, paths, proj, version="3"): print("Compare images (new spacing/origin") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images(paths.output_ref / "proj028_ref.mhd", paths.output / "proj028.mhd", stats, - tolerance=14, ignore_value_data2=0, axis="y", - fig_name=paths.output / f"proj028_{version}_no_offset.png", sum_tolerance=1.5) - and is_ok + utility.assert_images( + paths.output_ref / "proj028_ref.mhd", + paths.output / "proj028.mhd", + stats, + tolerance=14, + ignore_value_data2=0, + axis="y", + fig_name=paths.output / f"proj028_{version}_no_offset.png", + sum_tolerance=1.5, + ) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py index 5f618f829..276c81fd4 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py @@ -260,10 +260,17 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8): itk.imwrite(img, str(paths.output / "proj028_colli_offset.mhd")) # There are not enough event to make a proper comparison, so the tol is very high is_ok = ( - utility.assert_images(paths.gate_output / "projection4.mhd", paths.output / "proj028_colli_offset.mhd", - stats, tolerance=85, ignore_value_data2=0, axis="x", - fig_name=str(paths.output / fig_name), sum_tolerance=sum_tolerance) - and is_ok + utility.assert_images( + paths.gate_output / "projection4.mhd", + paths.output / "proj028_colli_offset.mhd", + stats, + tolerance=85, + ignore_value_data2=0, + axis="x", + fig_name=str(paths.output / fig_name), + sum_tolerance=sum_tolerance, + ) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test029_volume_time_rotation_1.py b/opengate/tests/src/test029_volume_time_rotation_1.py index b995b829e..c767bf0b7 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1.py +++ b/opengate/tests/src/test029_volume_time_rotation_1.py @@ -47,9 +47,16 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images(paths.output_ref / "proj029.mhd", proj_actor.get_output_path(), stats, tolerance=59, - ignore_value_data2=0, axis="x", sum_tolerance=2) - and is_ok + utility.assert_images( + paths.output_ref / "proj029.mhd", + proj_actor.get_output_path(), + stats, + tolerance=59, + ignore_value_data2=0, + axis="x", + sum_tolerance=2, + ) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_1_process.py b/opengate/tests/src/test029_volume_time_rotation_1_process.py index da3fdb50e..1c12a908a 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1_process.py +++ b/opengate/tests/src/test029_volume_time_rotation_1_process.py @@ -37,9 +37,16 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images(paths.output_ref / "proj029.mhd", proj_actor.get_output_path(), stats, tolerance=59, - ignore_value_data2=0, axis="x", sum_tolerance=2) - and is_ok + utility.assert_images( + paths.output_ref / "proj029.mhd", + proj_actor.get_output_path(), + stats, + tolerance=59, + ignore_value_data2=0, + axis="x", + sum_tolerance=2, + ) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_2.py b/opengate/tests/src/test029_volume_time_rotation_2.py index 18ec12457..a3fe22254 100755 --- a/opengate/tests/src/test029_volume_time_rotation_2.py +++ b/opengate/tests/src/test029_volume_time_rotation_2.py @@ -47,9 +47,16 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images(paths.output_ref / "proj029_scaled.mhd", proj_actor.get_output_path(), stats, - tolerance=60, ignore_value_data2=0, axis="x", sum_tolerance=6) - and is_ok + utility.assert_images( + paths.output_ref / "proj029_scaled.mhd", + proj_actor.get_output_path(), + stats, + tolerance=60, + ignore_value_data2=0, + axis="x", + sum_tolerance=6, + ) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test030_dose_motion.py b/opengate/tests/src/test030_dose_motion.py index 71f47a51e..4316e5dcb 100755 --- a/opengate/tests/src/test030_dose_motion.py +++ b/opengate/tests/src/test030_dose_motion.py @@ -112,16 +112,25 @@ gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, - tolerance=30, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=30, + ignore_value_data2=0, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), stats, tolerance=15, - ignore_value_data2=0) + utility.assert_images( + paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), + stats, + tolerance=15, + ignore_value_data2=0, + ) ) and is_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param.py b/opengate/tests/src/test030_dose_motion_dynamic_param.py index 5dfb1728d..123a78fb7 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param.py @@ -108,16 +108,26 @@ print() gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, - tolerance=30, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=30, + ignore_value_data2=0, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), stats, tolerance=15, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), + stats, + tolerance=15, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py index 4f579b241..a3018febf 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py @@ -185,16 +185,26 @@ def apply_change(self, run_id): print() gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, - tolerance=30, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=30, + ignore_value_data2=0, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), stats, tolerance=15, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), + stats, + tolerance=15, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_create_voxelized_volume.py b/opengate/tests/src/test032_create_voxelized_volume.py index 6dced7fad..47b74ec35 100755 --- a/opengate/tests/src/test032_create_voxelized_volume.py +++ b/opengate/tests/src/test032_create_voxelized_volume.py @@ -64,8 +64,13 @@ # compare images gate.exception.warning("\nDifference with ref image") is_ok = ( - utility.assert_images(paths.output_ref / "test032_iec.mhd", path_to_image_1mm, stats=None, tolerance=0.01) - and is_ok + utility.assert_images( + paths.output_ref / "test032_iec.mhd", + path_to_image_1mm, + stats=None, + tolerance=0.01, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_voxel_vs_volume.py b/opengate/tests/src/test032_voxel_vs_volume.py index facd4fb2b..7e1f55af1 100755 --- a/opengate/tests/src/test032_voxel_vs_volume.py +++ b/opengate/tests/src/test032_voxel_vs_volume.py @@ -141,7 +141,12 @@ dose2 = sim.get_actor("dose2") # compare edep map - is_ok = utility.assert_images(dose1.edep.get_output_path(), dose2.edep.get_output_path(), stats, tolerance=87, - axis="x") + is_ok = utility.assert_images( + dose1.edep.get_output_path(), + dose2.edep.get_output_path(), + stats, + tolerance=87, + axis="x", + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_voxelized_volume_source.py b/opengate/tests/src/test032_voxelized_volume_source.py index 4f7c0518f..292b6c8d3 100755 --- a/opengate/tests/src/test032_voxelized_volume_source.py +++ b/opengate/tests/src/test032_voxelized_volume_source.py @@ -49,19 +49,27 @@ # compare images gate.exception.warning("\nDifference with ref image") is_ok = ( - utility.assert_images(paths.output_ref / "iec_10mm.mhd", f1, stats=None, tolerance=0.001) - and is_ok + utility.assert_images( + paths.output_ref / "iec_10mm.mhd", f1, stats=None, tolerance=0.001 + ) + and is_ok ) is_ok = ( - utility.assert_images(paths.output_ref / "iec_source_10mm.mhd", f2, stats=None, tolerance=0.001) - and is_ok + utility.assert_images( + paths.output_ref / "iec_source_10mm.mhd", f2, stats=None, tolerance=0.001 + ) + and is_ok ) is_ok = ( - utility.assert_images(paths.output_ref / "iec_9mm.mhd", f3, stats=None, tolerance=0.001) - and is_ok + utility.assert_images( + paths.output_ref / "iec_9mm.mhd", f3, stats=None, tolerance=0.001 + ) + and is_ok ) is_ok = ( - utility.assert_images(paths.output_ref / "iec_source_9mm.mhd", f4, stats=None, tolerance=0.001) - and is_ok + utility.assert_images( + paths.output_ref / "iec_source_9mm.mhd", f4, stats=None, tolerance=0.001 + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index 29a644a17..582aa340a 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -180,14 +180,26 @@ def evaluate_test(sim, sources, itol, ref_skipped): # compare edep map gate.exception.warning(f"Check images") is_ok = ( - utility.assert_images(paths.output_ref / "test033_proj_1.mhd", paths.output / "test033_proj_1.mhd", stats, - tolerance=68, axis="x", sum_tolerance=itol) - and is_ok + utility.assert_images( + paths.output_ref / "test033_proj_1.mhd", + paths.output / "test033_proj_1.mhd", + stats, + tolerance=68, + axis="x", + sum_tolerance=itol, + ) + and is_ok ) is_ok = ( - utility.assert_images(paths.output_ref / "test033_proj_2.mhd", paths.output / "test033_proj_2.mhd", stats, - tolerance=68, axis="x", sum_tolerance=itol) - and is_ok + utility.assert_images( + paths.output_ref / "test033_proj_2.mhd", + paths.output / "test033_proj_2.mhd", + stats, + tolerance=68, + axis="x", + sum_tolerance=itol, + ) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test034_gan_phsp_linac.py b/opengate/tests/src/test034_gan_phsp_linac.py index c17240b69..fde867d1a 100755 --- a/opengate/tests/src/test034_gan_phsp_linac.py +++ b/opengate/tests/src/test034_gan_phsp_linac.py @@ -119,9 +119,14 @@ gate.exception.warning(f"Check dose") is_ok = ( - utility.assert_images(paths.gate / "dose-Edep.mhd", dose.edep.get_output_path(), stats, tolerance=58, - ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate / "dose-Edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=58, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test035_dose_rate.py b/opengate/tests/src/test035_dose_rate.py index 8be62b0cf..dc3aea578 100755 --- a/opengate/tests/src/test035_dose_rate.py +++ b/opengate/tests/src/test035_dose_rate.py @@ -58,9 +58,14 @@ h = sim.get_actor("dose") print(h) is_ok = ( - utility.assert_images(paths.output_ref / "edep.mhd", h.edep.get_output_path(), stats, tolerance=15, - ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "edep.mhd", + h.edep.get_output_path(), + stats, + tolerance=15, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test036_proj_param_1.py b/opengate/tests/src/test036_proj_param_1.py index c15403cbe..40c733633 100755 --- a/opengate/tests/src/test036_proj_param_1.py +++ b/opengate/tests/src/test036_proj_param_1.py @@ -44,8 +44,15 @@ # test the output stats = sim.get_actor("Stats") - is_ok = utility.assert_images(paths.output_ref / proj.output_filename, proj.get_output_path(), stats, tolerance=38, - ignore_value_data2=0, axis="y", fig_name=paths.output / f"proj.png", - sum_tolerance=1.5) + is_ok = utility.assert_images( + paths.output_ref / proj.output_filename, + proj.get_output_path(), + stats, + tolerance=38, + ignore_value_data2=0, + axis="y", + fig_name=paths.output / f"proj.png", + sum_tolerance=1.5, + ) utility.print_test(is_ok, f"Compare image proj:") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test036_proj_param_2_with_index.py b/opengate/tests/src/test036_proj_param_2_with_index.py index 5b8bda5dd..d5b6589e7 100755 --- a/opengate/tests/src/test036_proj_param_2_with_index.py +++ b/opengate/tests/src/test036_proj_param_2_with_index.py @@ -31,8 +31,15 @@ # test the output stats = sim.get_actor("Stats") - is_ok = utility.assert_images(paths.output_ref / "proj1.mha", proj.get_output_path(), stats, tolerance=38, - ignore_value_data2=0, axis="y", fig_name=paths.output / f"proj_index.png", - sum_tolerance=1.5) + is_ok = utility.assert_images( + paths.output_ref / "proj1.mha", + proj.get_output_path(), + stats, + tolerance=38, + ignore_value_data2=0, + axis="y", + fig_name=paths.output / f"proj_index.png", + sum_tolerance=1.5, + ) utility.print_test(is_ok, f"Compare image proj:") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py index e641f9436..9185fe78c 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py @@ -64,10 +64,15 @@ # image is_ok = ( - utility.assert_images(paths.output_ref / "test038_gan_aa_proj.mhd", - paths.output / "test038_gan_aa_proj.mhd", stats, tolerance=70, axis="x", - sum_tolerance=2.75) - and is_ok + utility.assert_images( + paths.output_ref / "test038_gan_aa_proj.mhd", + paths.output / "test038_gan_aa_proj.mhd", + stats, + tolerance=70, + axis="x", + sum_tolerance=2.75, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test041_dose_actor.py b/opengate/tests/src/test041_dose_actor.py index 655ec84a1..8c0c048f2 100755 --- a/opengate/tests/src/test041_dose_actor.py +++ b/opengate/tests/src/test041_dose_actor.py @@ -102,24 +102,38 @@ gate.exception.warning("\nDifference for EDEP") is_ok = ( - utility.assert_images(paths.gate_output / "output2-Edep.mhd", dose_actor.edep.get_output_path(), stats, - tolerance=10, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate_output / "output2-Edep.mhd", + dose_actor.edep.get_output_path(), + stats, + tolerance=10, + ignore_value_data2=0, + ) + and is_ok ) gate.exception.warning("\nDifference for uncertainty") is_ok = ( - utility.assert_images(paths.gate_output / "output2-Edep-Uncertainty.mhd", - dose_actor.edep_uncertainty.get_output_path(), stats, tolerance=30, - ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate_output / "output2-Edep-Uncertainty.mhd", + dose_actor.edep_uncertainty.get_output_path(), + stats, + tolerance=30, + ignore_value_data2=0, + ) + and is_ok ) gate.exception.warning("\nDifference for dose in Gray") is_ok = ( - utility.assert_images(paths.gate_output / "output2-Dose.mhd", dose_actor.dose.get_output_path(), stats, - tolerance=10, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate_output / "output2-Dose.mhd", + dose_actor.dose.get_output_path(), + stats, + tolerance=10, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py index 52a545618..0587b3fd6 100755 --- a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py +++ b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py @@ -150,8 +150,14 @@ # so we do not need to manually keep track of the paths here in the script # syntax: dose_actor.dose.get_output_path() - unused = utility.assert_images(dose_actor_IDD_d.dose.get_output_path(), dose_actor_IDD_d2w.dose.get_output_path(), - stats, tolerance=100, ignore_value_data2=0, axis="x") + unused = utility.assert_images( + dose_actor_IDD_d.dose.get_output_path(), + dose_actor_IDD_d2w.dose.get_output_path(), + stats, + tolerance=100, + ignore_value_data2=0, + axis="x", + ) mSPR_40MeV = 1.268771331 # from PSTAR NIST tables, Feb 2023 mSPR_80MeV = 1.253197674 # from PSTAR NIST tables, Feb 2023 diff --git a/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py b/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py index be14e3015..d4a9ea6d4 100755 --- a/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py +++ b/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py @@ -107,8 +107,14 @@ def run_sim(N_events: int, N_threads: int, N_voxels: int, paths): def run_test(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stat): - unused = utility.assert_images(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stat, tolerance=100, - ignore_value_data2=0, axis="x") + unused = utility.assert_images( + doseFpath_IDD_singleImage, + doseFpath_IDD_NthreadImages, + stat, + tolerance=100, + ignore_value_data2=0, + axis="x", + ) expected_ratio = 1.00 gate.exception.warning("Test ratio: edep in single image vs. Nthread image") is_ok = utility.assert_images_ratio( diff --git a/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py b/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py index 664cda904..a7be94324 100755 --- a/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py +++ b/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py @@ -127,8 +127,14 @@ def run_sim(n_thr, c4_ref=None, paths=None): sim.get_actor(doseActorName_IDD_singleImage).get_output_path("edep_uncertainty") ) - unused = utility.assert_images(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stats, tolerance=100, - ignore_value_data2=0, axis="x") + unused = utility.assert_images( + doseFpath_IDD_singleImage, + doseFpath_IDD_NthreadImages, + stats, + tolerance=100, + ignore_value_data2=0, + axis="x", + ) expected_ratio = 1.00 gate.exception.warning("Test ratio: dose / dose MT cp image for each trhead") is_ok = utility.assert_images_ratio( diff --git a/opengate/tests/src/test042_gauss_gps.py b/opengate/tests/src/test042_gauss_gps.py index 35d15c152..eb975732a 100755 --- a/opengate/tests/src/test042_gauss_gps.py +++ b/opengate/tests/src/test042_gauss_gps.py @@ -124,28 +124,42 @@ print() gate.exception.warning("Difference for EDEP XZ") is_ok = ( - utility.assert_images(paths.gate_output / "lateral_xz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInXZ").get_output_path("edep"), stats, tolerance=10, - ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate_output / "lateral_xz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInXZ").get_output_path("edep"), + stats, + tolerance=10, + ignore_value_data2=0, + ) + and is_ok ) print() gate.exception.warning("Difference for EDEP XY") is_ok = ( - utility.assert_images(paths.gate_output / "lateral_xy_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInXY").get_output_path("edep"), stats, tolerance=10, - ignore_value_data2=0, axis="y") - and is_ok + utility.assert_images( + paths.gate_output / "lateral_xy_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInXY").get_output_path("edep"), + stats, + tolerance=10, + ignore_value_data2=0, + axis="y", + ) + and is_ok ) print() gate.exception.warning("Difference for EDEP YZ") is_ok = ( - utility.assert_images(paths.gate_output / "lateral_yz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInYZ").get_output_path("edep"), stats, tolerance=30, - ignore_value_data2=0, axis="y") - and is_ok + utility.assert_images( + paths.gate_output / "lateral_yz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInYZ").get_output_path("edep"), + stats, + tolerance=30, + ignore_value_data2=0, + axis="y", + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test043_garf.py b/opengate/tests/src/test043_garf.py index e920fb817..48bc5a233 100755 --- a/opengate/tests/src/test043_garf.py +++ b/opengate/tests/src/test043_garf.py @@ -127,17 +127,30 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", filename1, stat, - tolerance=100, ignore_value_data2=0, axis="x", sum_tolerance=20) - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog.mhd", + filename1, + stat, + tolerance=100, + ignore_value_data2=0, + axis="x", + sum_tolerance=20, + ) + and is_ok ) print() gate.exception.warning("Compare image to analog high statistics") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", filename2, stat, - tolerance=52, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", + filename2, + stat, + tolerance=52, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) print() diff --git a/opengate/tests/src/test043_garf_analog.py b/opengate/tests/src/test043_garf_analog.py index d1563e794..a068a705f 100755 --- a/opengate/tests/src/test043_garf_analog.py +++ b/opengate/tests/src/test043_garf_analog.py @@ -85,15 +85,27 @@ print() gate.exception.warning("Tests projection (old gate)") is_ok = ( - utility.assert_images(test43.paths.gate_output / "projection_analog.mhd", fn, stats, tolerance=75, - ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + test43.paths.gate_output / "projection_analog.mhd", + fn, + stats, + tolerance=75, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) print() gate.exception.warning("Tests projection (new)") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", proj.get_output_path(), - stats, tolerance=80, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog.mhd", + proj.get_output_path(), + stats, + tolerance=80, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) diff --git a/opengate/tests/src/test043_garf_mt.py b/opengate/tests/src/test043_garf_mt.py index 9c2b02eae..f1f4f9f00 100755 --- a/opengate/tests/src/test043_garf_mt.py +++ b/opengate/tests/src/test043_garf_mt.py @@ -115,17 +115,30 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", filename1, stats, - tolerance=100, ignore_value_data2=0, axis="x", sum_tolerance=20) - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog.mhd", + filename1, + stats, + tolerance=100, + ignore_value_data2=0, + axis="x", + sum_tolerance=20, + ) + and is_ok ) print() gate.exception.warning("Compare image to analog high statistics") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", filename2, stats, - tolerance=52, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", + filename2, + stats, + tolerance=52, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) print() diff --git a/opengate/tests/src/test044_pbs.py b/opengate/tests/src/test044_pbs.py index f662a06d0..785411e1b 100755 --- a/opengate/tests/src/test044_pbs.py +++ b/opengate/tests/src/test044_pbs.py @@ -159,8 +159,10 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images(ref_path / mhd_ref, mhd_gate, stats, tolerance=50, ignore_value_data2=0) - and is_ok + utility.assert_images( + ref_path / mhd_ref, mhd_gate, stats, tolerance=50, ignore_value_data2=0 + ) + and is_ok ) """EdepColorMap = utility.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = 'Plane_'+str(i)+'ColorMap.png' diff --git a/opengate/tests/src/test044_pbs_rot_transl.py b/opengate/tests/src/test044_pbs_rot_transl.py index f516800eb..2bb616d3d 100755 --- a/opengate/tests/src/test044_pbs_rot_transl.py +++ b/opengate/tests/src/test044_pbs_rot_transl.py @@ -170,9 +170,15 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).edep.get_output_path() mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images(ref_path / mhd_ref, output_path / mhd_gate, stat, tolerance=50, - ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + ref_path / mhd_ref, + output_path / mhd_gate, + stat, + tolerance=50, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) """ diff --git a/opengate/tests/src/test044_pbs_source_to_volume.py b/opengate/tests/src/test044_pbs_source_to_volume.py index e18a5c283..ed953d1d3 100755 --- a/opengate/tests/src/test044_pbs_source_to_volume.py +++ b/opengate/tests/src/test044_pbs_source_to_volume.py @@ -162,8 +162,10 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images(ref_path / mhd_ref, mhd_gate, stat, tolerance=50, ignore_value_data2=0) - and is_ok + utility.assert_images( + ref_path / mhd_ref, mhd_gate, stat, tolerance=50, ignore_value_data2=0 + ) + and is_ok ) """EdepColorMap = utility.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = "Plane_" + str(i) + "ColorMap.png" diff --git a/opengate/tests/src/test044_pbs_unfocused.py b/opengate/tests/src/test044_pbs_unfocused.py index ee58e3d0e..33077ca6c 100755 --- a/opengate/tests/src/test044_pbs_unfocused.py +++ b/opengate/tests/src/test044_pbs_unfocused.py @@ -172,9 +172,14 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images(ref_path / mhd_ref, output_path / mhd_gate, stats, tolerance=50, - ignore_value_data2=0) - and is_ok + utility.assert_images( + ref_path / mhd_ref, + output_path / mhd_gate, + stats, + tolerance=50, + ignore_value_data2=0, + ) + and is_ok ) """EdepColorMap = utlity.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = 'Plane_'+str(i)+'ColorMap.png' diff --git a/opengate/tests/src/test047_gan_vox_source_cond.py b/opengate/tests/src/test047_gan_vox_source_cond.py index c6c49eb18..9a71075e3 100755 --- a/opengate/tests/src/test047_gan_vox_source_cond.py +++ b/opengate/tests/src/test047_gan_vox_source_cond.py @@ -140,9 +140,15 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images(paths.output_ref / "test047-edep.mhd", dose.get_output_path("edep"), stats, - tolerance=19, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + paths.output_ref / "test047-edep.mhd", + dose.get_output_path("edep"), + stats, + tolerance=19, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) print("Test with vv: ") diff --git a/opengate/tests/src/test048_split_spect_projections.py b/opengate/tests/src/test048_split_spect_projections.py index c496523fe..60e23132f 100755 --- a/opengate/tests/src/test048_split_spect_projections.py +++ b/opengate/tests/src/test048_split_spect_projections.py @@ -40,9 +40,15 @@ is_ok = True for i in range(nb_ene): is_ok = ( - utility.assert_images(paths.output_ref / f"t048_projection_{i}.mhd", output_filenames[i], None, - tolerance=1e-6, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + paths.output_ref / f"t048_projection_{i}.mhd", + output_filenames[i], + None, + tolerance=1e-6, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test050_let_actor_letd_mt.py b/opengate/tests/src/test050_let_actor_letd_mt.py index 20bde9437..9302e1265 100755 --- a/opengate/tests/src/test050_let_actor_letd_mt.py +++ b/opengate/tests/src/test050_let_actor_letd_mt.py @@ -165,9 +165,15 @@ fNameIDD = doseIDD.edep.output_filename if do_debug: - is_ok = utility.assert_images(ref_path / fNameIDD, doseIDD.edep.get_output_path(), stats, tolerance=100, - ignore_value_data2=0, axis="x", - scaleImageValuesFactor=numPartSimRef / numPartSimTest) + is_ok = utility.assert_images( + ref_path / fNameIDD, + doseIDD.edep.get_output_path(), + stats, + tolerance=100, + ignore_value_data2=0, + axis="x", + scaleImageValuesFactor=numPartSimRef / numPartSimTest, + ) tests_pass = [] is_ok = utility.assert_filtered_imagesprofile1D( diff --git a/opengate/tests/src/test059_tpsource_flat_generation_flag.py b/opengate/tests/src/test059_tpsource_flat_generation_flag.py index 2dd2ac789..8cbd3b479 100755 --- a/opengate/tests/src/test059_tpsource_flat_generation_flag.py +++ b/opengate/tests/src/test059_tpsource_flat_generation_flag.py @@ -239,18 +239,27 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): # check that the dose output is the same print("--- Dose image spot 0 ---") test = ( - utility.assert_images(output_path / dose_actors[0].get_output_path("edep"), - output_path / dose_actors[2].get_output_path("edep"), stats, tolerance=70, - ignore_value_data2=0) - and test + utility.assert_images( + output_path / dose_actors[0].get_output_path("edep"), + output_path / dose_actors[2].get_output_path("edep"), + stats, + tolerance=70, + ignore_value_data2=0, + ) + and test ) print("--- Dose image spot 1 ---") test = ( - utility.assert_images(output_path / dose_actors[1].get_output_path("edep"), - output_path / dose_actors[3].get_output_path("edep"), stats, tolerance=70, - ignore_value_data2=0, sum_tolerance=5.2) - and test + utility.assert_images( + output_path / dose_actors[1].get_output_path("edep"), + output_path / dose_actors[3].get_output_path("edep"), + stats, + tolerance=70, + ignore_value_data2=0, + sum_tolerance=5.2, + ) + and test ) # check that output with flat distribution has better statistics for the spot with less particles diff --git a/opengate/tests/src/test059_tpsource_weights.py b/opengate/tests/src/test059_tpsource_weights.py index 8f1680341..4a22bd685 100755 --- a/opengate/tests/src/test059_tpsource_weights.py +++ b/opengate/tests/src/test059_tpsource_weights.py @@ -161,14 +161,26 @@ # check first spot test = ( - utility.assert_images(ref_path / mhd_1, output_path / mhd_1, stats, tolerance=70, ignore_value_data2=0) - and test + utility.assert_images( + ref_path / mhd_1, + output_path / mhd_1, + stats, + tolerance=70, + ignore_value_data2=0, + ) + and test ) # check second spot test = ( - utility.assert_images(ref_path / mhd_1, output_path / mhd_1, stats, tolerance=70, ignore_value_data2=0) - and test + utility.assert_images( + ref_path / mhd_1, + output_path / mhd_1, + stats, + tolerance=70, + ignore_value_data2=0, + ) + and test ) print(" --------------------------------------- ") # fig1 = utility.create_2D_Edep_colorMap(output_path / mhd_1, show=True) diff --git a/opengate/tests/src/test067_cbct_fluence_actor_mt.py b/opengate/tests/src/test067_cbct_fluence_actor_mt.py index abe24c73b..5b7b470a7 100755 --- a/opengate/tests/src/test067_cbct_fluence_actor_mt.py +++ b/opengate/tests/src/test067_cbct_fluence_actor_mt.py @@ -92,6 +92,8 @@ out_path = detector_actor.get_output_path() # check images - is_ok = utility.assert_images(paths.gate_output / "detector.mhd", out_path, stats, tolerance=44, axis="y") + is_ok = utility.assert_images( + paths.gate_output / "detector.mhd", out_path, stats, tolerance=44, axis="y" + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test073_helpers.py b/opengate/tests/src/test073_helpers.py index 0ea9191af..48ec93e8b 100644 --- a/opengate/tests/src/test073_helpers.py +++ b/opengate/tests/src/test073_helpers.py @@ -170,8 +170,16 @@ def compare_proj_images(crystal, sim, stats, image_filename, path, n=1): img.SetOrigin(origin) itk.imwrite(img, f2) - is_ok = utility.assert_images(fr, f2, stats, tolerance=69, ignore_value_data2=0, axis="y", - fig_name=path / f"test073_test_{n}.png", sum_tolerance=6) + is_ok = utility.assert_images( + fr, + f2, + stats, + tolerance=69, + ignore_value_data2=0, + axis="y", + fig_name=path / f"test073_test_{n}.png", + sum_tolerance=6, + ) return is_ok diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index 7ad1d280d..d4b29d487 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -336,7 +336,9 @@ def assert_images( d2 = data2 else: if ignore_value_data1 is not None and ignore_value_data2 is not None: - mask = np.logical_or(data1 != ignore_value_data1, data2 != ignore_value_data2) + mask = np.logical_or( + data1 != ignore_value_data1, data2 != ignore_value_data2 + ) elif ignore_value_data1 is not None: mask = data1 != ignore_value_data1 else: From 97b2fce03efb38934611db866d155e2ce934c6da Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Tue, 8 Oct 2024 19:03:48 +0200 Subject: [PATCH 042/183] Added option to pass and specify the number of processes in multiprocessing --- opengate/bin/opengate_tests.py | 44 +++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index b068de1f8..c27a8cc44 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -29,6 +29,7 @@ @click.command(context_settings=CONTEXT_SETTINGS) @click.option("--start_id", "-i", default="all", help="Start test from this number") +@click.option("--end_id", "-e", default="all", help="Start test up to this number") @click.option( "--no_log_on_fail", default=False, @@ -48,6 +49,12 @@ default="mp", help="Start simulations in single process mode: 'legacy', 'sp' or multi process mode 'mp'", ) +@click.option( + "--num_processes", + "-n", + default="all", + help="Start simulations in multiprocessing mode and run n processes - default is all available cores", +) @click.option( "--run_previously_failed_jobs", "-f", @@ -56,7 +63,13 @@ help="Run only the tests that failed in the previous evaluation.", ) def go( - start_id, random_tests, no_log_on_fail, processes_run, run_previously_failed_jobs + start_id, + end_id, + random_tests, + no_log_on_fail, + processes_run, + run_previously_failed_jobs, + num_processes, ): path_tests_src = return_tests_path() # returns the path to the tests/src dir @@ -74,7 +87,7 @@ def go( files_to_run_avail, files_to_ignore = get_files_to_run() if not run_previously_failed_jobs: - files_to_run = select_files(files_to_run_avail, start_id, random_tests) + files_to_run = select_files(files_to_run_avail, start_id, end_id, random_tests) download_data_at_first_run(files_to_run_avail[0]) else: with open(fpath_dashboard_output, "r") as fp: @@ -88,14 +101,19 @@ def go( print( f"Found test cases with mutual dependencies, going to split evaluation into two sets. {len(files_to_run_part2_depending_on_part1)} tests will start right after first eval round." ) - runs_status_info = run_test_cases(files_to_run_part1, no_log_on_fail, processes_run) + runs_status_info = run_test_cases( + files_to_run_part1, no_log_on_fail, processes_run, num_processes + ) if len(files_to_run_part2_depending_on_part1) > 0: print( "Now starting evaluation of tests depending on results of previous tests:" ) runs_status_info_part2 = run_test_cases( - files_to_run_part2_depending_on_part1, no_log_on_fail, processes_run + files_to_run_part2_depending_on_part1, + no_log_on_fail, + processes_run, + num_processes, ) dashboard_dict, failure = status_summary_report( @@ -203,16 +221,17 @@ def get_files_to_run(): return files_to_run, files_to_ignore -def select_files(files_to_run, test_id, random_tests): +def select_files(files_to_run, test_id, end_id, random_tests): pattern = re.compile(r"^test([0-9]+)") - if test_id != "all": - test_id = int(test_id) + if test_id != "all" or end_id != "all": + test_id = int(test_id) if test_id != "all" else 0 + end_id = int(end_id) if end_id != "all" else sys.maxsize files_new = [] for f in files_to_run: match = pattern.match(f) f_test_id = int(float(match.group(1))) - if f_test_id >= test_id: + if f_test_id >= test_id and f_test_id <= end_id: files_new.append(f) else: print(f"Ignoring: {f:<40} (< {test_id}) ") @@ -353,9 +372,11 @@ def run_one_test_case_mp(f): return shell_output -def run_test_cases(files: list, no_log_on_fail: bool, processes_run: str): +def run_test_cases( + files: list, no_log_on_fail: bool, processes_run: str, num_processes: str +): path_tests_src = return_tests_path() - print("Looking for tests in: " + str(path_tests_src)) + # print("Looking for tests in: " + str(path_tests_src)) print(f"Running {len(files)} tests") print("-" * 70) @@ -366,7 +387,8 @@ def run_test_cases(files: list, no_log_on_fail: bool, processes_run: str): elif processes_run in ["sp"]: runs_status_info = list(map(run_one_test_case_mp, files)) else: - with Pool() as pool: + num_processes = int(float(num_processes)) if num_processes != "all" else None + with Pool(processes=num_processes) as pool: runs_status_info = pool.map(run_one_test_case_mp, files) end = time.time() From 8877a08aa65129425c40864583d77089124afdd3 Mon Sep 17 00:00:00 2001 From: Maxime Toussaint Date: Tue, 8 Oct 2024 13:31:13 -0400 Subject: [PATCH 043/183] Modifications by pre-commit --- core/opengate_core/g4DataSetup.py | 60 +++++++++++++++++++------------ 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/core/opengate_core/g4DataSetup.py b/core/opengate_core/g4DataSetup.py index 3c4b5365b..6970c0cb1 100644 --- a/core/opengate_core/g4DataSetup.py +++ b/core/opengate_core/g4DataSetup.py @@ -9,19 +9,19 @@ # Data for Geant4 # Geant4 11.2.1 -data_packages = [ - "https://cern.ch/geant4-data/datasets/G4NDL.4.7.tar.gz", - "https://cern.ch/geant4-data/datasets/G4EMLOW.8.5.tar.gz", - "https://cern.ch/geant4-data/datasets/G4PhotonEvaporation.5.7.tar.gz", - "https://cern.ch/geant4-data/datasets/G4RadioactiveDecay.5.6.tar.gz", - "https://cern.ch/geant4-data/datasets/G4PARTICLEXS.4.0.tar.gz", - "https://cern.ch/geant4-data/datasets/G4PII.1.3.tar.gz", - "https://cern.ch/geant4-data/datasets/G4RealSurface.2.2.tar.gz", - "https://cern.ch/geant4-data/datasets/G4SAIDDATA.2.0.tar.gz", - "https://cern.ch/geant4-data/datasets/G4ABLA.3.3.tar.gz", - "https://cern.ch/geant4-data/datasets/G4INCL.1.2.tar.gz", - "https://cern.ch/geant4-data/datasets/G4ENSDFSTATE.2.3.tar.gz", -] +data_packages = { + "G4NEUTRONHPDATA": "https://cern.ch/geant4-data/datasets/G4NDL.4.7.tar.gz", + "G4LEDATA": "https://cern.ch/geant4-data/datasets/G4EMLOW.8.5.tar.gz", + "G4LEVELGAMMADATA": "https://cern.ch/geant4-data/datasets/G4PhotonEvaporation.5.7.tar.gz", + "G4RADIOACTIVEDATA": "https://cern.ch/geant4-data/datasets/G4RadioactiveDecay.5.6.tar.gz", + "G4PARTICLEXSDATA": "https://cern.ch/geant4-data/datasets/G4PARTICLEXS.4.0.tar.gz", + "G4PIIDATA": "https://cern.ch/geant4-data/datasets/G4PII.1.3.tar.gz", + "G4REALSURFACEDATA": "https://cern.ch/geant4-data/datasets/G4RealSurface.2.2.tar.gz", + "G4SAIDXSDATA": "https://cern.ch/geant4-data/datasets/G4SAIDDATA.2.0.tar.gz", + "G4ABLADATA": "https://cern.ch/geant4-data/datasets/G4ABLA.3.3.tar.gz", + "G4INCLDATA": "https://cern.ch/geant4-data/datasets/G4INCL.1.2.tar.gz", + "G4ENSDFSTATEDATA": "https://cern.ch/geant4-data/datasets/G4ENSDFSTATE.2.3.tar.gz", +} def check_g4_data(): @@ -38,12 +38,12 @@ def check_g4_data(): return else: # Check if the G4 data folder is up to date - consistent = check_consistency_g4_data_folders() - if consistent is False: + missing_g4_Data = get_missing_g4_data() + if len(missing_g4_Data) != 0: print("\nI will download a fresh G4 dataset for you.") print("This will take a moment.") - download_g4_data() - if check_consistency_g4_data_folders() is True: + download_g4_data(missing_g4_Data) + if len(get_missing_g4_data()) == 0: print("\nGeant4 data has been set up successfully.") else: print("There is (still) a problem with the Geant4 data.") @@ -79,12 +79,22 @@ def download_with_resume(url, out, retries=5, delay=10): # Download Geant4 data: -def download_g4_data(): +def download_g4_data(missing_g4_Data=None): data_location = get_g4_data_folder() data_location.mkdir(parents=True, exist_ok=True) folder_names_from_tar = set() - for i, package in enumerate(data_packages): - print(f"\nDownloading {i + 1}/{len(data_packages)} {package}") + + if missing_g4_Data is None: + data_packages_needed = data_packages + else: + data_packages_needed = [ + package + for g4_data, package in data_packages.items() + if g4_data in missing_g4_Data + ] + + for i, package in enumerate(data_packages_needed): + print(f"\nDownloading {i + 1}/{len(data_packages_needed)} {package}") # download the archive (with resume if the connexion failed) package_archive = package.split("/")[-1] @@ -128,7 +138,7 @@ def check_for_non_required_files_folders(): print("\n" + 10 * "*") -def check_consistency_g4_data_folders() -> bool: +def get_missing_g4_data() -> list: dataLocation = get_g4_data_folder() required_paths = set(get_g4_data_paths().values()) existing_paths = set([(dataLocation / f) for f in dataLocation.iterdir()]) @@ -137,9 +147,13 @@ def check_consistency_g4_data_folders() -> bool: print("\nSome Geant4 data folder seem to be missing, namely:") for p in missing_paths: print(str(p)) - return False + return [ + g4_data + for g4_data, folder in get_g4_data_paths().items() + if folder in missing_paths + ] else: - return True + return [] # Return Geant4 data folder: From 33a36f2ecc8ca0fe80281c41fbab70aecf4d7221 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:11:01 +0200 Subject: [PATCH 044/183] Implement logical check in GateObject.__setattr__ --- opengate/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opengate/base.py b/opengate/base.py index 4551a56d0..70edada61 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -454,6 +454,10 @@ def __setattr__(self, key, value): f'For object "{self.name}", attribute "{key}" is not known. Maybe a typo?\n' f"Known attributes of this object are: {s}" ) + known_attributes = type(self).__dict__.get("known_attributes") + if known_attributes is None: + raise GateImplementationError(f"Did not find 'known_attributes' in the {self.type_name}. " + f"Has the class correctly been processed by process_cls()?") self.number_of_warnings += 1 super().__setattr__(key, value) From e3e416068a9d38fe18978f078bfcc5e311bee5b1 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:11:54 +0200 Subject: [PATCH 045/183] Improve warning message in GateObject.__setattr__ --- opengate/base.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index 70edada61..dcbb7ac8c 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -447,17 +447,16 @@ def __setattr__(self, key, value): ) # check if the attribute is known, otherwise warn the user - if len(self.known_attributes) > 0: - if key not in self.known_attributes: - s = ", ".join(str(a) for a in self.known_attributes) - self.warn_user( - f'For object "{self.name}", attribute "{key}" is not known. Maybe a typo?\n' - f"Known attributes of this object are: {s}" - ) known_attributes = type(self).__dict__.get("known_attributes") if known_attributes is None: raise GateImplementationError(f"Did not find 'known_attributes' in the {self.type_name}. " f"Has the class correctly been processed by process_cls()?") + if len(known_attributes) > 0: + if key not in known_attributes: + msg = f'For object "{self.name}", attribute "{key}" is not known. Maybe a typo?\n' + known_attr = ", ".join(str(a) for a in known_attributes) + msg += f"Known attributes of this object are: {known_attr}" + self.warn_user(msg) self.number_of_warnings += 1 super().__setattr__(key, value) From c5d80ad127335137eea1fd7507523ceda03db675 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:12:34 +0200 Subject: [PATCH 046/183] Suggest close matches in GateObject.__setattr__ --- opengate/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opengate/base.py b/opengate/base.py index dcbb7ac8c..e13c87e7a 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -1,6 +1,7 @@ import copy from pathlib import Path from typing import Optional, List +from difflib import get_close_matches from box import Box import sys @@ -454,6 +455,10 @@ def __setattr__(self, key, value): if len(known_attributes) > 0: if key not in known_attributes: msg = f'For object "{self.name}", attribute "{key}" is not known. Maybe a typo?\n' + close_matches = get_close_matches(key, known_attributes) + if len(close_matches)> 0: + msg_close_matches = f"Did you mean: " + " or ".join(close_matches) + "\n" + msg += msg_close_matches known_attr = ", ".join(str(a) for a in known_attributes) msg += f"Known attributes of this object are: {known_attr}" self.warn_user(msg) From be7567d2548ff702f29a5b5dfbbfbda7159e1969 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:12:53 +0200 Subject: [PATCH 047/183] Update imports in base.py --- opengate/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/base.py b/opengate/base.py index e13c87e7a..cc931fc47 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -6,7 +6,7 @@ from box import Box import sys -from .exception import fatal, warning, GateDeprecationError, GateFeatureUnavailableError +from .exception import fatal, warning, GateDeprecationError, GateFeatureUnavailableError, GateImplementationError from .definitions import ( __gate_list_objects__, __gate_dictionary_objects__, From 83be31fd9de9990979af2e0699b27a5685a21b57 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:13:11 +0200 Subject: [PATCH 048/183] Update docstring of process_cls --- opengate/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index cc931fc47..58eb2512a 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -50,9 +50,10 @@ def __call__(cls, *args, **kwargs): def process_cls(cls): - """Digest the class's user_infos and store the augmented class - in a dictionary inside the metaclass which handles the class creation. - Example: type(cls) yields the metaclass MetaUserInfo for the class GateObject. + """The factory function is meant to process classes inheriting from GateObject. + It digests the user info parametrisation from all classes in the inheritance tree + and enhances the __init__ method, so it calls the __finalize_init__ method at the + very end of the __init__ call, which is required to check for invalid attribute setting. """ # check if the class already has an attribute inherited_user_info_defaults # cannot use hasattr because it would find the attribute from already processed super classes From a6e28083757b22912834adbc767c477cdd151e38 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:13:31 +0200 Subject: [PATCH 049/183] Update comment in process_cls --- opengate/base.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index 58eb2512a..32e768a16 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -55,9 +55,12 @@ def process_cls(cls): and enhances the __init__ method, so it calls the __finalize_init__ method at the very end of the __init__ call, which is required to check for invalid attribute setting. """ - # check if the class already has an attribute inherited_user_info_defaults - # cannot use hasattr because it would find the attribute from already processed super classes - # -> must use __dict__ which contains only attribute of the specific cls object + # The class attribute inherited_user_info_defaults is exclusively set by this factory function + # Therefore, if this class does not yet have an attribute inherited_user_info_defaults, + # it means that it has not been processed yet. + # Note: we cannot use hasattr(cls, 'inherited_user_info_defaults') + # because it would potentially find the attribute from already processed super classes + # Therefore, we must use cls.__dict__ which contains only attributes of the specific cls object if "inherited_user_info_defaults" not in cls.__dict__: try: # type(cls)._created_classes[cls] = digest_user_info_defaults(cls) From 05593af8f99e4f246ff961a3be372f8a5364ad9c Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:14:02 +0200 Subject: [PATCH 050/183] raise GateImplementationError in process_cls rather than fatal --- opengate/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index 32e768a16..344434716 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -66,8 +66,8 @@ def process_cls(cls): # type(cls)._created_classes[cls] = digest_user_info_defaults(cls) digest_user_info_defaults(cls) except AttributeError: - fatal( - "Developer error: Looks like you are calling process_cls on a class " + raise GateImplementationError( + "Looks like you are calling process_cls on a class " "that does not inherit from GateObject." ) cls.known_attributes = set() From 734e59ddebe15484045c2c713061603d5def5b35 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:14:29 +0200 Subject: [PATCH 051/183] add and remove comments in process_cls() --- opengate/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/base.py b/opengate/base.py index 344434716..40f91f1a3 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -63,13 +63,13 @@ def process_cls(cls): # Therefore, we must use cls.__dict__ which contains only attributes of the specific cls object if "inherited_user_info_defaults" not in cls.__dict__: try: - # type(cls)._created_classes[cls] = digest_user_info_defaults(cls) digest_user_info_defaults(cls) except AttributeError: raise GateImplementationError( "Looks like you are calling process_cls on a class " "that does not inherit from GateObject." ) + # this class attribute is needed by the __setattr__ method of GateObject cls.known_attributes = set() From 7244a629f1efa3ba212d12ccac96f91f7ca0e63c Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:15:44 +0200 Subject: [PATCH 052/183] Implement wrap_init_method() and trigger it from process_cls() --- opengate/base.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/opengate/base.py b/opengate/base.py index 40f91f1a3..1283f8e6c 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -71,6 +71,52 @@ def process_cls(cls): ) # this class attribute is needed by the __setattr__ method of GateObject cls.known_attributes = set() + # enhance the __init__ method + wrap_init_method(cls) + + +def wrap_init_method(cls): + """This is a factory function to process classes which inherit from GateObject. + It is called from the main factory function process_cls(). + This function wraps and reattaches the __init__ method of this class, if it implements one. + The wrapped __init__ first calls the "original" __init__ and subsequently the method + __finalize_init__, which has a base implementation in GateObject, + in case the __init__ is the furthest down in the inheritance chain. + The method __finalize_init__ is needed to allow GateObject.__setattr__ to check for invalid attribute setting. + """ + # Get the __init__ method as the class implements it + original_init = cls.__dict__.get('__init__') + # if it is implemented, i.e. present in __dict__, wrap it + if original_init is not None: + # define a closure + def wrapped_init(self, *args, **kwargs): + # original_init is the __init__ captured in the closure + original_init(self, *args, **kwargs) + # figure out in which class the __init__ method is implemented. + # It could be in some super class with respect to the instance self. + class_to_which_original_init_belongs = vars(sys.modules[original_init.__module__])[ + original_init.__qualname__.split('.')[0]] + # Now figure out which is the "last" class in the inheritance chain + # (with respect to the instance self) + # which implements an __init__ method. Plus the children which do not implement an __init__ + classes_up_to_first_init_in_mro = [] + for c in type(self).mro(): + classes_up_to_first_init_in_mro.append(c) + if '__init__' in c.__dict__: + # found an __init__, so __init__ methods in further super classes + # should not call the __finalize_init__ method + break + # Now check if the class in which the __init__ we are wrapping is implemented + # is among the previously extracted classes. + # If that is the case, the call to this __init__ will be the last one to terminate + # in the chain if super().__init__() calls. + # In other words, we are at the very end of __init__, including calls to super classes, + # and it is time to call __finalize_init__ + if class_to_which_original_init_belongs in classes_up_to_first_init_in_mro: + self.__finalize_init__() + + # reattach the wrapped __init__ to the class, so it is used instead of the original one. + setattr(cls, '__init__', wrapped_init) # Utility function for object creation From 5d01a8b2e5da392e219091eaf5ef45dc5bad11a4 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:17:28 +0200 Subject: [PATCH 053/183] Include **kwargs in signature of Simulation.__init__ --- opengate/managers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/opengate/managers.py b/opengate/managers.py index 47cdbc406..63464de72 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1396,13 +1396,15 @@ class Simulation(GateObject): ), } - def __init__(self, name="simulation"): + def __init__(self, name="simulation", **kwargs): """ Main members are: - managers of volumes, physics, sources, actors and filters - the Geant4 objects will be only built during initialisation in SimulationEngine """ - super().__init__(name=name) + # The Simulation instance should not hold a reference to itself (cycle) + kwargs.pop('simulation', None) + super().__init__(name=name, **kwargs) # for debug only self.verbose_getstate = False From 422dc1777e478618abd43f72a20bae76f330b5e6 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:17:50 +0200 Subject: [PATCH 054/183] Decalre expected_number_of_events in Simulation.__init__ --- opengate/managers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opengate/managers.py b/opengate/managers.py index 63464de72..f1a6e5e2e 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1427,6 +1427,7 @@ def __init__(self, name="simulation", **kwargs): # list to store warning messages issued somewhere in the simulation self._user_warnings = [] + self.expected_number_of_events = None def __str__(self): s = ( From a90e4e5adec05c1ca8c2a7019d291f1e7b1d66e5 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:18:19 +0200 Subject: [PATCH 055/183] Implement Simulation.reset_warnings() --- opengate/managers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/opengate/managers.py b/opengate/managers.py index f1a6e5e2e..a29a7c086 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1463,6 +1463,9 @@ def current_random_seed(self): def warnings(self): return self._user_warnings + def reset_warnings(self): + self._user_warnings = [] + def to_dictionary(self): d = super().to_dictionary() d["volume_manager"] = self.volume_manager.to_dictionary() From e6312ad993c70f74e8a26d1c7519132e4981ef88 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:18:34 +0200 Subject: [PATCH 056/183] Implement Simulation.warn_user() --- opengate/managers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opengate/managers.py b/opengate/managers.py index a29a7c086..b432efab4 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1466,6 +1466,12 @@ def warnings(self): def reset_warnings(self): self._user_warnings = [] + def warn_user(self, message): + # We need this specific implementation because the Simulation does not hold a reference 'simulation', + # as required by the base class implementation of warn_user() + self._user_warnings.append(message) + super().warn_user(message) + def to_dictionary(self): d = super().to_dictionary() d["volume_manager"] = self.volume_manager.to_dictionary() From 100c68bb02628672cb2c017cb9aa3e976d3fc541 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:19:44 +0200 Subject: [PATCH 057/183] Handle warnings raise during engine run (potentially in subprocess) --- opengate/engines.py | 19 +++++++++---------- opengate/managers.py | 13 +++++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/opengate/engines.py b/opengate/engines.py index 881a6d4c3..d9f31a558 100644 --- a/opengate/engines.py +++ b/opengate/engines.py @@ -912,6 +912,7 @@ def __init__(self): self.ppid = os.getppid() self.current_random_seed = None self.user_hook_log = [] + self.warnings = None def store_actors(self, simulation_engine): self.actors = simulation_engine.simulation.actor_manager.actors @@ -1121,6 +1122,13 @@ def run_engine(self): # prepare the output output = SimulationOutput() + # if the simulation is run in a subprocess, + # we want to capture only the warnings from this point on + # because everything else has already been executed in the main process + # and potential warnings have already been registered. + if self.new_process is True: + self.simulation.reset_warnings() + # initialization self.initialize() @@ -1156,16 +1164,7 @@ def run_engine(self): output.store_hook_log(self) output.current_random_seed = self.current_random_seed output.expected_number_of_events = self.source_engine.expected_number_of_events - - if len(self.simulation.warnings) > 0: - print("*" * 20) - print( - f"{len(self.simulation.warnings)} warnings occurred in this simulation: " - ) - for w in self.simulation.warnings: - print(w) - print("-" * 10) - print("*" * 20) + output.warnings = self.simulation.warnings return output diff --git a/opengate/managers.py b/opengate/managers.py index b432efab4..517c45f28 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1678,6 +1678,8 @@ def run(self, start_new_process=False): # because everything is already in place. output = self._run_simulation_engine(False) + self._user_warnings.extend(output.warnings) + # FIXME workaround self.expected_number_of_events = output.expected_number_of_events @@ -1692,6 +1694,17 @@ def run(self, start_new_process=False): if self.volume_manager.material_database is None: self.volume_manager.material_database = MaterialDatabase() + if len(self.warnings) > 0: + print("*" * 20) + print( + f"{len(self.warnings)} warnings occurred in this simulation: \n" + ) + for i, w in enumerate(self.warnings): + print(f"{i+1}) " + "-" * 10) + print(w) + print() + print("*" * 20) + def voxelize_geometry( self, extent="auto", From 847734aedfbfc9acf8de2bc56eccddb478c65f3b Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:22:06 +0200 Subject: [PATCH 058/183] Remove explicit calls to self.__finalize_init__() (now automatic) --- opengate/actors/arfactors.py | 2 -- opengate/actors/digitizers.py | 9 --------- opengate/actors/doseactors.py | 3 --- opengate/actors/dynamicactors.py | 1 - opengate/actors/filters.py | 6 ------ opengate/actors/miscactors.py | 4 ---- 6 files changed, 25 deletions(-) diff --git a/opengate/actors/arfactors.py b/opengate/actors/arfactors.py index 433bbfdcc..87051d9e5 100644 --- a/opengate/actors/arfactors.py +++ b/opengate/actors/arfactors.py @@ -74,7 +74,6 @@ def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self._add_user_output(ActorOutputRoot, "root_output") self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateARFTrainingDatasetActor.__init__(self, self.user_info) @@ -220,7 +219,6 @@ def __init__(self, *args, **kwargs): self._add_user_output(ActorOutputSingleImage, "arf_projection") self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateARFActor.__init__(self, self.user_info) diff --git a/opengate/actors/digitizers.py b/opengate/actors/digitizers.py index 59e5038b1..6dbbdd6d6 100644 --- a/opengate/actors/digitizers.py +++ b/opengate/actors/digitizers.py @@ -328,7 +328,6 @@ def __init__(self, *args, **kwargs): DigitizerBase.__init__(self, *args, **kwargs) self._add_user_output_root() self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateDigitizerAdderActor.__init__(self, self.user_info) @@ -444,7 +443,6 @@ def __init__(self, *args, **kwargs): DigitizerBase.__init__(self, *args, **kwargs) self._add_user_output_root() self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateDigitizerBlurringActor.__init__(self, self.user_info) @@ -573,7 +571,6 @@ def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self._add_user_output_root() self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateDigitizerSpatialBlurringActor.__init__(self, self.user_info) @@ -651,7 +648,6 @@ def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self._add_user_output_root() self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateDigitizerEfficiencyActor.__init__(self, self.user_info) @@ -721,7 +717,6 @@ def __init__(self, *args, **kwargs): DigitizerBase.__init__(self, *args, **kwargs) self._add_user_output_root() self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateDigitizerEnergyWindowsActor.__init__(self, self.user_info) @@ -779,7 +774,6 @@ def __init__(self, *args, **kwargs): DigitizerBase.__init__(self, *args, **kwargs) self._add_user_output_root() self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateDigitizerHitsCollectionActor.__init__(self, self.user_info) @@ -856,7 +850,6 @@ def __init__(self, *args, **kwargs): self._add_user_output(ActorOutputSingleImage, "projection") self.start_output_origin = None self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateDigitizerProjectionActor.__init__(self, self.user_info) @@ -1037,7 +1030,6 @@ def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self._add_user_output_root() self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): # python 3.8 complains about init not called, we add explicit call to @@ -1100,7 +1092,6 @@ def __init__(self, *args, **kwargs): self.total_number_of_entries = 0 self.number_of_absorbed_events = 0 self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GatePhaseSpaceActor.__init__(self, self.user_info) diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index 4f909a422..85c9d6f80 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -496,7 +496,6 @@ def __init__(self, *args, **kwargs): self.user_output.density.set_item_suffix("density") self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateDoseActor.__init__(self, self.user_info) @@ -824,7 +823,6 @@ def __init__(self, *args, **kwargs): self.user_output.let.set_write_to_disk(True, item="quotient") self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateLETActor.__init__(self, self.user_info) @@ -911,7 +909,6 @@ def __init__(self, *args, **kwargs): self._add_user_output(ActorOutputSingleImage, "fluence") self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateFluenceActor.__init__(self, self.user_info) diff --git a/opengate/actors/dynamicactors.py b/opengate/actors/dynamicactors.py index 79164dfa1..6b8f02083 100644 --- a/opengate/actors/dynamicactors.py +++ b/opengate/actors/dynamicactors.py @@ -16,7 +16,6 @@ def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self.geometry_changers = [] self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateVActor.__init__(self, {"name": self.name}) diff --git a/opengate/actors/filters.py b/opengate/actors/filters.py index 82afbe9be..4d36faa03 100644 --- a/opengate/actors/filters.py +++ b/opengate/actors/filters.py @@ -36,7 +36,6 @@ def initialize(self): def __setstate__(self, state): self.__dict__ = state self.__initcpp__() - self.__finalize_init__() class ParticleFilter(FilterBase, g4.GateParticleFilter): @@ -56,7 +55,6 @@ class ParticleFilter(FilterBase, g4.GateParticleFilter): def __init__(self, *args, **kwargs): FilterBase.__init__(self, *args, **kwargs) self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateParticleFilter.__init__(self) @@ -86,7 +84,6 @@ class KineticEnergyFilter(FilterBase, g4.GateKineticEnergyFilter): def __init__(self, *args, **kwargs): FilterBase.__init__(self, *args, **kwargs) self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateKineticEnergyFilter.__init__(self) # no argument in cpp side @@ -109,7 +106,6 @@ class TrackCreatorProcessFilter(FilterBase, g4.GateTrackCreatorProcessFilter): def __init__(self, *args, **kwargs): FilterBase.__init__(self, *args, **kwargs) self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateTrackCreatorProcessFilter.__init__(self) # no argument in cpp side @@ -148,7 +144,6 @@ class ThresholdAttributeFilter(FilterBase, g4.GateThresholdAttributeFilter): def __init__(self, *args, **kwargs): FilterBase.__init__(self, *args, **kwargs) self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateThresholdAttributeFilter.__init__(self) @@ -166,7 +161,6 @@ class UnscatteredPrimaryFilter(FilterBase, g4.GateUnscatteredPrimaryFilter): def __init__(self, *args, **kwargs): FilterBase.__init__(self, *args, **kwargs) self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateUnscatteredPrimaryFilter.__init__(self) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index d8bc9d49a..ebf9b8d0d 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -204,7 +204,6 @@ def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self._add_user_output(ActorOutputStatisticsActor, "stats") self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateSimulationStatisticsActor.__init__(self, self.user_info) @@ -285,7 +284,6 @@ def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self.number_of_killed_particles = 0 self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateKillActor.__init__(self, self.user_info) @@ -400,7 +398,6 @@ class ComptSplittingActor(SplittingActorBase, g4.GateOptrComptSplittingActor): def __init__(self, *args, **kwargs): SplittingActorBase.__init__(self, *args, **kwargs) self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateOptrComptSplittingActor.__init__(self, {"name": self.name}) @@ -429,7 +426,6 @@ class BremSplittingActor(SplittingActorBase, g4.GateBOptrBremSplittingActor): def __init__(self, *args, **kwargs): SplittingActorBase.__init__(self, *args, **kwargs) self.__initcpp__() - self.__finalize_init__() def __initcpp__(self): g4.GateBOptrBremSplittingActor.__init__(self, {"name": self.name}) From c6486b83f6f35812f5858df7dc23d8907aedd0b5 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 27 Sep 2024 23:22:19 +0200 Subject: [PATCH 059/183] Enhance test078_check_userinfo.py --- opengate/tests/src/test078_check_userinfo.py | 36 +++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/opengate/tests/src/test078_check_userinfo.py b/opengate/tests/src/test078_check_userinfo.py index 8b720f911..5ad4ceb40 100755 --- a/opengate/tests/src/test078_check_userinfo.py +++ b/opengate/tests/src/test078_check_userinfo.py @@ -9,6 +9,27 @@ from opengate.utility import g4_units from opengate.managers import Simulation + +def set_wrong_attribute(obj, attr): + # set a WRONG attribute + number_of_warnings_before = getattr(obj, "number_of_warnings") + setattr(obj, attr, "nothing") + number_of_warnings_after = getattr(obj, "number_of_warnings") + + # check the number of warnings before and after + print( + f"Number of warnings for {obj.type_name} object {obj.name}: {number_of_warnings_before}" + ) + print( + f"Number of warnings for {obj.type_name} object {obj.name}: {number_of_warnings_after}" + ) + b = (number_of_warnings_after - number_of_warnings_before == 1) + print_test( + b, f"Tried to set a wrong attribute '{attr}'. It should print a single warning" + ) + return b + + if __name__ == "__main__": paths = get_default_test_paths( __file__, @@ -54,18 +75,9 @@ ) print() - # set a WRONG attribute - stats.TOTO = "nothing" - - # check the number of warnings (before the run) - print( - f"(before run) Number of warnings for stats object: {stats.number_of_warnings}" - ) - b = stats.number_of_warnings == 1 - print_test( - b, f"Try to set a wrong attribute 'TOTO', it should print a single warning" - ) - is_ok = is_ok and b + is_ok = is_ok and set_wrong_attribute(stats, 'TOTO') + is_ok = is_ok and set_wrong_attribute(waterbox, 'mohter') + is_ok = is_ok and set_wrong_attribute(sim, 'nthreads') sim.run(start_new_process=True) From 207743162cab4d74513eecdda193ac052490967b Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:12:40 +0200 Subject: [PATCH 060/183] Improve comment in base.py --- opengate/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/base.py b/opengate/base.py index 1283f8e6c..8ced8bc06 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -103,7 +103,7 @@ def wrapped_init(self, *args, **kwargs): for c in type(self).mro(): classes_up_to_first_init_in_mro.append(c) if '__init__' in c.__dict__: - # found an __init__, so __init__ methods in further super classes + # found an __init__, so __init__ methods in classes further up the inheritance tree # should not call the __finalize_init__ method break # Now check if the class in which the __init__ we are wrapping is implemented From 12f17652ce9efd9d1280cca70603adb3e455005b Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:13:15 +0200 Subject: [PATCH 061/183] Implement getter/setter property for simulation in GateObject --- opengate/base.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index 8ced8bc06..e9b456d7e 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -386,9 +386,7 @@ def __new__(cls, *args, **kwargs): return new_instance def __init__(self, *args, simulation=None, **kwargs): - self.simulation = None - if simulation is not None: - self.simulation = simulation + self._simulation = simulation # keep internal number of raised warnings (for debug) self.number_of_warnings = 0 # prefill user info with defaults @@ -427,6 +425,16 @@ def __init__(self, *args, simulation=None, **kwargs): f"{list(self.inherited_user_info_defaults.keys())}.\n" ) + @property + def simulation(self): + return self._simulation + + @simulation.setter + def simulation(self, sim): + sim.warnings.extend(self._temporary_warning_cache) + self._temporary_warning_cache = [] + self._simulation = sim + def __str__(self): ret_string = ( f"***\n" From f9be1c68729cfa161bfbd8e739a7b49d19a2e3a4 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:14:18 +0200 Subject: [PATCH 062/183] Implement a temporary warning cache for GateObject --- opengate/base.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index e9b456d7e..febe05d0b 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -389,6 +389,7 @@ def __init__(self, *args, simulation=None, **kwargs): self._simulation = simulation # keep internal number of raised warnings (for debug) self.number_of_warnings = 0 + self._temporary_warning_cache = [] # prefill user info with defaults self.user_info = Box( [ @@ -619,11 +620,14 @@ def from_dictionary(self, d): ) def warn_user(self, message): - # this may be called without simulation object, so we guard with try/except - try: + # If this GateObject does not (yet) have a reference to the simulation, + # we store the warning in a temporary cache + # (will be registered later to the simulation's warning cache) + if self.simulation is None: + self._temporary_warning_cache.append(message) + # if possible, register the warning directly + else: self.simulation._user_warnings.append(message) - except: - pass warning(message) From 2796a54e1355f597e474603d3dccd68c3d9dc26d Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:15:13 +0200 Subject: [PATCH 063/183] Use dir() builtin in GateObject.__finalize_init__() --- opengate/base.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index febe05d0b..0e7c93869 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -540,11 +540,7 @@ def __finalize_init__(self): """ # we define this at the class-level - type(self).known_attributes = set( - list(self.user_info.keys()) - + list(self.__dict__.keys()) - + list(["__dict__"]) - ) + type(self).known_attributes = set(dir(self)) def __add_to_simulation__(self): """Hook method which can be called by managers. From 5fb04205ca9d2106d33630edd776cc9dbc86d008 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:15:43 +0200 Subject: [PATCH 064/183] Improve warning message in GateObject.__setattr__() --- opengate/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/base.py b/opengate/base.py index 0e7c93869..7b6e20f0d 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -518,7 +518,7 @@ def __setattr__(self, key, value): if len(close_matches)> 0: msg_close_matches = f"Did you mean: " + " or ".join(close_matches) + "\n" msg += msg_close_matches - known_attr = ", ".join(str(a) for a in known_attributes) + known_attr = ", ".join(str(a) for a in known_attributes if not a.startswith('_')) msg += f"Known attributes of this object are: {known_attr}" self.warn_user(msg) self.number_of_warnings += 1 From 1beeb0059b19f94b4451931ddd7f38a982138f43 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:16:21 +0200 Subject: [PATCH 065/183] Move declaration of _user_warnings to the top of init --- opengate/managers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/opengate/managers.py b/opengate/managers.py index 517c45f28..471d72fd3 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1406,6 +1406,9 @@ def __init__(self, name="simulation", **kwargs): kwargs.pop('simulation', None) super().__init__(name=name, **kwargs) + # list to store warning messages issued somewhere in the simulation + self._user_warnings = [] + # for debug only self.verbose_getstate = False self.verbose_close = False @@ -1425,8 +1428,6 @@ def __init__(self, name="simulation", **kwargs): # read-only info self._current_random_seed = None - # list to store warning messages issued somewhere in the simulation - self._user_warnings = [] self.expected_number_of_events = None def __str__(self): From ce6bb23359a49331cc7429b46d7a66dc462f0f81 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:17:03 +0200 Subject: [PATCH 066/183] Remove SimulationStatisticsActor.__finalize_init__ (not needed anymore) --- opengate/actors/miscactors.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index ebf9b8d0d..b63e8ad20 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -209,11 +209,11 @@ def __initcpp__(self): g4.GateSimulationStatisticsActor.__init__(self, self.user_info) self.AddActions({"StartSimulationAction", "EndSimulationAction"}) - def __finalize_init__(self): - super().__finalize_init__() - # this attribute is considered sometimes in the read_stat_file - # we declare it here to avoid warning - self.known_attributes.add("date") + # def __finalize_init__(self): + # super().__finalize_init__() + # # this attribute is considered sometimes in the read_stat_file + # # we declare it here to avoid warning + # self.known_attributes.add("date") def __str__(self): s = self.user_output["stats"].__str__() From be709e6447609abcace55ed41669eeb0429d1a43 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:18:53 +0200 Subject: [PATCH 067/183] Implement specialized __finalize_init__() in VolumeBase --- opengate/geometry/volumes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index 6317c87ef..39e42f8aa 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -237,6 +237,12 @@ def __getstate__(self): return_dict["_is_constructed"] = False return return_dict + def __finalize_init__(self): + super().__finalize_init__() + # need to add this explciitly because anytree does not properly declare + # the attribute __parent in the NodeMixin.__init__ which leads to falls warnings + self.known_attributes.add('_NodeMixin__parent') + def _update_node(self): """Internal method which retrieves the volume object from the volume manager based on the mother's name stored as user info 'mother' From 2e3bf33149ce36bd03b32bfbc850eeccddcc0d61 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 00:19:26 +0200 Subject: [PATCH 068/183] remove specialized implementation ActorBase.__finalize_init__() - not needed anymore --- opengate/actors/base.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/opengate/actors/base.py b/opengate/actors/base.py index e8ed5c28f..ad579ca81 100644 --- a/opengate/actors/base.py +++ b/opengate/actors/base.py @@ -149,13 +149,13 @@ def __setstate__(self, state): self.__initcpp__() self.__update_interface_properties__() - def __finalize_init__(self): - super().__finalize_init__() - # The following attributes exist. They are declared here to avoid warning - # fFilters is not known here because ActorBase does not inherit from a cpp counterpart. - self.known_attributes.add("fFilters") - # output_filename is a property - self.known_attributes.add("output_filename") + # def __finalize_init__(self): + # super().__finalize_init__() + # # The following attributes exist. They are declared here to avoid warning + # # fFilters is not known here because ActorBase does not inherit from a cpp counterpart. + # self.known_attributes.add("fFilters") + # # output_filename is a property + # self.known_attributes.add("output_filename") def to_dictionary(self): d = super().to_dictionary() From b0ef0b75c6981a6a3023f518761c623ddc35aa82 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 22:27:40 +0000 Subject: [PATCH 069/183] [pre-commit.ci] Automatic python and c++ formatting --- opengate/base.py | 35 ++++++++++++++------ opengate/geometry/volumes.py | 2 +- opengate/managers.py | 6 ++-- opengate/tests/src/test078_check_userinfo.py | 8 ++--- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index 7b6e20f0d..ff3949874 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -6,7 +6,13 @@ from box import Box import sys -from .exception import fatal, warning, GateDeprecationError, GateFeatureUnavailableError, GateImplementationError +from .exception import ( + fatal, + warning, + GateDeprecationError, + GateFeatureUnavailableError, + GateImplementationError, +) from .definitions import ( __gate_list_objects__, __gate_dictionary_objects__, @@ -85,7 +91,7 @@ def wrap_init_method(cls): The method __finalize_init__ is needed to allow GateObject.__setattr__ to check for invalid attribute setting. """ # Get the __init__ method as the class implements it - original_init = cls.__dict__.get('__init__') + original_init = cls.__dict__.get("__init__") # if it is implemented, i.e. present in __dict__, wrap it if original_init is not None: # define a closure @@ -94,15 +100,16 @@ def wrapped_init(self, *args, **kwargs): original_init(self, *args, **kwargs) # figure out in which class the __init__ method is implemented. # It could be in some super class with respect to the instance self. - class_to_which_original_init_belongs = vars(sys.modules[original_init.__module__])[ - original_init.__qualname__.split('.')[0]] + class_to_which_original_init_belongs = vars( + sys.modules[original_init.__module__] + )[original_init.__qualname__.split(".")[0]] # Now figure out which is the "last" class in the inheritance chain # (with respect to the instance self) # which implements an __init__ method. Plus the children which do not implement an __init__ classes_up_to_first_init_in_mro = [] for c in type(self).mro(): classes_up_to_first_init_in_mro.append(c) - if '__init__' in c.__dict__: + if "__init__" in c.__dict__: # found an __init__, so __init__ methods in classes further up the inheritance tree # should not call the __finalize_init__ method break @@ -116,7 +123,7 @@ def wrapped_init(self, *args, **kwargs): self.__finalize_init__() # reattach the wrapped __init__ to the class, so it is used instead of the original one. - setattr(cls, '__init__', wrapped_init) + setattr(cls, "__init__", wrapped_init) # Utility function for object creation @@ -509,16 +516,22 @@ def __setattr__(self, key, value): # check if the attribute is known, otherwise warn the user known_attributes = type(self).__dict__.get("known_attributes") if known_attributes is None: - raise GateImplementationError(f"Did not find 'known_attributes' in the {self.type_name}. " - f"Has the class correctly been processed by process_cls()?") + raise GateImplementationError( + f"Did not find 'known_attributes' in the {self.type_name}. " + f"Has the class correctly been processed by process_cls()?" + ) if len(known_attributes) > 0: if key not in known_attributes: msg = f'For object "{self.name}", attribute "{key}" is not known. Maybe a typo?\n' close_matches = get_close_matches(key, known_attributes) - if len(close_matches)> 0: - msg_close_matches = f"Did you mean: " + " or ".join(close_matches) + "\n" + if len(close_matches) > 0: + msg_close_matches = ( + f"Did you mean: " + " or ".join(close_matches) + "\n" + ) msg += msg_close_matches - known_attr = ", ".join(str(a) for a in known_attributes if not a.startswith('_')) + known_attr = ", ".join( + str(a) for a in known_attributes if not a.startswith("_") + ) msg += f"Known attributes of this object are: {known_attr}" self.warn_user(msg) self.number_of_warnings += 1 diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index 39e42f8aa..0abc2f1b8 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -241,7 +241,7 @@ def __finalize_init__(self): super().__finalize_init__() # need to add this explciitly because anytree does not properly declare # the attribute __parent in the NodeMixin.__init__ which leads to falls warnings - self.known_attributes.add('_NodeMixin__parent') + self.known_attributes.add("_NodeMixin__parent") def _update_node(self): """Internal method which retrieves the volume object diff --git a/opengate/managers.py b/opengate/managers.py index 471d72fd3..2dd3701df 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1403,7 +1403,7 @@ def __init__(self, name="simulation", **kwargs): - the Geant4 objects will be only built during initialisation in SimulationEngine """ # The Simulation instance should not hold a reference to itself (cycle) - kwargs.pop('simulation', None) + kwargs.pop("simulation", None) super().__init__(name=name, **kwargs) # list to store warning messages issued somewhere in the simulation @@ -1697,9 +1697,7 @@ def run(self, start_new_process=False): if len(self.warnings) > 0: print("*" * 20) - print( - f"{len(self.warnings)} warnings occurred in this simulation: \n" - ) + print(f"{len(self.warnings)} warnings occurred in this simulation: \n") for i, w in enumerate(self.warnings): print(f"{i+1}) " + "-" * 10) print(w) diff --git a/opengate/tests/src/test078_check_userinfo.py b/opengate/tests/src/test078_check_userinfo.py index 5ad4ceb40..c40bf916d 100755 --- a/opengate/tests/src/test078_check_userinfo.py +++ b/opengate/tests/src/test078_check_userinfo.py @@ -23,7 +23,7 @@ def set_wrong_attribute(obj, attr): print( f"Number of warnings for {obj.type_name} object {obj.name}: {number_of_warnings_after}" ) - b = (number_of_warnings_after - number_of_warnings_before == 1) + b = number_of_warnings_after - number_of_warnings_before == 1 print_test( b, f"Tried to set a wrong attribute '{attr}'. It should print a single warning" ) @@ -75,9 +75,9 @@ def set_wrong_attribute(obj, attr): ) print() - is_ok = is_ok and set_wrong_attribute(stats, 'TOTO') - is_ok = is_ok and set_wrong_attribute(waterbox, 'mohter') - is_ok = is_ok and set_wrong_attribute(sim, 'nthreads') + is_ok = is_ok and set_wrong_attribute(stats, "TOTO") + is_ok = is_ok and set_wrong_attribute(waterbox, "mohter") + is_ok = is_ok and set_wrong_attribute(sim, "nthreads") sim.run(start_new_process=True) From 0124b4412f5d0f1d7583cfdbe4d1722ee133be09 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 12:37:58 +0200 Subject: [PATCH 070/183] Extend test078_check_userinfo.py --- opengate/tests/src/test078_check_userinfo.py | 26 ++++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/opengate/tests/src/test078_check_userinfo.py b/opengate/tests/src/test078_check_userinfo.py index c40bf916d..67899514c 100755 --- a/opengate/tests/src/test078_check_userinfo.py +++ b/opengate/tests/src/test078_check_userinfo.py @@ -7,7 +7,7 @@ print_test, ) from opengate.utility import g4_units -from opengate.managers import Simulation +import opengate as gate def set_wrong_attribute(obj, attr): @@ -35,7 +35,7 @@ def set_wrong_attribute(obj, attr): __file__, ) - sim = Simulation() + sim = gate.Simulation() m = g4_units.m cm = g4_units.cm @@ -48,10 +48,21 @@ def set_wrong_attribute(obj, attr): world.size = [3 * m, 3 * m, 3 * m] world.material = "G4_AIR" - waterbox = sim.add_volume("Box", "Waterbox") + is_ok = True + + # create the volume without adding it to the simulation + waterbox = gate.geometry.volumes.BoxVolume(name="Waterbox") waterbox.size = [40 * cm, 40 * cm, 40 * cm] waterbox.translation = [0 * cm, 0 * cm, 25 * cm] waterbox.material = "G4_WATER" + # provoke a warning + is_ok = is_ok and set_wrong_attribute(waterbox, "mohter") + # now add the volume to the simulation + sim.add_volume(waterbox) + # a warning about the wrong attribute "mohter" should appear + # in the list of warnings at the end of the simulation + # because it should be transferred from the object's warning cache + # to the simulation's warning cache once the object is added to the simulation source = sim.add_source("GenericSource", "Default") source.particle = "gamma" @@ -61,7 +72,6 @@ def set_wrong_attribute(obj, attr): source.n = 20 # test wrong attributes - is_ok = True stats = sim.add_actor("SimulationStatisticsActor", "Stats") stats.track_types_flag = True try: @@ -76,11 +86,17 @@ def set_wrong_attribute(obj, attr): print() is_ok = is_ok and set_wrong_attribute(stats, "TOTO") - is_ok = is_ok and set_wrong_attribute(waterbox, "mohter") is_ok = is_ok and set_wrong_attribute(sim, "nthreads") sim.run(start_new_process=True) + found_warning_about_waterbox_mohter = False + for w in sim.warnings: + if "mohter" in w: + found_warning_about_waterbox_mohter = True + break + is_ok = is_ok and found_warning_about_waterbox_mohter + print( f"(after run) Number of warnings for stats object: {stats.number_of_warnings}" ) From d1685ea287b2bba3ceee05398c40628fbe6f040d Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 12:43:45 +0200 Subject: [PATCH 071/183] Add @wraps(original_init) decorator to wrapped_init --- opengate/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opengate/base.py b/opengate/base.py index ff3949874..fa3543530 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import Optional, List from difflib import get_close_matches +from functools import wraps from box import Box import sys @@ -95,6 +96,7 @@ def wrap_init_method(cls): # if it is implemented, i.e. present in __dict__, wrap it if original_init is not None: # define a closure + @wraps(original_init) def wrapped_init(self, *args, **kwargs): # original_init is the __init__ captured in the closure original_init(self, *args, **kwargs) From c5ec2ac2ee2a4c5a3ccbbfa56afa310698b21c1f Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 12:44:22 +0200 Subject: [PATCH 072/183] Implement GateObject.has_been_processed() classmethod to make it explicit how this is determined --- opengate/base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/opengate/base.py b/opengate/base.py index fa3543530..a74e0dc49 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -68,7 +68,7 @@ def process_cls(cls): # Note: we cannot use hasattr(cls, 'inherited_user_info_defaults') # because it would potentially find the attribute from already processed super classes # Therefore, we must use cls.__dict__ which contains only attributes of the specific cls object - if "inherited_user_info_defaults" not in cls.__dict__: + if not cls.has_been_processed(): try: digest_user_info_defaults(cls) except AttributeError: @@ -389,6 +389,10 @@ class GateObject: user_info_defaults = {"name": (None, {"required": True})} + @classmethod + def has_been_processed(cls): + return "inherited_user_info_defaults" in cls.__dict__ + def __new__(cls, *args, **kwargs): process_cls(cls) new_instance = super(GateObject, cls).__new__(cls) From 446d6fb0f6fb687c99149244fb97755d01a978eb Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 28 Sep 2024 12:45:34 +0200 Subject: [PATCH 073/183] Update comments in base.py --- opengate/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index a74e0dc49..8f8870bfc 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -76,9 +76,9 @@ def process_cls(cls): "Looks like you are calling process_cls on a class " "that does not inherit from GateObject." ) - # this class attribute is needed by the __setattr__ method of GateObject + # the class attribute known_attributes is needed by the __setattr__ method of GateObject cls.known_attributes = set() - # enhance the __init__ method + # enhance the __init__ method to ensure __finalize_init__ is called at the end wrap_init_method(cls) From 3ae49a6fadb1746d7ce978b52e8c7c0c800704bb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:48:01 +0000 Subject: [PATCH 074/183] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-clang-format: v18.1.8 → v19.1.0](https://github.com/pre-commit/mirrors-clang-format/compare/v18.1.8...v19.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d5e1691d1..f6ca9dcce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: hooks: - id: black - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v18.1.8 + rev: v19.1.0 hooks: - id: clang-format ci: From 0a1c932aa9d447378a360e96bc9a1f943c3e9da2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:48:19 +0000 Subject: [PATCH 075/183] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_core/opengate_lib/indicators.hpp | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/core/opengate_core/opengate_lib/indicators.hpp b/core/opengate_core/opengate_lib/indicators.hpp index 47f93bbd5..92539ca86 100644 --- a/core/opengate_core/opengate_lib/indicators.hpp +++ b/core/opengate_core/opengate_lib/indicators.hpp @@ -804,7 +804,7 @@ static inline size_t terminal_width() { return terminal_size().second; } namespace indicators { static inline std::pair terminal_size() { - struct winsize size {}; + struct winsize size{}; ioctl(STDOUT_FILENO, TIOCGWINSZ, &size); return {static_cast(size.ws_row), static_cast(size.ws_col)}; } @@ -1023,10 +1023,9 @@ struct option_idx, counter> { }; template -auto get_value(Settings &&settings) - -> decltype(( - std::get::type>::value>( - std::declval()))) { +auto get_value(Settings &&settings) -> decltype(( + std::get::type>::value>( + std::declval()))) { return std::get::type>::value>( std::forward(settings)); } @@ -1972,9 +1971,8 @@ class ProgressBar { } template - auto get_value() const - -> decltype(( - details::get_value(std::declval()).value)) { + auto get_value() const -> decltype(( + details::get_value(std::declval()).value)) { return details::get_value(settings_).value; } @@ -2309,9 +2307,8 @@ class BlockProgressBar { } template - auto get_value() const - -> decltype(( - details::get_value(std::declval()).value)) { + auto get_value() const -> decltype(( + details::get_value(std::declval()).value)) { return details::get_value(settings_).value; } @@ -2638,9 +2635,8 @@ class IndeterminateProgressBar { } template - auto get_value() const - -> decltype(( - details::get_value(std::declval()).value)) { + auto get_value() const -> decltype(( + details::get_value(std::declval()).value)) { return details::get_value(settings_).value; } @@ -2900,9 +2896,8 @@ template class DynamicProgress { } template - auto get_value() const - -> decltype(( - details::get_value(std::declval()).value)) { + auto get_value() const -> decltype(( + details::get_value(std::declval()).value)) { return details::get_value(settings_).value; } @@ -3115,9 +3110,8 @@ class ProgressSpinner { } template - auto get_value() const - -> decltype(( - details::get_value(std::declval()).value)) { + auto get_value() const -> decltype(( + details::get_value(std::declval()).value)) { return details::get_value(settings_).value; } From a5e114f4c9b655f2ad3f90ddc62f573ce279282c Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Sep 2024 09:00:43 +0200 Subject: [PATCH 076/183] Fix spatial blurring for parameterised volumes. Ensure correct volume extents by considering parent volume for parameterised volumes. This prevents inaccurate calculations based on a single instance of a repeated solid. --- .../digitizer/GateDigitizerSpatialBlurringActor.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/opengate_core/opengate_lib/digitizer/GateDigitizerSpatialBlurringActor.cpp b/core/opengate_core/opengate_lib/digitizer/GateDigitizerSpatialBlurringActor.cpp index 1ffa4e209..d0187c311 100644 --- a/core/opengate_core/opengate_lib/digitizer/GateDigitizerSpatialBlurringActor.cpp +++ b/core/opengate_core/opengate_lib/digitizer/GateDigitizerSpatialBlurringActor.cpp @@ -108,6 +108,13 @@ void GateDigitizerSpatialBlurringActor::BlurCurrentThreeVectorValue() { l.fNavigator->LocateGlobalPointAndUpdateTouchable(vec, &fTouchableHistory); auto vid = GateUniqueVolumeID::New(&fTouchableHistory); phys_vol = vid->GetVolumeDepthID().back().fVolume; + // If the volume is parameterised, we consider the parent volume to compute + // the extent (otherwise the keep in solid will consider one single instance + // of the repeated solid, instead of the whole parameterised volume). + if (phys_vol->IsParameterised()) { + auto n = vid->GetVolumeDepthID().size(); + phys_vol = vid->GetVolumeDepthID()[n - 2].fVolume; + } } // consider local position From 3a708d17c21cc4464de1443110a0463c4029e3fd Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Sep 2024 09:01:10 +0200 Subject: [PATCH 077/183] Update actor properties and improve digitizer documentation Replaced `mother` and `output` properties with `attached_to` and `output_filename`. Added detailed descriptions and common features for digitizer actors, improving clarity and consistency in the user guide. --- docs/source/user_guide_2_4_actors.md | 47 ++++++++++++++++------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/docs/source/user_guide_2_4_actors.md b/docs/source/user_guide_2_4_actors.md index f15dd7484..5a2c9816c 100644 --- a/docs/source/user_guide_2_4_actors.md +++ b/docs/source/user_guide_2_4_actors.md @@ -34,8 +34,8 @@ Several tests depict usage of DoseActor: test008, test009, test021, test035, etc ````python dose = sim.add_actor("DoseActor", "dose") -dose.output = output_path / "test008-edep.mhd" -dose.mother = "waterbox" +dose.output_filename = output_path / "test008-edep.mhd" +dose.attached_to = "waterbox" dose.size = [99, 99, 99] mm = gate.g4_units.mm dose.spacing = [2 * mm, 2 * mm, 2 * mm] @@ -50,7 +50,7 @@ A PhaseSpaceActor stores any set of particles reaching a given volume during the ```python phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") -phsp.mother = plane.name +phsp.attached_to = plane.name phsp.attributes = [ "KineticEnergy", "Weight", @@ -64,7 +64,7 @@ phsp.attributes = [ "LocalTime", "EventPosition", ] -phsp.output = "test019_hits.root" +phsp.output_filename = "test019_hits.root" f = sim.add_filter("ParticleFilter", "f") f.particle = "gamma" phsp.filters.append(f) @@ -105,7 +105,14 @@ Note: all three conditions may be combined (if one condition is True, the partic ### Hits-related actors (digitizer) -In legacy Gate, the digitizer module is a set of tools used to simulate the behaviour of the scanner detectors and signal processing chain. The tools consider a list of interactions occurring in the detector (e.g. in the crystal), named as "hits collections". Then, this collection of hits is processed and filtered by different modules to end up with a final digital value. To start a digitizer chain, we must start defining a `HitsCollectionActor`, explained in the next sections. +The digitizer module is a set of tools used to simulate the behaviour of the scanner detectors and signal processing chain. The tools consider a list of interactions occurring in the detector (e.g. in the crystal), named as "hits collections". This collection of hits is then processed and filtered by different modules to produce a final digital value. To initiate a digitizer chain, you begin by defining a `HitsCollectionActor`, as explained in the following sections. + +Common features of all digitizer actors: + +- Most digitizers have a root output (with the exception for `DigitizerProjectionActor`, which outputs an image). This output can be written to disk with `my_digitizer.root_output.write_to_disk = True`. Multiple digitizers can share the same root output file, with each storing data in a separate branch named after the actor. + +- `authorize_repeated_volumes`: Set this to True if you want the digitizer to work with repeated volumes. This is useful, for example, when the digitizer is attached to a region with repeated crystal volumes (as in a PET system). However, if you are repeating some SPECT heads, you may not want the digitizer to record hits from both heads in the same file (in which case, set the flag to False). + #### DigitizerHitsCollectionActor @@ -113,13 +120,13 @@ The `DigitizerHitsCollectionActor` is an actor that collects hits occurring in a ```python hc = sim.add_actor('DigitizerHitsCollectionActor', 'Hits') -hc.mother = ['crystal1', 'crystal2'] -hc.output = 'test_hits.root' +hc.attached_to = ['crystal1', 'crystal2'] +hc.output_filename = 'test_hits.root' hc.attributes = ['TotalEnergyDeposit', 'KineticEnergy', 'PostPosition', 'CreatorProcess', 'GlobalTime', 'VolumeName', 'RunID', 'ThreadID', 'TrackID'] ``` -In this example, the actor is attached (`mother` option) to several volumes (`crystal1` and `crystal2` ) but most of the time, one single volume is sufficient. This volume is important: every time an interaction (a step) is occurring in this volume, a hit will be created. The list of attributes is defined with the given array of attribute names. The names of the attributes are as close as possible to the Geant4 terminology. They can be of a few types: 3 (ThreeVector), D (double), S (string), I (int), U (unique volume ID, see DigitizerAdderActor section). The list of available attributes is defined in the file `core/opengate_core/opengate_lib/GateDigiAttributeList.cpp` and can be printed with: +In this example, the actor is attached (`attached_to` option) to several volumes (`crystal1` and `crystal2` ) but most of the time, one single volume is sufficient. This volume is important: every time an interaction (a step) is occurring in this volume, a hit will be created. The list of attributes is defined with the given array of attribute names. The names of the attributes are as close as possible to the Geant4 terminology. They can be of a few types: 3 (ThreeVector), D (double), S (string), I (int), U (unique volume ID, see `DigitizerAdderActor` section). The list of available attributes is defined in the file `core/opengate_core/opengate_lib/GateDigiAttributeList.cpp` and can be printed with: ```python import opengate_core as gate_core @@ -192,29 +199,29 @@ The two actors used to convert some `hits` to one `digi` are "DigitizerHitsAdder This actor groups the hits per different volumes according to the option `group_volume` (by default, this is the deeper volume that contains the hit). All hits (in the same event) occurring in the same volume are gathered into one single digi according to one of the two available policies: - EnergyWeightedCentroidPosition: - - the final energy ("TotalEnergyDeposit") is the sum of all deposited energy - - the position ("PostPosition") is the energy-weighted centroid position - - the time ("GlobalTime") is the time of the earliest hit + - the final energy (`TotalEnergyDeposit`") is the sum of all deposited energy + - the position (`PostPosition`) is the energy-weighted centroid position + - the time (`GlobalTime`) is the time of the earliest hit - EnergyWinnerPosition - - the final energy ("TotalEnergyDeposit") is the energy of the hit with the largest deposited energy - - the position ("PostPosition") is the position of the hit with the largest deposited energy - - the time ("GlobalTime") is the time of the earliest hit + - the final energy (`TotalEnergyDeposit`) is the energy of the hit with the largest deposited energy + - the position (`PostPosition`) is the position of the hit with the largest deposited energy + - the time (`GlobalTime`) is the time of the earliest hit ```python sc = sim.add_actor("DigitizerAdderActor", "Singles") -sc.output = 'test_hits.root' +sc.output_filename = 'test_hits.root' sc.input_digi_collection = "Hits" sc.policy = "EnergyWeightedCentroidPosition" # sc.policy = "EnergyWinnerPosition" sc.group_volume = crystal.name ``` -Note that this actor is only triggered at the end of an event, so the `mother` volume to which it is attached has no effect. Examples are available in test 037. +Note that this actor is only triggered at the end of an event, so the `attached_to` volume to which it is attached has no effect. Examples are available in test 037. #### DigitizerReadoutActor -This actor is the same as the previous one (DigitizerHitsAdderActor) with one additional option: the resulting positions of the digi are set in the center of the defined volumes (discretized). We keep two different actors (Adder and Readout) to be close to the previous legacy GATE versions. The additional option `discretize_volume` indicates the volume name in which the discrete position will be taken. +This actor is the same as the previous one (`DigitizerHitsAdderActor`) with one additional option: the resulting positions of the digi are set in the center of the defined volumes (discretized). We keep two different actors (Adder and Readout) to be close to the previous legacy GATE versions. The additional option `discretize_volume` indicates the volume name in which the discrete position will be taken. ```python sc = sim.add_actor("HitsReadoutActor", "Singles") @@ -239,7 +246,7 @@ For Linear: `blur_reference_value`, `blur_reference_value` and `blur_slope` EQU ```python bc = sim.add_actor("DigitizerBlurringActor", "Singles_with_blur") -bc.output = "output.root" +bc.output_filename = "output.root" bc.input_digi_collection = "Singles_readout" bc.blur_attribute = "GlobalTime" bc.blur_method = "Gaussian" @@ -306,7 +313,7 @@ As parameters, Coincidence Sorter expects as input: #### Policies -When more than two singles are found in coincidence, several type of behavior could be implemented. GATE allows to model 5 different policies to treat multiple coincidences that can be used. Mutliple coincidences or "multicoincidence" are composed of at least three singles detected in the same **time window** that could form coincidence. The list of policies along with their explanation are given in Table below. The 5 policies, same as in [Gate9.X](https://opengate.readthedocs.io/en/latest/digitizer_and_detector_modeling.html#id43), were selected for the implementation as the most used. If an option that you need is missing, please, don't hesitate to report it in [Issues](https://github.com/OpenGATE/opengate/issues). +When more than two singles are found in coincidence, several type of behavior could be implemented. GATE allows to model 5 different policies to treat multiple coincidences that can be used. Multiple coincidences or "multicoincidence" are composed of at least three singles detected in the same **time window** that could form coincidence. The list of policies along with their explanation are given in Table below. The 5 policies, same as in [Gate9.X](https://opengate.readthedocs.io/en/latest/digitizer_and_detector_modeling.html#id43), were selected for the implementation as the most used. If an option that you need is missing, please, don't hesitate to report it in [Issues](https://github.com/OpenGATE/opengate/issues). **Available multiple policies and associated meaning**. When a multiple coincidence involving n *singles* is processed, it is first decomposed into a list of n·(n−1) pairs which are analyzed individually. @@ -368,7 +375,7 @@ The Compton splitting actor generates N particles, each with a weight equal to t ```python compt_splitting_actor = sim.add_actor("ComptSplittingActor", "ComptSplitting") -compt_splitting_actor.mother = W_tubs.name +compt_splitting_actor.attached_to = W_tubs.name compt_splitting_actor.splitting_factor = nb_split compt_splitting_actor.russian_roulette = True compt_splitting_actor.rotation_vector_director = True From 2ce472949b6c1a65956f67fe194912cf199c54bc Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Sep 2024 09:01:45 +0200 Subject: [PATCH 078/183] Add compare_root4 for some tests --- opengate/tests/utility.py | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index c32854fb8..7bef4284a 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -884,6 +884,60 @@ def compare_root3( return is_ok +def compare_root4( + ref_root_filename, + root_filename, + attributes, + branch1, + branch2=None, + img="root.png", + hits_tol=6, + nb_bins=200, +): + + keys1 = [] + keys2 = [] + tols = [] + scalings1 = [] + scalings2 = [] + for k, att in attributes.items(): + keys1.append(k) + if "key" in att: + keys2.append(att["key"]) + else: + keys2.append(k) + if "tol" in att: + tols.append(att["tol"]) + else: + tols.append(0.2) + if "scaling1" in att: + scalings1.append(att["scaling1"]) + else: + scalings1.append(1.0) + if "scaling2" in att: + scalings2.append(att["scaling2"]) + else: + scalings2.append(1.0) + + if branch2 is None: + branch2 = branch1 + + return compare_root3( + ref_root_filename, + root_filename, + branch1, + branch2, + keys1, + keys2, + tols, + scalings1, + scalings2, + img, + hits_tol, + nb_bins, + ) + + def open_root_as_np(root_file, tree_name): a = uproot.open(root_file)[tree_name] n = a.num_entries @@ -1900,3 +1954,10 @@ def plot_compare_profile(ref_names, test_names, options): # Adjust spacing between subplots if necessary plt.tight_layout() return plt + + +class RootComparison: + + def __init__(self, ref_filename, filename): + self.root_ref = uproot.open(ref_filename) + self.root_cmp = uproot.open(filename) From c39cc7d40dd0f4bd8d8e2a8882fcea630ed45b21 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Sep 2024 10:41:22 +0200 Subject: [PATCH 079/183] Rename statistical fields for consistency Updated field names for statistics in multiple test files and the GateSimulationStatisticsActor class for better consistency. Changed 'run_count', 'event_count', 'track_count', and 'step_count' to 'runs', 'events', 'tracks', and 'steps' respectively. Also, add setter_hook for output_filename, set write_to_disk to True if the filename is not "auto" (default) --- .../GateSimulationStatisticsActor.cpp | 22 +++-- opengate/actors/miscactors.py | 25 +++--- opengate/tests/src/test004_simple_mt.py | 2 +- .../tests/src/test004_simple_visu_gdml.py | 2 +- opengate/tests/src/test006_runs.py | 8 +- opengate/tests/src/test007_volumes.py | 8 +- opengate/tests/src/test009_voxels_dynamic.py | 2 +- opengate/tests/src/test011_mt.py | 2 +- opengate/tests/src/test012_mt_dose_actor.py | 2 +- opengate/tests/src/test014_engine_helpers.py | 2 +- ...019_linac_elekta_versa_with_mlc_rt_plan.py | 2 +- .../tests/src/test019_linac_phsp_helpers.py | 4 +- opengate/tests/src/test020_profiling.py | 2 +- opengate/tests/src/test021_voxel_source1.py | 2 +- opengate/tests/src/test021_voxel_source2.py | 2 +- .../tests/src/test021_voxel_source_old_wip.py | 2 +- opengate/tests/src/test022_half_life.py | 4 +- .../src/test025_hits_collection_helpers.py | 4 +- opengate/tests/src/test027_fake_spect_mt.py | 4 +- .../src/test028_ge_nm670_spect_1_colli.py | 4 +- .../test028_ge_nm670_spect_1_colli_visu.py | 4 +- .../src/test028_ge_nm670_spect_2_helpers.py | 6 +- ...t028_ge_nm670_spect_4_acc_angle_helpers.py | 22 ++--- .../src/test029_volume_time_rotation_1.py | 4 +- .../test029_volume_time_rotation_1_process.py | 4 +- .../src/test029_volume_time_rotation_2.py | 4 +- .../src/test033_rotation_spect_aa_helpers.py | 18 ++-- .../tests/src/test036_adder_depth_helpers.py | 4 +- .../src/test038_gan_phsp_spect_gan_aa.py | 4 +- .../src/test038_gan_phsp_spect_gan_helpers.py | 16 ++-- ...t038_gan_phsp_spect_training_dataset_mt.py | 2 +- .../src/test039_hits_memory_check_helpers.py | 2 +- .../tests/src/test040_gan_phsp_pet_gan.py | 16 ++-- opengate/tests/src/test043_garf.py | 2 +- opengate/tests/src/test043_garf_analog.py | 2 +- opengate/tests/src/test043_garf_mt.py | 4 +- .../src/test043_garf_training_dataset.py | 2 +- .../src/test049_pet_digit_blurring_v2_mt.py | 2 +- opengate/tests/src/test053_phid_helpers1.py | 2 +- opengate/tests/src/test056_template_source.py | 2 +- ...rtainty_flags_over_multiple_runs_mt_wip.py | 2 +- ...test066_stop_simulation_criteria_mt_WIP.py | 2 +- opengate/tests/src/test073_helpers.py | 2 +- opengate/tests/src/test076_progress_bar_mt.py | 2 +- opengate/tests/utility.py | 88 +++++++++++++------ 45 files changed, 173 insertions(+), 150 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateSimulationStatisticsActor.cpp b/core/opengate_core/opengate_lib/GateSimulationStatisticsActor.cpp index 00305168f..0f5ccfae7 100644 --- a/core/opengate_core/opengate_lib/GateSimulationStatisticsActor.cpp +++ b/core/opengate_core/opengate_lib/GateSimulationStatisticsActor.cpp @@ -54,18 +54,16 @@ void GateSimulationStatisticsActor::StartSimulationAction() { fStartRunTimeIsSet = false; // initialise the counts - fCounts["run_count"] = 0; - fCounts["event_count"] = 0; - fCounts["track_count"] = 0; - fCounts["step_count"] = 0; + fCounts["runs"] = 0; + fCounts["events"] = 0; + fCounts["tracks"] = 0; + fCounts["steps"] = 0; } py::dict GateSimulationStatisticsActor::GetCounts() { auto dd = py::dict( - "run_count"_a = fCounts["run_count"], - "event_count"_a = fCounts["event_count"], - "track_count"_a = fCounts["track_count"], - "step_count"_a = fCounts["step_count"], + "runs"_a = fCounts["runs"], "events"_a = fCounts["events"], + "tracks"_a = fCounts["tracks"], "steps"_a = fCounts["steps"], "duration"_a = fCountsD["duration"], "init"_a = fCountsD["init"], "start_time"_a = fCountsStr["start_time"], "stop_time"_a = fCountsStr["stop_time"], "track_types"_a = fTrackTypes); @@ -124,10 +122,10 @@ void GateSimulationStatisticsActor::EndOfSimulationWorkerAction( G4AutoLock mutex(&GateSimulationStatisticsActorMutex); threadLocal_t &data = threadLocalData.Get(); // merge all threads (need mutex) - fCounts["run_count"] += data.fRunCount; - fCounts["event_count"] += data.fEventCount; - fCounts["track_count"] += data.fTrackCount; - fCounts["step_count"] += data.fStepCount; + fCounts["runs"] += data.fRunCount; + fCounts["events"] += data.fEventCount; + fCounts["tracks"] += data.fTrackCount; + fCounts["steps"] += data.fStepCount; if (fTrackTypesFlag) { for (auto v : data.fTrackTypes) { if (fTrackTypes.count(v.first) == 0) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index b63e8ad20..6eb8fa544 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -8,7 +8,7 @@ from ..exception import warning -def _setter_hook_output_filename(self, output_filename): +def _setter_hook_stats_actor_output_filename(self, output_filename): # By default, write_to_disk is False. # However, if user actively sets the output_filename # s/he most likely wants to write to disk also @@ -40,6 +40,7 @@ class ActorOutputStatisticsActor(ActorOutputBase): "Relative paths and filenames are taken " "relative to the global simulation output folder " "set via the Simulation.output_dir option. ", + "setter_hook": _setter_hook_stats_actor_output_filename, }, ), "write_to_disk": ( @@ -57,10 +58,10 @@ def __init__(self, *args, **kwargs): # predefine the merged_data self.merged_data = Box() - self.merged_data.run_count = 0 - self.merged_data.event_count = 0 - self.merged_data.track_count = 0 - self.merged_data.step_count = 0 + self.merged_data.runs = 0 + self.merged_data.events = 0 + self.merged_data.tracks = 0 + self.merged_data.steps = 0 self.merged_data.duration = 0 self.merged_data.start_time = 0 self.merged_data.stop_time = 0 @@ -74,7 +75,7 @@ def __init__(self, *args, **kwargs): def pps(self): if self.merged_data.duration != 0: return int( - self.merged_data.event_count / (self.merged_data.duration / g4_units.s) + self.merged_data.events / (self.merged_data.duration / g4_units.s) ) else: return 0 @@ -83,7 +84,7 @@ def pps(self): def tps(self): if self.merged_data.duration != 0: return int( - self.merged_data.track_count / (self.merged_data.duration / g4_units.s) + self.merged_data.tracks / (self.merged_data.duration / g4_units.s) ) else: return 0 @@ -92,7 +93,7 @@ def tps(self): def sps(self): if self.merged_data.duration != 0: return int( - self.merged_data.step_count / (self.merged_data.duration / g4_units.s) + self.merged_data.steps / (self.merged_data.duration / g4_units.s) ) else: return 0 @@ -112,10 +113,10 @@ def get_data(self, **kwargs): def get_processed_output(self): d = {} - d["runs"] = {"value": self.merged_data.run_count, "unit": None} - d["events"] = {"value": self.merged_data.event_count, "unit": None} - d["tracks"] = {"value": self.merged_data.track_count, "unit": None} - d["steps"] = {"value": self.merged_data.step_count, "unit": None} + d["runs"] = {"value": self.merged_data.runs, "unit": None} + d["events"] = {"value": self.merged_data.events, "unit": None} + d["tracks"] = {"value": self.merged_data.tracks, "unit": None} + d["steps"] = {"value": self.merged_data.steps, "unit": None} val, unit = g4_best_unit_tuple(self.merged_data.init, "Time") d["init"] = { "value": val, diff --git a/opengate/tests/src/test004_simple_mt.py b/opengate/tests/src/test004_simple_mt.py index 0603831af..b155148d2 100755 --- a/opengate/tests/src/test004_simple_mt.py +++ b/opengate/tests/src/test004_simple_mt.py @@ -77,7 +77,7 @@ # gate_test4_simulation_stats_actor # Gate mac/main.mac stats_ref = utility.read_stat_file(paths.gate_output / "stat.txt") - stats_ref.counts.run_count = sim.number_of_threads + stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.01) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test004_simple_visu_gdml.py b/opengate/tests/src/test004_simple_visu_gdml.py index 882f6e859..9b6b5ee75 100755 --- a/opengate/tests/src/test004_simple_visu_gdml.py +++ b/opengate/tests/src/test004_simple_visu_gdml.py @@ -59,7 +59,7 @@ sim.run() stats = sim.get_actor("Stats") - stats.counts.run_count = 1 + stats.counts.runs = 1 # gate_test4_simulation_stats_actor # Gate mac/main.mac diff --git a/opengate/tests/src/test006_runs.py b/opengate/tests/src/test006_runs.py index a5e781a28..b9f00d80c 100755 --- a/opengate/tests/src/test006_runs.py +++ b/opengate/tests/src/test006_runs.py @@ -87,10 +87,10 @@ stats_ref = gate.actors.miscactors.SimulationStatisticsActor(name="stat_ref") c = stats_ref.counts - c.run_count = 3 - c.event_count = 7800 - c.track_count = 37584 # 56394 - c.step_count = 266582 # 217234 + c.runs = 3 + c.events = 7800 + c.tracks = 37584 # 56394 + c.steps = 266582 # 217234 # stats_ref.pps = 4059.6 3 3112.2 c.duration = 1 / 4059.6 * 7800 * sec print("-" * 80) diff --git a/opengate/tests/src/test007_volumes.py b/opengate/tests/src/test007_volumes.py index 1c2e8abd5..575aafaba 100755 --- a/opengate/tests/src/test007_volumes.py +++ b/opengate/tests/src/test007_volumes.py @@ -215,10 +215,10 @@ def check_mat(se): # check stats_ref = gate.actors.miscactors.SimulationStatisticsActor(name="ref") c = stats_ref.counts - c.run_count = 1 - c.event_count = 1280 - c.track_count = 17034 # 25668 - c.step_count = 78096 # 99465 + c.runs = 1 + c.events = 1280 + c.tracks = 17034 # 25668 + c.steps = 78096 # 99465 # stats_ref.pps = 506.6 sec = gate.g4_units.second c.duration = 2.5267 * sec diff --git a/opengate/tests/src/test009_voxels_dynamic.py b/opengate/tests/src/test009_voxels_dynamic.py index 7ebd7643d..0a1f4995f 100755 --- a/opengate/tests/src/test009_voxels_dynamic.py +++ b/opengate/tests/src/test009_voxels_dynamic.py @@ -119,7 +119,7 @@ # tests stats_ref = utility.read_stat_file(paths.gate_output / "stat.txt") - stats.counts.run_count = 1 + stats.counts.runs = 1 print( "Setting run count to 1, although more than 1 run was used in the simulation. " "This is to avoid a wrongly failing test." diff --git a/opengate/tests/src/test011_mt.py b/opengate/tests/src/test011_mt.py index 9eea2e7a2..1a84c3629 100755 --- a/opengate/tests/src/test011_mt.py +++ b/opengate/tests/src/test011_mt.py @@ -73,6 +73,6 @@ / "output" / "stat.txt" ) - stats_ref.counts.run_count = sim.number_of_threads * len(sim.run_timing_intervals) + stats_ref.counts.runs = sim.number_of_threads * len(sim.run_timing_intervals) is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.03) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test012_mt_dose_actor.py b/opengate/tests/src/test012_mt_dose_actor.py index 4c524b9f8..c904ab688 100755 --- a/opengate/tests/src/test012_mt_dose_actor.py +++ b/opengate/tests/src/test012_mt_dose_actor.py @@ -99,7 +99,7 @@ # tests stats_ref = utility.read_stat_file(paths.gate_output / "stat.txt") # change the number of run to the number of threads - stats_ref.counts.run_count = sim.number_of_threads + stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stat, stats_ref, 0.10) is_ok = ( diff --git a/opengate/tests/src/test014_engine_helpers.py b/opengate/tests/src/test014_engine_helpers.py index 48f410930..a500fd0c5 100644 --- a/opengate/tests/src/test014_engine_helpers.py +++ b/opengate/tests/src/test014_engine_helpers.py @@ -56,7 +56,7 @@ def test_output(sim): stats = sim.get_actor("Stats") print(stats) stats_ref = utility.read_stat_file(paths.gate_output / "stat.txt") - stats_ref.counts.run_count = sim.number_of_threads + stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.01) return is_ok diff --git a/opengate/tests/src/test019_linac_elekta_versa_with_mlc_rt_plan.py b/opengate/tests/src/test019_linac_elekta_versa_with_mlc_rt_plan.py index 3158c99f4..39a208978 100755 --- a/opengate/tests/src/test019_linac_elekta_versa_with_mlc_rt_plan.py +++ b/opengate/tests/src/test019_linac_elekta_versa_with_mlc_rt_plan.py @@ -245,6 +245,6 @@ def validation_test_19_rt_plan( l_cp[0], rt_plan_parameters, nb_part, - stats.counts.event_count, + stats.counts.events, ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test019_linac_phsp_helpers.py b/opengate/tests/src/test019_linac_phsp_helpers.py index bc06e6894..c9c8c5a31 100644 --- a/opengate/tests/src/test019_linac_phsp_helpers.py +++ b/opengate/tests/src/test019_linac_phsp_helpers.py @@ -134,8 +134,8 @@ def run_test019(sim): # check stats print() stats_ref = utility.read_stat_file(paths.gate_output / "output-writePhS-stat.txt") - print(f"Number of runs was {stats.counts.run_count}. Set to 1 before comparison") - stats.counts.run_count = 1 + print(f"Number of runs was {stats.counts.runs}. Set to 1 before comparison") + stats.counts.runs = 1 is_ok = utility.assert_stats(stats, stats_ref, 0.2) # compare the phsp tree diff --git a/opengate/tests/src/test020_profiling.py b/opengate/tests/src/test020_profiling.py index 48841c8b0..8574b60fa 100755 --- a/opengate/tests/src/test020_profiling.py +++ b/opengate/tests/src/test020_profiling.py @@ -98,7 +98,7 @@ # tests stats_ref = utility.read_stat_file(paths.gate / "output" / "stat_profiling.txt") - stats_ref.counts.run_count = sim.number_of_threads + stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, 0.1) is_ok = is_ok and utility.assert_images( paths.gate / "output" / "output_profiling-Edep.mhd", diff --git a/opengate/tests/src/test021_voxel_source1.py b/opengate/tests/src/test021_voxel_source1.py index f938f334e..5825faeb8 100755 --- a/opengate/tests/src/test021_voxel_source1.py +++ b/opengate/tests/src/test021_voxel_source1.py @@ -134,7 +134,7 @@ def t(s, v): is_ok = t(2000, v4) and is_ok stats_ref = utility.read_stat_file(paths.output_ref / "stat021_ref_1.txt") - stats_ref.counts.run_count = sim.number_of_threads + stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, 0.1) and is_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test021_voxel_source2.py b/opengate/tests/src/test021_voxel_source2.py index 479fb286b..00d94c167 100755 --- a/opengate/tests/src/test021_voxel_source2.py +++ b/opengate/tests/src/test021_voxel_source2.py @@ -143,7 +143,7 @@ is_ok = is_ok and b stats_ref = utility.read_stat_file(paths.output_ref / "stat021_ref_2.txt") - stats_ref.counts.run_count = sim.number_of_threads + stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, 0.1) and is_ok is_ok = ( diff --git a/opengate/tests/src/test021_voxel_source_old_wip.py b/opengate/tests/src/test021_voxel_source_old_wip.py index 113638065..5a210331a 100755 --- a/opengate/tests/src/test021_voxel_source_old_wip.py +++ b/opengate/tests/src/test021_voxel_source_old_wip.py @@ -174,7 +174,7 @@ def t(s, v): is_ok = t(0.8, v4) and is_ok stats_ref = utility.read_stat_file(paths.output_ref / "stat021_ref.txt") - stats_ref.counts.run_count = sim.number_of_threads + stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stat, stats_ref, 0.05) and is_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test022_half_life.py b/opengate/tests/src/test022_half_life.py index c922d3ee3..cca716a81 100755 --- a/opengate/tests/src/test022_half_life.py +++ b/opengate/tests/src/test022_half_life.py @@ -145,8 +145,8 @@ is_ok = is_ok and b # check thread - b = sim.number_of_threads * len(sim.run_timing_intervals) == stats.counts.run_count - utility.print_test(b, f"Number of run: {stats.counts.run_count}") + b = sim.number_of_threads * len(sim.run_timing_intervals) == stats.counts.runs + utility.print_test(b, f"Number of run: {stats.counts.runs}") is_ok = is_ok and b diff --git a/opengate/tests/src/test025_hits_collection_helpers.py b/opengate/tests/src/test025_hits_collection_helpers.py index 436b44da5..cbec4ba96 100644 --- a/opengate/tests/src/test025_hits_collection_helpers.py +++ b/opengate/tests/src/test025_hits_collection_helpers.py @@ -158,8 +158,8 @@ def branch_fill(att, step, touchable): def test_simulation_results(sim): # Compare stats file stats = sim.get_actor("Stats") - print(f"Number of runs was {stats.counts.run_count}. Set to 1 before comparison") - stats.counts.run_count = 1 # force to 1 to compare with gate result + print(f"Number of runs was {stats.counts.runs}. Set to 1 before comparison") + stats.counts.runs = 1 # force to 1 to compare with gate result stats_ref = utility.read_stat_file(paths.gate_output / "stat.txt") is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.05) diff --git a/opengate/tests/src/test027_fake_spect_mt.py b/opengate/tests/src/test027_fake_spect_mt.py index c7e9b5e65..066619496 100755 --- a/opengate/tests/src/test027_fake_spect_mt.py +++ b/opengate/tests/src/test027_fake_spect_mt.py @@ -108,8 +108,8 @@ gate.exception.warning("Compare stats") stats = sim.get_actor("Stats") print(stats) - print(f"Number of runs was {stats.counts.run_count}. Set to 1 before comparison") - stats.counts.run_count = 1 # force to 1 + print(f"Number of runs was {stats.counts.runs}. Set to 1 before comparison") + stats.counts.runs = 1 # force to 1 stats_ref = utility.read_stat_file(paths.gate_output / "stat.txt") is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.07) diff --git a/opengate/tests/src/test028_ge_nm670_spect_1_colli.py b/opengate/tests/src/test028_ge_nm670_spect_1_colli.py index 41bb998e2..225b879f0 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_1_colli.py +++ b/opengate/tests/src/test028_ge_nm670_spect_1_colli.py @@ -91,8 +91,8 @@ # stat gate.exception.warning("Compare stats") print(stats) - print(f"Number of runs was {stats.counts.run_count}. Set to 1 before comparison") - stats.counts.run_count = 1 # force to 1 + print(f"Number of runs was {stats.counts.runs}. Set to 1 before comparison") + stats.counts.runs = 1 # force to 1 stats_ref = utility.read_stat_file(paths.gate_output / "stat1.txt") is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.02) diff --git a/opengate/tests/src/test028_ge_nm670_spect_1_colli_visu.py b/opengate/tests/src/test028_ge_nm670_spect_1_colli_visu.py index 08391d0f8..c728c4fdc 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_1_colli_visu.py +++ b/opengate/tests/src/test028_ge_nm670_spect_1_colli_visu.py @@ -94,8 +94,8 @@ gate.exception.warning("Compare stats") stats = sim.get_actor("Stats") print(stats) - print(f"Number of runs was {stats.counts.run_count}. Set to 1 before comparison") - stats.counts.run_count = 1 # force to 1 + print(f"Number of runs was {stats.counts.runs}. Set to 1 before comparison") + stats.counts.runs = 1 # force to 1 stats_ref = utility.read_stat_file(paths.gate_output / "stat1.txt") is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.02) diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py index 9924a716e..cfdde0d37 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py @@ -197,8 +197,8 @@ def test_spect_hits(sim, paths, version="2"): gate.exception.warning("Compare stats") stats = sim.get_actor("Stats") print(stats) - print(f"Number of runs was {stats.counts.run_count}. Set to 1 before comparison") - stats.counts.run_count = 1 # force to 1 + print(f"Number of runs was {stats.counts.runs}. Set to 1 before comparison") + stats.counts.runs = 1 # force to 1 stats_ref = utility.read_stat_file(paths.gate_output / f"stat{version}.txt") is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.07) @@ -332,7 +332,7 @@ def test_spect_hits(sim, paths, version="2"): def test_spect_proj(sim, paths, proj, version="3"): print() stats = sim.get_actor("Stats") - stats.counts.run_count = 1 # force to 1 to compare with gate result + stats.counts.runs = 1 # force to 1 to compare with gate result print(stats) stats_ref = utility.read_stat_file(paths.gate_output / f"stat{version}.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.025) diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py index 1874bd091..1c338c7c0 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py @@ -199,7 +199,7 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8): b3 = gate.sources.generic.get_source_zero_events(sim, "beam3") print(f"Number of zeros events: {b1} {b2} {b3}") - print(f"Number of simulated events: {stats.counts.event_count}") + print(f"Number of simulated events: {stats.counts.events}") beam1 = sim.source_manager.get_source_info("beam1") mode = beam1.direction.acceptance_angle.skip_policy stats_ref = utility.read_stat_file(paths.gate_output / "stat4.txt") @@ -208,28 +208,28 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8): b1 = gate.sources.generic.get_source_skipped_events(sim, "beam1") b2 = gate.sources.generic.get_source_skipped_events(sim, "beam2") b3 = gate.sources.generic.get_source_skipped_events(sim, "beam3") - stats.counts.event_count = stats.counts.event_count + b1 + b2 + b3 + stats.counts.events = stats.counts.events + b1 + b2 + b3 print(f"Skip Events mode, adding the skipped ones") - print(f"Number of simulated events: {stats.counts.event_count}") + print(f"Number of simulated events: {stats.counts.events}") # do not compare track in this mode - stats.counts.track_count = stats_ref.counts.track_count + stats.counts.tracks = stats_ref.counts.tracks tol = 0.3 - r1 = b1 / stats.counts.event_count + r1 = b1 / stats.counts.events is_ok = (r1 - reference_ratio) / reference_ratio < tol utility.print_test( is_ok, f"Skipped particles b1 = {b1} {r1 * 100:.2f} % vs {reference_ratio * 100:.2f} % ", ) - r2 = b2 / stats.counts.event_count + r2 = b2 / stats.counts.events is_ok = (r2 - reference_ratio) / reference_ratio < tol utility.print_test( is_ok, f"Skipped particles b2 = {b2} {r2 * 100:.2f} % vs {reference_ratio * 100:.2f} % ", ) - r3 = b3 / stats.counts.event_count + r3 = b3 / stats.counts.events is_ok = (r3 - reference_ratio) / reference_ratio < tol utility.print_test( is_ok, @@ -239,12 +239,12 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8): # stat gate.exception.warning("Compare stats") print(stats) - print(f"Number of runs was {stats.counts.run_count}. Set to 1 before comparison") - stats.counts.run_count = 1 # force to 1 + print(f"Number of runs was {stats.counts.runs}. Set to 1 before comparison") + stats.counts.runs = 1 # force to 1 print( - f"Number of steps was {stats.counts.step_count}, force to the same value (because of angle acceptance). " + f"Number of steps was {stats.counts.steps}, force to the same value (because of angle acceptance). " ) - stats.counts.step_count = stats_ref.counts.step_count # force to id + stats.counts.steps = stats_ref.counts.steps # force to id is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.07) and is_ok # read image and force change the offset to be similar to old Gate diff --git a/opengate/tests/src/test029_volume_time_rotation_1.py b/opengate/tests/src/test029_volume_time_rotation_1.py index 33521d745..31a341b15 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1.py +++ b/opengate/tests/src/test029_volume_time_rotation_1.py @@ -38,9 +38,9 @@ print(stats) stats_ref = utility.read_stat_file(paths.output_ref / "stats029.txt") print( - f"Number of steps was {stats.counts.step_count}, forced to the same value (because of angle acceptance). " + f"Number of steps was {stats.counts.steps}, forced to the same value (because of angle acceptance). " ) - stats.counts.step_count = stats_ref.counts.step_count # force to id + stats.counts.steps = stats_ref.counts.steps # force to id is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.02) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_1_process.py b/opengate/tests/src/test029_volume_time_rotation_1_process.py index 4684c148b..13e3edd66 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1_process.py +++ b/opengate/tests/src/test029_volume_time_rotation_1_process.py @@ -28,9 +28,9 @@ print(stats) stats_ref = utility.read_stat_file(paths.output_ref / "stats029.txt") print( - f"Number of steps was {stats.counts.step_count}, forced to the same value (because of angle acceptance). " + f"Number of steps was {stats.counts.steps}, forced to the same value (because of angle acceptance). " ) - stats.counts.step_count = stats_ref.counts.step_count # force to id + stats.counts.steps = stats_ref.counts.steps # force to id is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.02) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_2.py b/opengate/tests/src/test029_volume_time_rotation_2.py index e2f812005..d3a9b4ac0 100755 --- a/opengate/tests/src/test029_volume_time_rotation_2.py +++ b/opengate/tests/src/test029_volume_time_rotation_2.py @@ -38,9 +38,9 @@ print(stats) stats_ref = utility.read_stat_file(paths.output_ref / "stats029.txt") print( - f"Number of steps was {stats.counts.step_count}, forced to the same value (because of angle acceptance). " + f"Number of steps was {stats.counts.steps}, forced to the same value (because of angle acceptance). " ) - stats.counts.step_count = stats_ref.counts.step_count # force these to be identical + stats.counts.steps = stats_ref.counts.steps # force these to be identical is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.01) print(is_ok) diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index fc38bcd63..a291da278 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -163,18 +163,18 @@ def evaluate_test(sim, sources, itol, ref_skipped): # check stats gate.exception.warning(f"Check stats") stats_ref = utility.read_stat_file(paths.output_ref / "test033_stats.txt") - print(f"Steps counts not compared (was {stats.counts.step_count})") + print(f"Steps counts not compared (was {stats.counts.steps})") nbt = sim.number_of_threads - stats.counts.step_count = stats_ref.counts.step_count - stats_ref.counts.run_count *= nbt + stats.counts.steps = stats_ref.counts.steps + stats_ref.counts.runs *= nbt if se > 0: - print(f"Track counts not compared (was {stats.counts.track_count})") - print(f"Modify Events + skipped {stats.counts.event_count + se})") - stats.counts.event_count += se - stats.counts.track_count = stats_ref.counts.track_count + print(f"Track counts not compared (was {stats.counts.tracks})") + print(f"Modify Events + skipped {stats.counts.events + se})") + stats.counts.events += se + stats.counts.tracks = stats_ref.counts.tracks if ze > 0: - print(f"Track counts not compared (was {stats.counts.track_count})") - stats.counts.track_count = stats_ref.counts.track_count + print(f"Track counts not compared (was {stats.counts.tracks})") + stats.counts.tracks = stats_ref.counts.tracks is_ok = utility.assert_stats(stats, stats_ref, 0.03) and is_ok # compare edep map diff --git a/opengate/tests/src/test036_adder_depth_helpers.py b/opengate/tests/src/test036_adder_depth_helpers.py index ede0281ca..e56185600 100644 --- a/opengate/tests/src/test036_adder_depth_helpers.py +++ b/opengate/tests/src/test036_adder_depth_helpers.py @@ -172,8 +172,8 @@ def test_output(sim, paths): gate.exception.warning("Compare stats") stats = sim.actor_manager.get_actor("Stats") print(stats) - print(f"Number of runs was {stats.counts.run_count}. Set to 1 before comparison") - stats.counts.run_count = 1 # force to 1 + print(f"Number of runs was {stats.counts.runs}. Set to 1 before comparison") + stats.counts.runs = 1 # force to 1 stats_ref = utility.read_stat_file(paths.gate_output / "stats.txt") is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.07) diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py index 9cbac2ef1..9185fe78c 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py @@ -55,10 +55,10 @@ stats = sim.get_actor("Stats") stats_ref = utility.read_stat_file(paths.output_ref / "test038_gan_aa_stats.txt") # do not compare steps - stats_ref.counts.step_count = stats.counts.step_count + stats_ref.counts.steps = stats.counts.steps is_ok = utility.assert_stats(stats, stats_ref, 0.02) and is_ok - stats.counts.event_count += s.fTotalSkippedEvents + stats.counts.events += s.fTotalSkippedEvents print("Number of events is increased by the nb of skipped events") print(stats) diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_helpers.py b/opengate/tests/src/test038_gan_phsp_spect_gan_helpers.py index 171a6cee9..d0f549a20 100644 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_helpers.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_helpers.py @@ -220,18 +220,16 @@ def analyze_results(sim, paths, all_cond): stats = sim.get_actor("Stats") print(stats) - stats.counts.event_count += s.fTotalSkippedEvents + stats.counts.events += s.fTotalSkippedEvents stats_ref = utility.read_stat_file(paths.output_ref / "test038_ref_stats.txt") - r = ( - stats_ref.counts.step_count - stats.counts.step_count - ) / stats_ref.counts.step_count - print(f"Steps cannot be compared => was {stats.counts.step_count}, {r:.2f}%") - stats.counts.step_count = stats_ref.counts.step_count + r = (stats_ref.counts.steps - stats.counts.steps) / stats_ref.counts.steps + print(f"Steps cannot be compared => was {stats.counts.steps}, {r:.2f}%") + stats.counts.steps = stats_ref.counts.steps if s.fTotalSkippedEvents > 0: - print(f"Tracks cannot be compared => was {stats.counts.track_count}") - stats.counts.track_count = stats_ref.counts.track_count + print(f"Tracks cannot be compared => was {stats.counts.tracks}") + stats.counts.tracks = stats_ref.counts.tracks - stats.counts.run_count = 1 # force for MT + stats.counts.runs = 1 # force for MT is_ok = utility.assert_stats(stats, stats_ref, 0.10) # save conditional for checking with reference cond diff --git a/opengate/tests/src/test038_gan_phsp_spect_training_dataset_mt.py b/opengate/tests/src/test038_gan_phsp_spect_training_dataset_mt.py index bbba65327..ff376894d 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_training_dataset_mt.py +++ b/opengate/tests/src/test038_gan_phsp_spect_training_dataset_mt.py @@ -119,7 +119,7 @@ stats = sim.get_actor("Stats") print(stats) stats_ref = utility.read_stat_file(paths.output_ref / "test038_train_stats.txt") - stats.counts.run_count = 1 + stats.counts.runs = 1 is_ok = utility.assert_stats(stats, stats_ref, 0.02) # check phsp diff --git a/opengate/tests/src/test039_hits_memory_check_helpers.py b/opengate/tests/src/test039_hits_memory_check_helpers.py index ae20466b7..c0977b154 100644 --- a/opengate/tests/src/test039_hits_memory_check_helpers.py +++ b/opengate/tests/src/test039_hits_memory_check_helpers.py @@ -186,7 +186,7 @@ def test_results(sim, paths): # stats.write(paths.output_ref / 'test039_stats.txt') print(stats) stats_ref = utility.read_stat_file(paths.output_ref / "test039_stats.txt") - stats.counts.run_count = 2 # sim.number_of_threads + stats.counts.runs = 2 # sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, 0.05) # Compare singles diff --git a/opengate/tests/src/test040_gan_phsp_pet_gan.py b/opengate/tests/src/test040_gan_phsp_pet_gan.py index 65a8bc0f1..30963a1ff 100755 --- a/opengate/tests/src/test040_gan_phsp_pet_gan.py +++ b/opengate/tests/src/test040_gan_phsp_pet_gan.py @@ -216,16 +216,12 @@ def gen_cond(n): print(stats) stats_ref = utility.read_stat_file(paths.output_ref / "test040_ref_stats.txt") - r = ( - stats_ref.counts.step_count - stats.counts.step_count - ) / stats_ref.counts.step_count - print(f"!!! Steps cannot be compared => was {stats.counts.step_count}, {r:.2f}%") - stats.counts.step_count = stats_ref.counts.step_count - r = ( - stats_ref.counts.track_count - stats.counts.track_count - ) / stats_ref.counts.track_count - print(f"!!! Tracks cannot be compared => was {stats.counts.track_count}, {r:.2f}%") - stats.counts.track_count = stats_ref.counts.track_count + r = (stats_ref.counts.steps - stats.counts.steps) / stats_ref.counts.steps + print(f"!!! Steps cannot be compared => was {stats.counts.steps}, {r:.2f}%") + stats.counts.steps = stats_ref.counts.steps + r = (stats_ref.counts.tracks - stats.counts.tracks) / stats_ref.counts.tracks + print(f"!!! Tracks cannot be compared => was {stats.counts.tracks}, {r:.2f}%") + stats.counts.tracks = stats_ref.counts.tracks is_ok = utility.assert_stats(stats, stats_ref, 0.10) # save conditional for checking with reference cond diff --git a/opengate/tests/src/test043_garf.py b/opengate/tests/src/test043_garf.py index 100dbc9c8..fb092405e 100755 --- a/opengate/tests/src/test043_garf.py +++ b/opengate/tests/src/test043_garf.py @@ -121,7 +121,7 @@ gate.exception.warning("Tests stats file") stats_ref = utility.read_stat_file(test43.paths.gate_output / "stats_analog.txt") # dont compare steps of course - stats_ref.counts.step_count = stat.counts.step_count + stats_ref.counts.steps = stat.counts.steps is_ok = utility.assert_stats(stat, stats_ref, 0.01) print() diff --git a/opengate/tests/src/test043_garf_analog.py b/opengate/tests/src/test043_garf_analog.py index 31f983c88..c007214fc 100755 --- a/opengate/tests/src/test043_garf_analog.py +++ b/opengate/tests/src/test043_garf_analog.py @@ -79,7 +79,7 @@ print() gate.exception.warning("Tests stats file") stats_ref = utility.read_stat_file(test43.paths.gate_output / "stats_analog.txt") - stats.counts.run_count = 1 # force to one run (ref only have 1 thread) + stats.counts.runs = 1 # force to one run (ref only have 1 thread) is_ok = utility.assert_stats(stats, stats_ref, 0.01) print() diff --git a/opengate/tests/src/test043_garf_mt.py b/opengate/tests/src/test043_garf_mt.py index 402c0d24e..99f557f45 100755 --- a/opengate/tests/src/test043_garf_mt.py +++ b/opengate/tests/src/test043_garf_mt.py @@ -108,8 +108,8 @@ gate.exception.warning("Tests stats file") stats_ref = utility.read_stat_file(test43.paths.gate_output / "stats_analog.txt") # dont compare steps of course - stats_ref.counts.step_count = stats.counts.step_count - stats_ref.counts.run_count = 3 + stats_ref.counts.steps = stats.counts.steps + stats_ref.counts.runs = 3 is_ok = utility.assert_stats(stats, stats_ref, 0.01) print() diff --git a/opengate/tests/src/test043_garf_training_dataset.py b/opengate/tests/src/test043_garf_training_dataset.py index 57cbf2a8c..a52afc427 100755 --- a/opengate/tests/src/test043_garf_training_dataset.py +++ b/opengate/tests/src/test043_garf_training_dataset.py @@ -105,7 +105,7 @@ print_test( is_ok, f"Nb of skip particles {skip} (vs {ref_skip}) " - f"{(skip / stats.counts.event_count) * 100:.2f}%", + f"{(skip / stats.counts.events) * 100:.2f}%", ) # ---------------------------------------------------------------------------------------------------------------- diff --git a/opengate/tests/src/test049_pet_digit_blurring_v2_mt.py b/opengate/tests/src/test049_pet_digit_blurring_v2_mt.py index 55f3cccf4..ba596060e 100755 --- a/opengate/tests/src/test049_pet_digit_blurring_v2_mt.py +++ b/opengate/tests/src/test049_pet_digit_blurring_v2_mt.py @@ -42,7 +42,7 @@ gate.exception.warning(f"Check stats") p = paths.gate_output stats_ref = utility.read_stat_file(p / "stats.txt") - stats_ref.counts.run_count = nb_threads + stats_ref.counts.runs = nb_threads is_ok = utility.assert_stats(stats, stats_ref, 0.025) # check root hits diff --git a/opengate/tests/src/test053_phid_helpers1.py b/opengate/tests/src/test053_phid_helpers1.py index 8cd4d807c..7f69bb4d6 100644 --- a/opengate/tests/src/test053_phid_helpers1.py +++ b/opengate/tests/src/test053_phid_helpers1.py @@ -243,7 +243,7 @@ def analyse(paths, sim, output, ion_name, z, a, daughters, log_flag=True, tol=0. print("Data from MC, normalized by nb events") phsp = sim.get_actor("phsp") g2_ene, g2_w = analyse_ion_gamma_from_root( - phsp.get_output_path(), daughters, stats.counts.event_count + phsp.get_output_path(), daughters, stats.counts.events ) # direct computation of gammas diff --git a/opengate/tests/src/test056_template_source.py b/opengate/tests/src/test056_template_source.py index d6913eb32..944003656 100755 --- a/opengate/tests/src/test056_template_source.py +++ b/opengate/tests/src/test056_template_source.py @@ -67,5 +67,5 @@ # get results print(stats) - is_ok = stats.counts.event_count = 666 + is_ok = stats.counts.events = 666 utility.test_ok(is_ok) diff --git a/opengate/tests/src/test058_uncertainty_flags_over_multiple_runs_mt_wip.py b/opengate/tests/src/test058_uncertainty_flags_over_multiple_runs_mt_wip.py index 387bdac0d..5ea8a527e 100755 --- a/opengate/tests/src/test058_uncertainty_flags_over_multiple_runs_mt_wip.py +++ b/opengate/tests/src/test058_uncertainty_flags_over_multiple_runs_mt_wip.py @@ -133,7 +133,7 @@ def run_simulation(n_runs, n_part_tot, n_threads, uncertainty_type="uncertainty" # test that the simulation didn't stop because we reached the planned number of runs stats_ref = utility.read_stat_file(paths.output / "stats066.txt") n_runs_planned = len(run_timing_intervals) * n_threads - n_effective_runs = stats_ref.counts.run_count + n_effective_runs = stats_ref.counts.runs print(f"{n_runs_planned = }") print(f"{n_effective_runs = }") diff --git a/opengate/tests/src/test066_stop_simulation_criteria_mt_WIP.py b/opengate/tests/src/test066_stop_simulation_criteria_mt_WIP.py index 8e17edd5b..1fafe0a74 100755 --- a/opengate/tests/src/test066_stop_simulation_criteria_mt_WIP.py +++ b/opengate/tests/src/test066_stop_simulation_criteria_mt_WIP.py @@ -170,7 +170,7 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): # test that the simulation didn't stop because we reached the planned number of runs stats_ref = utility.read_stat_file(paths.output / "stats066.txt") n_runs_planned = len(run_timing_intervals) * n_threads - n_effective_runs = stats_ref.counts.run_count + n_effective_runs = stats_ref.counts.runs print(f"{n_runs_planned = }") print(f"{n_effective_runs = }") ok = ok and n_effective_runs < n_runs_planned diff --git a/opengate/tests/src/test073_helpers.py b/opengate/tests/src/test073_helpers.py index 56658c98b..00c412b6f 100644 --- a/opengate/tests/src/test073_helpers.py +++ b/opengate/tests/src/test073_helpers.py @@ -99,7 +99,7 @@ def compare_stats(sim, filename): stats = sim.get_actor("stats") # force nb of thread to 1 stats_ref = utility.read_stat_file(filename) - stats.counts.run_count = stats_ref.counts.run_count + stats.counts.runs = stats_ref.counts.runs is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.01) return is_ok diff --git a/opengate/tests/src/test076_progress_bar_mt.py b/opengate/tests/src/test076_progress_bar_mt.py index d0a218051..cdaf0d318 100755 --- a/opengate/tests/src/test076_progress_bar_mt.py +++ b/opengate/tests/src/test076_progress_bar_mt.py @@ -86,7 +86,7 @@ # Comparison with gate simulation n1 = sim.expected_number_of_events - n2 = stats.counts.event_count + n2 = stats.counts.events f = abs(n1 - n2) / n2 is_ok = f < 0.01 print() diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index 7bef4284a..9c1cf2487 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -1,3 +1,4 @@ +import json import itk import numpy as np import os @@ -22,7 +23,6 @@ ) from ..exception import fatal, color_error, color_ok from ..image import get_info_from_image, itk_image_from_array, write_itk_image -from ..userinfo import UserInfo from ..actors.miscactors import SimulationStatisticsActor plt = LazyModuleLoader("matplotlib.pyplot") @@ -49,7 +49,25 @@ def test_ok(is_ok=False, exceptions=None): sys.exit(-1) -def read_stat_file(filename): +def read_stat_file(filename, encoder="legacy"): + if encoder == "json": + return read_stat_file_json(filename) + return read_stat_file_legacy(filename) + + +def read_stat_file_json(filename): + with open(filename, "r") as f: + data = json.load(f) + r = "".join(random.choices(string.ascii_lowercase + string.digits, k=20)) + counts = {} + for k, d in data.items(): + counts[k] = d["value"] + stat = SimulationStatisticsActor(name=r) + stat.user_output.stats.store_data(counts) + return stat + + +def read_stat_file_legacy(filename): p = os.path.abspath(filename) with open(p, "r") as f: lines = f.readlines() @@ -59,13 +77,13 @@ def read_stat_file(filename): read_track = False for line in lines: if "NumberOfRun" in line: - counts.run_count = int(line[len("# NumberOfRun =") :]) + counts.runs = int(line[len("# NumberOfRun =") :]) if "NumberOfEvents" in line: - counts.event_count = int(line[len("# NumberOfEvents = ") :]) + counts.events = int(line[len("# NumberOfEvents = ") :]) if "NumberOfTracks" in line: - counts.track_count = int(line[len("# NumberOfTracks =") :]) + counts.tracks = int(line[len("# NumberOfTracks =") :]) if "NumberOfSteps" in line: - counts.step_count = int(line[len("# NumberOfSteps =") :]) + counts.steps = int(line[len("# NumberOfSteps =") :]) sec = g4_units.s if "ElapsedTimeWoInit" in line: counts.duration = float(line[len("# ElapsedTimeWoInit =") :]) * sec @@ -100,21 +118,33 @@ def print_test(b, s): return b -def assert_stats(stats_actor_1, stats_actor_2, tolerance=0, is_ok=True): - output1 = stats_actor_1.user_output.stats - output2 = stats_actor_2.user_output.stats +def assert_stats(stats_actor_1, stats_actor_2, tolerance=0): + return assert_stats_json( + stats_actor_1.user_output.stats, + stats_actor_2.user_output.stats, + tolerance, + track_types_flag=stats_actor_1.track_types_flag, + ) + + +def assert_stats_json(stats_actor_1, stats_actor_2, tolerance=0, track_types_flag=None): + output1 = stats_actor_1 # .user_output.stats + output2 = stats_actor_2 # .user_output.stats + if track_types_flag is None: + track_types_flag = len(output1.track_types) > 0 + counts1 = output1.merged_data counts2 = output2.merged_data - if counts2.event_count != 0: - event_d = counts1.event_count / counts2.event_count * 100 - 100 + if counts2.events != 0: + event_d = counts1.events / counts2.events * 100 - 100 else: event_d = 100 - if counts2.track_count != 0: - track_d = counts1.track_count / counts2.track_count * 100 - 100 + if counts2.tracks != 0: + track_d = counts1.tracks / counts2.tracks * 100 - 100 else: track_d = 100 - if counts1.step_count != 0: - step_d = counts1.step_count / counts2.step_count * 100 - 100 + if counts1.steps != 0: + step_d = counts1.steps / counts2.steps * 100 - 100 else: step_d = 100 if output2.pps != 0: @@ -132,30 +162,30 @@ def assert_stats(stats_actor_1, stats_actor_2, tolerance=0, is_ok=True): else: sps_d = 100 - b = counts1.run_count == counts2.run_count - is_ok = b and is_ok - print_test(b, f"Runs: {counts1.run_count} {counts2.run_count}") + b = counts1.runs == counts2.runs + is_ok = b + print_test(b, f"Runs: {counts1.runs} {counts2.runs}") b = abs(event_d) <= tolerance * 100 is_ok = b and is_ok st = f"(tol = {tolerance * 100:.2f} %)" print_test( b, - f"Events: {counts1.event_count} {counts2.event_count} : {event_d:+.2f} % {st}", + f"Events: {counts1.events} {counts2.events} : {event_d:+.2f} % {st}", ) b = abs(track_d) <= tolerance * 100 is_ok = b and is_ok print_test( b, - f"Tracks: {counts1.track_count} {counts2.track_count} : {track_d:+.2f} % {st}", + f"Tracks: {counts1.tracks} {counts2.tracks} : {track_d:+.2f} % {st}", ) b = abs(step_d) <= tolerance * 100 is_ok = b and is_ok print_test( b, - f"Steps: {counts1.step_count} {counts2.step_count} : {step_d:+.2f} % {st}", + f"Steps: {counts1.steps} {counts2.steps} : {step_d:+.2f} % {st}", ) print_test( @@ -175,7 +205,7 @@ def assert_stats(stats_actor_1, stats_actor_2, tolerance=0, is_ok=True): ) # particles types (Track) - if stats_actor_1.track_types_flag and stats_actor_1.track_types_flag: + if track_types_flag: for item in counts1.track_types: v1 = counts1.track_types[item] if item in counts2.track_types: @@ -193,15 +223,15 @@ def assert_stats(stats_actor_1, stats_actor_2, tolerance=0, is_ok=True): print_test(b, f"Track {item:8}0 {v2}") # consistency check - if stats_actor_1.track_types_flag: + if track_types_flag: n = 0 for t in counts1.track_types.values(): n += int(t) - b = n == counts1.track_count + b = n == counts1.tracks print_test(b, f"Tracks : {counts1.track_types}") if "track_types" in counts2: print_test(b, f"Tracks (ref): {counts2.track_types}") - print_test(b, f"Tracks vs track_types : {counts1.track_count} {n}") + print_test(b, f"Tracks vs track_types : {counts1.tracks} {n}") is_ok = b and is_ok return is_ok @@ -315,8 +345,8 @@ def assert_images( # normalise by event if stats is not None: - d1 = d1 / stats.counts.event_count - d2 = d2 / stats.counts.event_count + d1 = d1 / stats.counts.events + d2 = d2 / stats.counts.events # normalize by sum of d1 s = np.sum(d2) @@ -408,8 +438,8 @@ def assert_filtered_imagesprofile1D( # normalise by event if stats is not None: - d1 = d1 / stats.counts.event_count - d2 = d2 / stats.counts.event_count + d1 = d1 / stats.counts.events + d2 = d2 / stats.counts.events mean_deviation = np.mean(d2 / d1 - 1) * 100 max_deviation = np.amax(np.abs(d1 / d2 - 1)) * 100 From cbfccef9a6a87ad0059741f6120f07dab3478ac7 Mon Sep 17 00:00:00 2001 From: David Sarrut Date: Mon, 30 Sep 2024 19:59:06 +0200 Subject: [PATCH 080/183] Fix typo and add attribute in geometry volumes Corrected a typo in the word "explicitly" within a comment. Also, added "_NodeMixin__children" to known_attributes to suppress warnings related to undeclared attributes in anytree. --- opengate/geometry/volumes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index 0abc2f1b8..ee356b0b8 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -239,9 +239,10 @@ def __getstate__(self): def __finalize_init__(self): super().__finalize_init__() - # need to add this explciitly because anytree does not properly declare + # need to add this explicitly because anytree does not properly declare # the attribute __parent in the NodeMixin.__init__ which leads to falls warnings self.known_attributes.add("_NodeMixin__parent") + self.known_attributes.add("_NodeMixin__children") def _update_node(self): """Internal method which retrieves the volume object From b64f439762565ba25b477ad5482a9d7d6b653baa Mon Sep 17 00:00:00 2001 From: David Sarrut Date: Mon, 30 Sep 2024 19:59:19 +0200 Subject: [PATCH 081/183] Refactor date attribute handling and thread statistics. Removed unused commented-out initialization of the `date` attribute. Updated thread statistics to be merged into `merged_data.nb_threads` and corrected date assignment to `counts.start_time` during parsing. --- opengate/actors/miscactors.py | 10 +++------- opengate/tests/utility.py | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 6eb8fa544..1396dfb89 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -210,12 +210,6 @@ def __initcpp__(self): g4.GateSimulationStatisticsActor.__init__(self, self.user_info) self.AddActions({"StartSimulationAction", "EndSimulationAction"}) - # def __finalize_init__(self): - # super().__finalize_init__() - # # this attribute is considered sometimes in the read_stat_file - # # we declare it here to avoid warning - # self.known_attributes.add("date") - def __str__(self): s = self.user_output["stats"].__str__() return s @@ -234,7 +228,9 @@ def initialize(self): def StartSimulationAction(self): g4.GateSimulationStatisticsActor.StartSimulationAction(self) - self.user_output.stats.nb_threads = self.simulation.number_of_threads + self.user_output.stats.merged_data.nb_threads = ( + self.simulation.number_of_threads + ) def EndSimulationAction(self): g4.GateSimulationStatisticsActor.EndSimulationAction(self) diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index 9c1cf2487..198526428 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -97,7 +97,7 @@ def read_stat_file_legacy(filename): stat.track_types_flag = True counts.track_types = {} if "Date" in line: - stat.date = line[len("# Date =") :] + counts.start_time = line[len("# Date =") :] if "Threads" in line: a = line[len(f"# Threads =") :] try: From e36aa3354faec862d61430046c0ca4d696eea29a Mon Sep 17 00:00:00 2001 From: David Sarrut Date: Mon, 30 Sep 2024 20:16:53 +0200 Subject: [PATCH 082/183] Refactor overlap check flag naming convention Updated the variable name for checking volume overlaps from `g4_check_overlap_flag` to `check_volumes_overlap` across multiple files. This change ensures consistency and readability in the codebase. --- opengate/contrib/linacs/elektasynergy.py | 2 +- opengate/contrib/phantoms/nemaiec.py | 2 +- opengate/contrib/spect/ge_discovery_nm670.py | 2 +- opengate/contrib/spect/siemens_intevo.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/opengate/contrib/linacs/elektasynergy.py b/opengate/contrib/linacs/elektasynergy.py index 1edded882..47ac1bb55 100644 --- a/opengate/contrib/linacs/elektasynergy.py +++ b/opengate/contrib/linacs/elektasynergy.py @@ -37,7 +37,7 @@ def add_linac(sim, name="linac", sad=1000): # sim.volume_manager.add_material_database('../contrib/elekta_synergy_materials.db') # check overlap - sim.g4_check_overlap_flag = True + sim.check_volumes_overlap = True # global box linac = add_empty_linac_box(sim, name, sad) diff --git a/opengate/contrib/phantoms/nemaiec.py b/opengate/contrib/phantoms/nemaiec.py index 91bba0bc5..9af0e8e2e 100644 --- a/opengate/contrib/phantoms/nemaiec.py +++ b/opengate/contrib/phantoms/nemaiec.py @@ -39,7 +39,7 @@ def add_iec_phantom( create_material(simulation) # check overlap only for debug - simulation.g4_check_overlap_flag = check_overlap + simulation.check_volumes_overlap = check_overlap # Outside structure iec, _, _ = add_iec_body(simulation, name) diff --git a/opengate/contrib/spect/ge_discovery_nm670.py b/opengate/contrib/spect/ge_discovery_nm670.py index 7eee3b327..7b9707f1d 100644 --- a/opengate/contrib/spect/ge_discovery_nm670.py +++ b/opengate/contrib/spect/ge_discovery_nm670.py @@ -58,7 +58,7 @@ def add_spect_head(sim, name="spect", collimator_type="lehr", debug=False): sim.volume_manager.add_material_database(fdb) # check overlap - sim.g4_check_overlap_flag = False # set to True for debug + sim.check_volumes_overlap = False # set to True for debug # spect head head, lead_cover = add_spect_box(sim, name) diff --git a/opengate/contrib/spect/siemens_intevo.py b/opengate/contrib/spect/siemens_intevo.py index 2681d5bdd..7e39733a5 100644 --- a/opengate/contrib/spect/siemens_intevo.py +++ b/opengate/contrib/spect/siemens_intevo.py @@ -35,7 +35,7 @@ def add_spect_head(sim, name="spect", collimator_type="lehr", debug=False): sim.volume_manager.add_material_database(fdb) # check overlap - sim.g4_check_overlap_flag = debug + sim.check_volumes_overlap = debug # main box head = add_head_box(sim, name) From f4329805ae8b176922934d71e6651e7eff2f83f8 Mon Sep 17 00:00:00 2001 From: David Sarrut Date: Tue, 1 Oct 2024 21:31:02 +0200 Subject: [PATCH 083/183] print Qt and Qt version in opengate_info --- core/opengate_core/opengate_lib/GateInfo.cpp | 20 +++++++++++++++++++ core/opengate_core/opengate_lib/GateInfo.h | 4 ++++ .../opengate_core/opengate_lib/pyGateInfo.cpp | 2 ++ 3 files changed, 26 insertions(+) diff --git a/core/opengate_core/opengate_lib/GateInfo.cpp b/core/opengate_core/opengate_lib/GateInfo.cpp index 723b23faa..9641872b6 100644 --- a/core/opengate_core/opengate_lib/GateInfo.cpp +++ b/core/opengate_core/opengate_lib/GateInfo.cpp @@ -8,6 +8,7 @@ #include "GateInfo.h" #include "G4Version.hh" #include +#include #ifdef G4MULTITHREADED @@ -25,6 +26,25 @@ bool GateInfo::get_G4MULTITHREADED() { #endif } +bool GateInfo::get_G4VIS_USE_OPENGLQT() { +#ifdef G4VIS_USE_OPENGLQT + return true; +#else + return false; +#endif +} + +std::string GateInfo::get_QT_VERSION() { +#ifdef G4VIS_USE_OPENGLQT +#include + std::ostringstream oss; + oss << QT_VERSION_MAJOR << "." << QT_VERSION_MINOR << "." << QT_VERSION_PATCH; + return oss.str(); +#else + return "no_qt"; +#endif +} + std::string GateInfo::get_G4Version() { return G4Version; } std::string GateInfo::get_G4Date() { return G4Date; } diff --git a/core/opengate_core/opengate_lib/GateInfo.h b/core/opengate_core/opengate_lib/GateInfo.h index eee77f30a..984d9700b 100644 --- a/core/opengate_core/opengate_lib/GateInfo.h +++ b/core/opengate_core/opengate_lib/GateInfo.h @@ -17,6 +17,10 @@ class GateInfo { static std::string get_ITKVersion(); + static bool get_G4VIS_USE_OPENGLQT(); + + static std::string get_QT_VERSION(); + static bool get_G4GDML(); static void test(); diff --git a/core/opengate_core/opengate_lib/pyGateInfo.cpp b/core/opengate_core/opengate_lib/pyGateInfo.cpp index 32bd9f000..1bd05c6dd 100644 --- a/core/opengate_core/opengate_lib/pyGateInfo.cpp +++ b/core/opengate_core/opengate_lib/pyGateInfo.cpp @@ -18,6 +18,8 @@ void init_GateInfo(py::module &m) { .def("get_G4Version", &GateInfo::get_G4Version) .def("get_G4Date", &GateInfo::get_G4Date) .def("get_ITKVersion", &GateInfo::get_ITKVersion) + .def("get_G4VIS_USE_OPENGLQT", &GateInfo::get_G4VIS_USE_OPENGLQT) + .def("get_QT_VERSION", &GateInfo::get_QT_VERSION) .def("test", &GateInfo::test) .def("get_G4GDML", &GateInfo::get_G4GDML); } From efa5aa0273ba32f0f3c6203a159cfa6c2f7b5b74 Mon Sep 17 00:00:00 2001 From: David Sarrut Date: Tue, 1 Oct 2024 21:31:41 +0200 Subject: [PATCH 084/183] Add type hints to Simulation class for enhanced IDE support The type hints provide better code clarity and assist IDEs with more accurate autocompletions and error checking. This change includes optional and union types, improving overall maintainability and readability of the codebase. --- opengate/managers.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/opengate/managers.py b/opengate/managers.py index 2dd3701df..425b11cf4 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1,5 +1,6 @@ import sys import logging +from typing import Optional, List, Union from box import Box from anytree import RenderTree, LoopError import shutil @@ -1211,6 +1212,36 @@ class Simulation(GateObject): There is NO Geant4 engine here, it is only a set of parameters and options. """ + # hints for IDE + verbose_level: int + verbose_close: bool + verbose_getstate: bool + running_verbose_level: int + g4_verbose_level: int + g4_verbose: bool + g4_verbose_level_tracking: int + visu: bool + visu_type: str + visu_filename: Optional[Path] + visu_verbose: bool + visu_commands: List[str] + visu_commands_vrml: List[str] + visu_commands_gdml: List[str] + check_volumes_overlap: bool + number_of_threads: int + force_multithread_mode: bool + random_engine: str + random_seed: Union[str, int] + run_timing_intervals: List[List[float]] + output_dir: Path + store_json_archive: bool + json_archive_filename: Path + store_input_files: bool + g4_commands_before_init: List[str] + g4_commands_after_init: List[str] + init_only: bool + progress_bar: bool + user_info_defaults = { "verbose_level": ( logger.INFO, From cc27b5f2e88c5dda209369bf07c793e28ee81d84 Mon Sep 17 00:00:00 2001 From: David Sarrut Date: Tue, 1 Oct 2024 21:32:18 +0200 Subject: [PATCH 085/183] Suppress debug prints and add Geant4 Qt details Commented out debug print statements in `LazyModuleLoader` for cleaner logs during module import. Added an informative print statement to display Geant4 Qt status and version, enhancing the output details for better tracking of Geant4 visual options. --- opengate/utility.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/opengate/utility.py b/opengate/utility.py index 81b4d02f2..e9734e98e 100644 --- a/opengate/utility.py +++ b/opengate/utility.py @@ -39,9 +39,9 @@ def __getattr__(self, name): if self.module is None: # Check module existence and import it try: - print(f"LazyModuleLoader is importing module {self.module_name} ...") + # print(f"LazyModuleLoader is importing module {self.module_name} ...") self.module = importlib.import_module(self.module_name) - print("... done") + # print("... done") except ModuleNotFoundError: fatal( f"The module '{self.module_name}' is not installed. " @@ -308,6 +308,7 @@ def print_opengate_info(): print(f"Geant4 version {v}") print(f"Geant4 MT {gi.get_G4MULTITHREADED()}") + print(f"Geant4 Qt {gi.get_G4VIS_USE_OPENGLQT()} {gi.get_QT_VERSION()}") print(f"Geant4 GDML {gi.get_G4GDML()}") print(f"Geant4 date {gi.get_G4Date().replace(')', '').replace('(', '')}") print(f"Geant4 data {g4.get_g4_data_folder()}") From caee927e2fc0a08cdd4a406e5bbcb3bc03b5379f Mon Sep 17 00:00:00 2001 From: David Date: Thu, 3 Oct 2024 07:52:52 +0200 Subject: [PATCH 086/183] Fix progress bar step calculation in GateSourceManager Ensure that the progress bar step is always at least 1 to prevent invalid step values. This change adjusts the step size conditionally based on the expected number of events. --- core/opengate_core/opengate_lib/GateSourceManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/opengate_core/opengate_lib/GateSourceManager.cpp b/core/opengate_core/opengate_lib/GateSourceManager.cpp index 9717edc92..acab1c297 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.cpp +++ b/core/opengate_core/opengate_lib/GateSourceManager.cpp @@ -195,6 +195,10 @@ void GateSourceManager::ComputeExpectedNumberOfEvents() { fProgressBarStep = (long)round((double)fExpectedNumberOfEvents / 100.0); if (fExpectedNumberOfEvents > 1e7) fProgressBarStep = (long)round((double)fExpectedNumberOfEvents / 1000.0); + + if (fProgressBarStep < 1) { + fProgressBarStep = 1; + } } long int GateSourceManager::GetExpectedNumberOfEvents() const { From 1a1845eb20510d326b971fb724726865ea24adc7 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 3 Oct 2024 07:53:44 +0200 Subject: [PATCH 087/183] update data for bg source iec --- opengate/tests/data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/tests/data b/opengate/tests/data index e3d68b9c3..c01821128 160000 --- a/opengate/tests/data +++ b/opengate/tests/data @@ -1 +1 @@ -Subproject commit e3d68b9c3966f990c33ff3237965792c44407e73 +Subproject commit c01821128f405b8d94aed7a70f4b1def5c30685e From 3d758a24000b72c1db91e5c11006fecd49a401e4 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 3 Oct 2024 07:54:45 +0200 Subject: [PATCH 088/183] `read_stat_file` now guess if json or legacy format Refactor `read_stat_file` to handle encoding better by adding a guessing mechanism for JSON or legacy encodings. Modify image comparison functions to return profile data and add a profile SAD check with a specified tolerance. --- opengate/tests/utility.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index 198526428..c543ccbae 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -49,9 +49,16 @@ def test_ok(is_ok=False, exceptions=None): sys.exit(-1) -def read_stat_file(filename, encoder="legacy"): +def read_stat_file(filename, encoder=None): if encoder == "json": return read_stat_file_json(filename) + if encoder == "legacy": + return read_stat_file_legacy(filename) + # guess if it is json or not + try: + return read_stat_file_json(filename) + except (json.JSONDecodeError, ValueError): + pass return read_stat_file_legacy(filename) @@ -253,6 +260,7 @@ def plot_img_z(ax, img, label): x = np.arange(len(y)) * img.GetSpacing()[2] ax.plot(x, y, label=label) ax.legend() + return y def plot_img_y(ax, img, label): @@ -263,6 +271,7 @@ def plot_img_y(ax, img, label): x = np.arange(len(y)) * img.GetSpacing()[1] ax.plot(x, y, label=label) ax.legend() + return y def plot_img_x(ax, img, label): @@ -273,6 +282,7 @@ def plot_img_x(ax, img, label): x = np.arange(len(y)) * img.GetSpacing()[0] ax.plot(x, y, label=label) ax.legend() + return y def assert_images_properties(info1, info2): @@ -308,6 +318,7 @@ def assert_images( fig_name=None, sum_tolerance=5, scaleImageValuesFactor=None, + sad_profile_tolerance=None, ): # read image and info (size, spacing, etc.) ref_filename1 = ensure_filename_is_str(ref_filename1) @@ -355,18 +366,27 @@ def assert_images( # sum of absolute difference (in %) sad = np.fabs(d1 - d2).sum() * 100 - is_ok = is_ok and sad < tolerance + b = sad < tolerance print_test( - is_ok, + b, f"Image diff computed on {len(data2[data2 != 0])}/{len(data2.ravel())} \n" f"SAD (per event/total): {sad:.2f} % " f" (tolerance is {tolerance :.2f} %)", ) + is_ok = is_ok and b # plot fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(25, 10)) - plot_img_axis(ax, img1, "reference", axis) - plot_img_axis(ax, img2, "test", axis) + p1 = plot_img_axis(ax, img1, "reference", axis) + p2 = plot_img_axis(ax, img2, "test", axis) + if sad_profile_tolerance is not None: + sad = np.fabs(p1 - p2).sum() / p1.sum() * 100 + b = sad < sad_profile_tolerance + print_test( + b, + f"Profile {axis} relative SAD is {sad:.2f}% (tol {sad_profile_tolerance}%)", + ) + is_ok = is_ok and b if fig_name is None: filename2 = Path(filename2) fn = filename2.stem + "_test" + ".png" From 9493518b5a8583b83d7c1dc50034bfc6043ac055 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 3 Oct 2024 07:56:13 +0200 Subject: [PATCH 089/183] correct bg source for iec nema phantom Update associated tests. It is unclear how the "confine" function in Geant4 handles the boolean solid. --- opengate/contrib/phantoms/nemaiec.py | 5 ++ .../src/test010_generic_source_confine.py | 2 +- ...antom_3.py => test015_iec_phantom_3_mt.py} | 21 +++-- opengate/tests/src/test015_iec_phantom_4.py | 82 +++++++++++++++++++ ..._4_wip.py => test015_iec_phantom_5_wip.py} | 0 ...t038_gan_phsp_spect_training_dataset_mt.py | 2 +- .../test040_gan_phsp_pet_training_dataset.py | 4 +- opengate/tests/src/test058_iec_six_spheres.py | 5 +- 8 files changed, 106 insertions(+), 15 deletions(-) rename opengate/tests/src/{test015_iec_phantom_3.py => test015_iec_phantom_3_mt.py} (82%) create mode 100755 opengate/tests/src/test015_iec_phantom_4.py rename opengate/tests/src/{test015_iec_phantom_4_wip.py => test015_iec_phantom_5_wip.py} (100%) diff --git a/opengate/contrib/phantoms/nemaiec.py b/opengate/contrib/phantoms/nemaiec.py index 9af0e8e2e..0831c11c6 100644 --- a/opengate/contrib/phantoms/nemaiec.py +++ b/opengate/contrib/phantoms/nemaiec.py @@ -423,6 +423,11 @@ def add_background_source( bg.particle = "e+" bg.energy.type = "F18" bg.activity = activity_Bq_mL * s.cubic_volume + # the confine procedure from G4 seems to be confused when using a boolean solid like {iec_name}_interior + # (or I did understand correctly how it works) + # so, we need to move the source for correct sampling of the volume + mm = g4_units.mm + bg.position.translation = [0, 35 * mm, 0] # verbose ? if verbose: # print(f"Bg volume {s.cubic_volume} cc") diff --git a/opengate/tests/src/test010_generic_source_confine.py b/opengate/tests/src/test010_generic_source_confine.py index f9d2073e2..489dbc480 100755 --- a/opengate/tests/src/test010_generic_source_confine.py +++ b/opengate/tests/src/test010_generic_source_confine.py @@ -81,7 +81,7 @@ """ the source is confined in the given volume ('stuff'), it means that all particles will be emitted only in this volume. - The 'box' type is reqsimred to defined a larger volume that 'stuff'. + The 'box' type is required to define a larger volume than 'stuff'. It is done here by computing the bounding box Daughter volumes of 'stuff' do not count : no particle will be generated from 'stuff_inside' diff --git a/opengate/tests/src/test015_iec_phantom_3.py b/opengate/tests/src/test015_iec_phantom_3_mt.py similarity index 82% rename from opengate/tests/src/test015_iec_phantom_3.py rename to opengate/tests/src/test015_iec_phantom_3_mt.py index ffdbe14bb..5f8109b0b 100755 --- a/opengate/tests/src/test015_iec_phantom_3.py +++ b/opengate/tests/src/test015_iec_phantom_3_mt.py @@ -22,8 +22,10 @@ # main options sim.check_volumes_overlap = True - sim.random_seed = 123654987 + sim.random_seed = 123456789 sim.output_dir = paths.output + sim.progress_bar = True + sim.number_of_threads = 2 # physics sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" @@ -36,13 +38,13 @@ iec_phantom = gate_iec.add_iec_phantom(sim) # add sources for all central cylinder - a = 20 * BqmL - bg1 = gate_iec.add_central_cylinder_source( + a = 20 * BqmL / sim.number_of_threads + """bg1 = gate_iec.add_central_cylinder_source( sim, iec_phantom.name, "bg1", a * 5, verbose=True ) bg1.particle = "alpha" bg1.energy.type = "mono" - bg1.energy.mono = 100 * MeV + bg1.energy.mono = 100 * MeV""" # add background source bg2 = gate_iec.add_background_source(sim, iec_phantom.name, "bg2", a, verbose=True) @@ -53,14 +55,14 @@ # add stat actor stats = sim.add_actor("SimulationStatisticsActor", "stats") stats.track_types_flag = True - stats.output_filename = "test015_iec_3_stats.txt" + stats.output_filename = "test015_iec_3_stats.json" # add dose actor dose = sim.add_actor("DoseActor", "dose") dose.edep.output_filename = "test015_iec_3.mhd" dose.attached_to = iec_phantom - dose.size = [100, 100, 100] - dose.spacing = [2 * mm, 2 * mm, 2 * mm] + dose.size = [150, 150, 150] + dose.spacing = [3 * mm, 3 * mm, 3 * mm] # start sim.run() @@ -77,9 +79,10 @@ dose.edep.get_output_path(), stats, axis="y", - tolerance=86, + tolerance=80, ignore_value=0, - sum_tolerance=2, + sum_tolerance=0.5, + sad_profile_tolerance=2, ) is_ok = is_ok and im_ok diff --git a/opengate/tests/src/test015_iec_phantom_4.py b/opengate/tests/src/test015_iec_phantom_4.py new file mode 100755 index 000000000..45d9696a9 --- /dev/null +++ b/opengate/tests/src/test015_iec_phantom_4.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +import opengate.contrib.phantoms.nemaiec as gate_iec + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__, "", "test015") + + # create the simulation + sim = gate.Simulation() + + # units + MeV = gate.g4_units.MeV + m = gate.g4_units.m + mm = gate.g4_units.mm + cm = gate.g4_units.cm + cm3 = gate.g4_units.cm3 + Bq = gate.g4_units.Bq + BqmL = Bq / cm3 + + # main options + sim.check_volumes_overlap = True + sim.random_seed = "auto" # 123456789 + sim.output_dir = paths.output + sim.progress_bar = True + + # physics + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.set_production_cut("world", "all", 10 * mm) + + # world size + sim.world.size = [0.5 * m, 0.5 * m, 0.5 * m] + + # add an iec phantom + iec_phantom = gate_iec.add_iec_phantom(sim) + + # add sources for all central cylinder + a = 20 * BqmL + bg1 = gate_iec.add_central_cylinder_source( + sim, iec_phantom.name, "bg1", a * 5, verbose=True + ) + bg1.particle = "alpha" + bg1.energy.type = "mono" + bg1.energy.mono = 100 * MeV + + # add stat actor + stats = sim.add_actor("SimulationStatisticsActor", "stats") + stats.track_types_flag = True + stats.output_filename = "test015_iec_4_stats.txt" + + # add dose actor + dose = sim.add_actor("DoseActor", "dose") + dose.edep.output_filename = "test015_iec_4.mhd" + dose.attached_to = iec_phantom + dose.size = [150, 150, 150] + dose.spacing = [3 * mm, 3 * mm, 3 * mm] + + # start + sim.run() + + # compare stats + stats_ref = utility.read_stat_file(paths.output_ref / "test015_iec_4_stats.txt") + is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.02) + + # compare images + dose = sim.get_actor("dose") + f = paths.output / "test015_iec_4.mhd" + im_ok = utility.assert_images( + paths.output_ref / "test015_iec_4.mhd", + dose.edep.get_output_path(), + stats, + axis="y", + tolerance=50, + ignore_value=0, + sum_tolerance=1.0, + sad_profile_tolerance=3.0, + ) + + is_ok = is_ok and im_ok + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_4_wip.py b/opengate/tests/src/test015_iec_phantom_5_wip.py similarity index 100% rename from opengate/tests/src/test015_iec_phantom_4_wip.py rename to opengate/tests/src/test015_iec_phantom_5_wip.py diff --git a/opengate/tests/src/test038_gan_phsp_spect_training_dataset_mt.py b/opengate/tests/src/test038_gan_phsp_spect_training_dataset_mt.py index ff376894d..124c285d6 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_training_dataset_mt.py +++ b/opengate/tests/src/test038_gan_phsp_spect_training_dataset_mt.py @@ -28,7 +28,7 @@ # main parameters sim.check_volumes_overlap = True sim.number_of_threads = 2 - sim.random_seed = 8123456 + sim.random_seed = 321654987 sim.output_dir = paths.output ac = 100 * BqmL / sim.number_of_threads sim.visu = False diff --git a/opengate/tests/src/test040_gan_phsp_pet_training_dataset.py b/opengate/tests/src/test040_gan_phsp_pet_training_dataset.py index 39b3bacc8..ec9dc9003 100755 --- a/opengate/tests/src/test040_gan_phsp_pet_training_dataset.py +++ b/opengate/tests/src/test040_gan_phsp_pet_training_dataset.py @@ -28,7 +28,7 @@ sim.output_dir = paths.output sim.check_volumes_overlap = True sim.number_of_threads = 1 - sim.random_seed = 123456 + sim.random_seed = 123456789 sim.output_dir = paths.output ac = 15 * BqmL sim.visu = False @@ -148,7 +148,7 @@ tols = [1.0] * len(checked_keys) tols[checked_keys.index("TimeFromBeginOfEvent")] = 0.007 tols[checked_keys.index("KineticEnergy")] = 0.003 - tols[checked_keys.index("PrePosition_X")] = 1.6 + tols[checked_keys.index("PrePosition_X")] = 1.7 tols[checked_keys.index("PrePosition_Y")] = 1.6 tols[checked_keys.index("PrePosition_Z")] = 1.9 tols[checked_keys.index("PreDirection_X")] = 0.01 diff --git a/opengate/tests/src/test058_iec_six_spheres.py b/opengate/tests/src/test058_iec_six_spheres.py index e4ee1d51e..3fb57200a 100755 --- a/opengate/tests/src/test058_iec_six_spheres.py +++ b/opengate/tests/src/test058_iec_six_spheres.py @@ -20,8 +20,9 @@ sim.visu = False sim.visu_type = "vrml" sim.check_volumes_overlap = True - sim.random_seed = 321654 + sim.random_seed = 123456789 sim.output_dir = paths.output + sim.progress_bar = True # units m = gate.g4_units.m @@ -141,7 +142,7 @@ "phsp_bg", k, k, - [0.2] * len(k), + [0.26] * len(k), [1] * len(k), [1] * len(k), paths.output / "test058_bg.png", From 9ff160eed7d74199b13d0f549b5e310d94760850 Mon Sep 17 00:00:00 2001 From: Alexis Pereda Date: Thu, 3 Oct 2024 16:59:50 +0200 Subject: [PATCH 090/183] fix include position in GateInfo.cpp --- core/opengate_core/opengate_lib/GateInfo.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/opengate_core/opengate_lib/GateInfo.cpp b/core/opengate_core/opengate_lib/GateInfo.cpp index 9641872b6..217ad0b1f 100644 --- a/core/opengate_core/opengate_lib/GateInfo.cpp +++ b/core/opengate_core/opengate_lib/GateInfo.cpp @@ -10,6 +10,10 @@ #include #include +#ifdef G4VIS_USE_OPENGLQT +#include +#endif + #ifdef G4MULTITHREADED #include "G4MTRunManager.hh" @@ -36,7 +40,6 @@ bool GateInfo::get_G4VIS_USE_OPENGLQT() { std::string GateInfo::get_QT_VERSION() { #ifdef G4VIS_USE_OPENGLQT -#include std::ostringstream oss; oss << QT_VERSION_MAJOR << "." << QT_VERSION_MINOR << "." << QT_VERSION_PATCH; return oss.str(); From 905a560437d8d15560907e58811914e11043fb94 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:24:45 +0000 Subject: [PATCH 091/183] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0) - [github.com/pre-commit/mirrors-clang-format: v19.1.0 → v19.1.1](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.0...v19.1.1) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f6ca9dcce..632a1552b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,14 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v19.1.0 + rev: v19.1.1 hooks: - id: clang-format ci: From 427d5169817e5c72da1646764fee4af623a72787 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 7 Oct 2024 09:35:05 +0200 Subject: [PATCH 092/183] Remove debug print "loading opengate" Commented out initialization status prints and removed redundant "done" message to streamline the module's import process. --- opengate/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/opengate/__init__.py b/opengate/__init__.py index 53e30a72c..df806a5e4 100644 --- a/opengate/__init__.py +++ b/opengate/__init__.py @@ -1,8 +1,8 @@ # This file handles the way opengate is imported. +""" import colored import threading - print( colored.stylize( f"Importing opengate (thread " f"{threading.get_native_id()}) ... ", @@ -11,6 +11,8 @@ end="", flush=True, ) +print(colored.stylize("done", colored.fore("dark_gray"))) +""" # the following modules are imported respecting the package structure # they will be available via @@ -52,6 +54,3 @@ from opengate.managers import Simulation from opengate.managers import create_sim_from_json from opengate.utility import g4_units - - -print(colored.stylize("done", colored.fore("dark_gray"))) From b2ee356e4784d3b4d150ed35c3ab9b12053b8cb2 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 13:27:31 +0200 Subject: [PATCH 093/183] Fix opengate_user_info.py --- opengate/bin/opengate_user_info.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/opengate/bin/opengate_user_info.py b/opengate/bin/opengate_user_info.py index e9488806f..221a5d46a 100755 --- a/opengate/bin/opengate_user_info.py +++ b/opengate/bin/opengate_user_info.py @@ -2,9 +2,10 @@ import click from opengate.userinfo import UserInfo -from opengate.geometry.builders import volume_type_names +from opengate.managers import VolumeManager, actor_types from opengate.sources.builders import source_type_names + CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @@ -27,8 +28,8 @@ def go(): print() print(f"Volumes") print() - for v in volume_type_names: - print_one(v, "Volume") + for v in VolumeManager.volume_types.values(): + print(v) print() print(f"Sources") @@ -39,8 +40,8 @@ def go(): print() print(f"Actors") print() - for v in actor_type_names: - print_one(v, "Actor") + for v in actor_types.values(): + print(v) if __name__ == "__main__": From d8cf69bbea8f4eaf3d0686a28c00bfff8d401635 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 22:44:57 +0200 Subject: [PATCH 094/183] Remove obsolete actors/builder.py module and update element.py --- opengate/actors/builders.py | 17 ----------------- opengate/element.py | 4 ---- 2 files changed, 21 deletions(-) delete mode 100644 opengate/actors/builders.py diff --git a/opengate/actors/builders.py b/opengate/actors/builders.py deleted file mode 100644 index 2745c238e..000000000 --- a/opengate/actors/builders.py +++ /dev/null @@ -1,17 +0,0 @@ -from .filters import ( - KineticEnergyFilter, - ParticleFilter, - TrackCreatorProcessFilter, - ThresholdAttributeFilter, - UnscatteredPrimaryFilter, -) -from ..utility import make_builders - -filter_type_names = { - ParticleFilter, - KineticEnergyFilter, - TrackCreatorProcessFilter, - ThresholdAttributeFilter, - UnscatteredPrimaryFilter, -} -filter_builders = make_builders(filter_type_names) diff --git a/opengate/element.py b/opengate/element.py index 8fab706f1..b2348b19f 100644 --- a/opengate/element.py +++ b/opengate/element.py @@ -1,12 +1,10 @@ import copy from .sources.builders import source_builders, source_type_names -from .actors.builders import filter_builders, filter_type_names from .exception import fatal element_builders = { "Source": source_builders, - "Filter": filter_builders, } @@ -17,8 +15,6 @@ def get_element_class(element_type, type_name): elements = None if element_type == "Source": elements = source_type_names - if element_type == "Filter": - elements = filter_type_names if not elements: fatal( f"Error, element_type={element_type} is unknown. Use Volume, Source or Actor." From d2361709e77ef92a2adbc0929895ac43b3c46635 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 22:45:36 +0200 Subject: [PATCH 095/183] Fix tests that did not use if __name__ == "__main__" clause --- .../tests/src/test013_phys_lists_2_wip.py | 58 ++-- .../tests/src/test013_phys_lists_5_wip.py | 122 ++++---- opengate/tests/src/test045_speedup_all_wip.py | 279 +++++++++--------- 3 files changed, 232 insertions(+), 227 deletions(-) diff --git a/opengate/tests/src/test013_phys_lists_2_wip.py b/opengate/tests/src/test013_phys_lists_2_wip.py index 4726f862d..a6658a224 100755 --- a/opengate/tests/src/test013_phys_lists_2_wip.py +++ b/opengate/tests/src/test013_phys_lists_2_wip.py @@ -4,41 +4,43 @@ from opengate.tests import utility from test013_phys_lists_helpers import create_pl_sim -paths = utility.get_default_test_paths(__file__, "gate_test013_phys_lists") -# create simulation -sim = create_pl_sim() +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__, "gate_test013_phys_lists") -# remove ion sources -sim.source_manager.user_info_sources.pop("ion1") -sim.source_manager.user_info_sources.pop("ion2") + # create simulation + sim = create_pl_sim() -# change physics -sim.physics_manager.physics_list_name = "QGSP_BERT_EMZ" + # remove ion sources + sim.source_manager.user_info_sources.pop("ion1") + sim.source_manager.user_info_sources.pop("ion2") -# em parameters -sim.physics_manager.em_parameters.fluo = True -sim.physics_manager.em_parameters.auger = True -sim.physics_manager.em_parameters.auger_cascade = True -sim.physics_manager.em_parameters.pixe = True -sim.physics_manager.em_parameters.deexcitation_ignore_cut = True + # change physics + sim.physics_manager.physics_list_name = "QGSP_BERT_EMZ" -sim.physics_manager.em_switches_world.deex = True -sim.physics_manager.em_switches_world.auger = True -sim.physics_manager.em_switches_world.pixe = True + # em parameters + sim.physics_manager.em_parameters.fluo = True + sim.physics_manager.em_parameters.auger = True + sim.physics_manager.em_parameters.auger_cascade = True + sim.physics_manager.em_parameters.pixe = True + sim.physics_manager.em_parameters.deexcitation_ignore_cut = True -print("Phys list cuts:") -print(sim.physics_manager.dump_production_cuts()) + sim.physics_manager.em_switches_world.deex = True + sim.physics_manager.em_switches_world.auger = True + sim.physics_manager.em_switches_world.pixe = True -# start simulation -# sim.set_g4_verbose(True) -# sim.g4_commands_after_init.append("/tracking/verbose 1") -sim.run() + print("Phys list cuts:") + print(sim.physics_manager.dump_production_cuts()) -stats = sim.get_actor("Stats") + # start simulation + # sim.set_g4_verbose(True) + # sim.g4_commands_after_init.append("/tracking/verbose 1") + sim.run() -# Gate mac/main_2.mac -stats_ref = utility.read_stat_file(paths.gate_output / "stat_2.txt") -is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.07) + stats = sim.get_actor("Stats") -utility.test_ok(is_ok) + # Gate mac/main_2.mac + stats_ref = utility.read_stat_file(paths.gate_output / "stat_2.txt") + is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.07) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test013_phys_lists_5_wip.py b/opengate/tests/src/test013_phys_lists_5_wip.py index dd5d2340b..d7dbcb200 100755 --- a/opengate/tests/src/test013_phys_lists_5_wip.py +++ b/opengate/tests/src/test013_phys_lists_5_wip.py @@ -6,63 +6,65 @@ from opengate.userhooks import check_production_cuts from test013_phys_lists_helpers import create_pl_sim -paths = utility.get_default_test_paths(__file__, "gate_test013_phys_lists") - -mm = gate.g4_units.mm - -# create simulation -sim = create_pl_sim() - -# keep only ion sources -sim.source_manager.user_info_sources.pop("gamma") - -# change physics -sim.physics_manager.physics_list_name = "QGSP_BERT_EMZ" -sim.physics_manager.enable_decay = True - -sim.physics_manager.global_production_cuts.gamma = 5 * mm -sim.physics_manager.global_production_cuts.electron = "default" -sim.physics_manager.global_production_cuts.positron = 3 * mm -sim.physics_manager.global_production_cuts.proton = 1 * mm - -sim.physics_manager.set_production_cut( - volume_name="waterbox", - particle_name="gamma", - value=2 * mm, -) -sim.physics_manager.set_production_cut( - volume_name="b2", - particle_name="electron", - value=5 * mm, -) - -# em parameters -sim.physics_manager.em_parameters.fluo = True -sim.physics_manager.em_parameters.auger = True -sim.physics_manager.em_parameters.auger_cascade = True -sim.physics_manager.em_parameters.pixe = True -sim.physics_manager.em_parameters.deexcitation_ignore_cut = True - -sim.physics_manager.em_switches_world.deex = True -sim.physics_manager.em_switches_world.auger = True -sim.physics_manager.em_switches_world.pixe = True - -print("Phys list cuts:") -print(sim.physics_manager.dump_production_cuts()) - -# start simulation -# sim.g4_commands_after_init.append("/tracking/verbose 1") -sim.g4_verbose = False -sim.user_hook_after_init = check_production_cuts -sim.run() - -stats = sim.get_actor("Stats") - -# gate_test4_simulation_stats_actor -# Gate mac/main_4.mac -f = paths.gate_output / "stat_5.txt" -print("Reference file", f) -stats_ref = utility.read_stat_file(f) -is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.13) - -utility.test_ok(is_ok) + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__, "gate_test013_phys_lists") + + mm = gate.g4_units.mm + + # create simulation + sim = create_pl_sim() + + # keep only ion sources + sim.source_manager.user_info_sources.pop("gamma") + + # change physics + sim.physics_manager.physics_list_name = "QGSP_BERT_EMZ" + sim.physics_manager.enable_decay = True + + sim.physics_manager.global_production_cuts.gamma = 5 * mm + sim.physics_manager.global_production_cuts.electron = "default" + sim.physics_manager.global_production_cuts.positron = 3 * mm + sim.physics_manager.global_production_cuts.proton = 1 * mm + + sim.physics_manager.set_production_cut( + volume_name="waterbox", + particle_name="gamma", + value=2 * mm, + ) + sim.physics_manager.set_production_cut( + volume_name="b2", + particle_name="electron", + value=5 * mm, + ) + + # em parameters + sim.physics_manager.em_parameters.fluo = True + sim.physics_manager.em_parameters.auger = True + sim.physics_manager.em_parameters.auger_cascade = True + sim.physics_manager.em_parameters.pixe = True + sim.physics_manager.em_parameters.deexcitation_ignore_cut = True + + sim.physics_manager.em_switches_world.deex = True + sim.physics_manager.em_switches_world.auger = True + sim.physics_manager.em_switches_world.pixe = True + + print("Phys list cuts:") + print(sim.physics_manager.dump_production_cuts()) + + # start simulation + # sim.g4_commands_after_init.append("/tracking/verbose 1") + sim.g4_verbose = False + sim.user_hook_after_init = check_production_cuts + sim.run() + + stats = sim.get_actor("Stats") + + # gate_test4_simulation_stats_actor + # Gate mac/main_4.mac + f = paths.gate_output / "stat_5.txt" + print("Reference file", f) + stats_ref = utility.read_stat_file(f) + is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.13) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test045_speedup_all_wip.py b/opengate/tests/src/test045_speedup_all_wip.py index 07ba6e2aa..f7c570389 100755 --- a/opengate/tests/src/test045_speedup_all_wip.py +++ b/opengate/tests/src/test045_speedup_all_wip.py @@ -5,142 +5,143 @@ from box import Box from opengate.tests import utility -paths = utility.get_default_test_paths(__file__, "", "test045") - -p = Box() -p.phantom_type = "analytic" -p.source_type = "analytic" -p.use_pet = True -p.use_gaga = False -p.a = 1e2 - -# debug -skip = False -# the seed is only for the G4 part, the GAN use his own seed -# so the results are not reproducible (yet) -seed = 812365478 - - -def run(param): - print("run ", param) - cmd_line = ( - f"{paths.current}/test045_speedup.py -o AUTO --seed {seed} -p {param.phantom_type} " - f"-s {param.source_type} -r Ga68 -a {param.a}" - ) - if param.use_pet: - cmd_line += " --pet " - if param.use_gaga: - cmd_line += " --gaga " - out = f"test045_speedup_p_{param.phantom_type}_s_{param.source_type}_pet_{param.use_pet}_gaga_{param.use_gaga}.txt" - if not skip: - print("cmd line", cmd_line) - r = os.system(f"python {cmd_line}") - - print("Output ", out) - return out - - -# output -output = [] - -# Test 1 -p.phantom_type = "analytic" -p.source_type = "analytic" -p.use_gaga = False -p.a = 1e3 -out = run(p) -output.append(out) - -# Test 2 -p.phantom_type = "analytic" -p.source_type = "vox" -p.use_gaga = False -p.a = 1e3 -out = run(p) -output.append(out) - -# Test 3 -p.phantom_type = "vox" -p.source_type = "vox" -p.use_gaga = False -p.a = 1e3 -out = run(p) -output.append(out) - -# Test 4 -p.phantom_type = "analytic" -p.source_type = "analytic" -p.use_gaga = True -p.a = 1e3 -out = run(p) -output.append(out) - -# Test 5 -p.phantom_type = "analytic" -p.source_type = "vox" -p.use_gaga = True -p.a = 1e3 -out = run(p) -output.append(out) - -# Test 6 -p.phantom_type = "vox" -p.source_type = "vox" -p.use_gaga = True -p.a = 1e3 -out = run(p) -output.append(out) - -print(output) - -# tests stats file -is_ok = True -for o in output: - stats = utility.read_stat_file(paths.output / o) - stats_ref = utility.read_stat_file(paths.output_ref / o) - ok = utility.assert_stats(stats, stats_ref, 0.06) - utility.print_test(ok, f"Check {o}") - is_ok = is_ok and ok - print() - -# tests pet files -keys = [ - "GlobalTime", - "PostPosition_X", - "PostPosition_Y", - "PostPosition_Z", - "TotalEnergyDeposit", - "TrackVolumeCopyNo", -] -tols = [10.0] * len(keys) -tols[keys.index("GlobalTime")] = 0.04 -tols[keys.index("PostPosition_X")] = 8.4 -tols[keys.index("PostPosition_Y")] = 13 -tols[keys.index("PostPosition_Z")] = 1.5 -tols[keys.index("TotalEnergyDeposit")] = 0.03 -tols[keys.index("TrackVolumeCopyNo")] = 4.1 -scalings = [1.0] * len(keys) -scalings[keys.index("GlobalTime")] = 1e-9 # time in ns -for o in output: - o = o.replace(".txt", ".root") - o1 = paths.output / o - o2 = paths.output_ref / o - img = paths.output / o.replace(".root", ".png") - ok = utility.compare_root3( - o1, - o2, - "Singles", - "Singles", - keys, - keys, - tols, - scalings, - scalings, - img, - hits_tol=5, - ) - utility.print_test(ok, f"Check {o}") - is_ok = is_ok and ok - print() - -utility.test_ok(is_ok) +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__, "", "test045") + + p = Box() + p.phantom_type = "analytic" + p.source_type = "analytic" + p.use_pet = True + p.use_gaga = False + p.a = 1e2 + + # debug + skip = False + # the seed is only for the G4 part, the GAN use his own seed + # so the results are not reproducible (yet) + seed = 812365478 + + + def run(param): + print("run ", param) + cmd_line = ( + f"{paths.current}/test045_speedup.py -o AUTO --seed {seed} -p {param.phantom_type} " + f"-s {param.source_type} -r Ga68 -a {param.a}" + ) + if param.use_pet: + cmd_line += " --pet " + if param.use_gaga: + cmd_line += " --gaga " + out = f"test045_speedup_p_{param.phantom_type}_s_{param.source_type}_pet_{param.use_pet}_gaga_{param.use_gaga}.txt" + if not skip: + print("cmd line", cmd_line) + r = os.system(f"python {cmd_line}") + + print("Output ", out) + return out + + + # output + output = [] + + # Test 1 + p.phantom_type = "analytic" + p.source_type = "analytic" + p.use_gaga = False + p.a = 1e3 + out = run(p) + output.append(out) + + # Test 2 + p.phantom_type = "analytic" + p.source_type = "vox" + p.use_gaga = False + p.a = 1e3 + out = run(p) + output.append(out) + + # Test 3 + p.phantom_type = "vox" + p.source_type = "vox" + p.use_gaga = False + p.a = 1e3 + out = run(p) + output.append(out) + + # Test 4 + p.phantom_type = "analytic" + p.source_type = "analytic" + p.use_gaga = True + p.a = 1e3 + out = run(p) + output.append(out) + + # Test 5 + p.phantom_type = "analytic" + p.source_type = "vox" + p.use_gaga = True + p.a = 1e3 + out = run(p) + output.append(out) + + # Test 6 + p.phantom_type = "vox" + p.source_type = "vox" + p.use_gaga = True + p.a = 1e3 + out = run(p) + output.append(out) + + print(output) + + # tests stats file + is_ok = True + for o in output: + stats = utility.read_stat_file(paths.output / o) + stats_ref = utility.read_stat_file(paths.output_ref / o) + ok = utility.assert_stats(stats, stats_ref, 0.06) + utility.print_test(ok, f"Check {o}") + is_ok = is_ok and ok + print() + + # tests pet files + keys = [ + "GlobalTime", + "PostPosition_X", + "PostPosition_Y", + "PostPosition_Z", + "TotalEnergyDeposit", + "TrackVolumeCopyNo", + ] + tols = [10.0] * len(keys) + tols[keys.index("GlobalTime")] = 0.04 + tols[keys.index("PostPosition_X")] = 8.4 + tols[keys.index("PostPosition_Y")] = 13 + tols[keys.index("PostPosition_Z")] = 1.5 + tols[keys.index("TotalEnergyDeposit")] = 0.03 + tols[keys.index("TrackVolumeCopyNo")] = 4.1 + scalings = [1.0] * len(keys) + scalings[keys.index("GlobalTime")] = 1e-9 # time in ns + for o in output: + o = o.replace(".txt", ".root") + o1 = paths.output / o + o2 = paths.output_ref / o + img = paths.output / o.replace(".root", ".png") + ok = utility.compare_root3( + o1, + o2, + "Singles", + "Singles", + keys, + keys, + tols, + scalings, + scalings, + img, + hits_tol=5, + ) + utility.print_test(ok, f"Check {o}") + is_ok = is_ok and ok + print() + + utility.test_ok(is_ok) From bd168cc610baadb94b6106726799e50378b14802 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 23:27:55 +0200 Subject: [PATCH 096/183] Extend devtools.py --- opengate/devtools.py | 115 ++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 41 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index df64e6f53..e72a0aa0f 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -3,46 +3,11 @@ import importlib -def get_attribute_type(attribute): - if callable(attribute): - return "method" - elif isinstance(attribute, property): - return "property" - else: - return "plain" - - -def check_if_class_has_attribute(cls, attribute_name=None, attribute_type=None): - """Check if the class has the desired attribute""" - if attribute_name is None: - raise ValueError("kwarg 'attribute_name' is required. ") - warning = None - if hasattr(cls, attribute_name): - attribute = getattr(cls, attribute_name) - if attribute_type is not None: - found_attribute_type = get_attribute_type(attribute) - if found_attribute_type != attribute_type: - base_msg = ( - f"Class {cls.__name__} in module {cls.__module__} " - f"has the attribute '{attribute_name}', " - "but it is not a " - ) - if found_attribute_type in ("method", "property"): - warning = base_msg + f"{attribute_type}." - elif found_attribute_type == "plain": - warning = base_msg + "plain attribute." - else: - warning = ( - f"Class {cls.__name__} in module {cls.__module__} " - f"does NOT have the attribute '{attribute_name}'." - ) - return warning - - def apply_class_check_to_package( check_func, package_name=None, sub_package_name=None, + exclude_modules_packages=None, inherits_from=None, func_kwargs=None, ): @@ -50,6 +15,7 @@ def apply_class_check_to_package( Checks for the presence of a certain attribute type (attribute, property, or method) in all classes of the current package, optionally restricted to a sub-package. + :param check_func: Function to which the class is passed as an argument. Should return a warning string or None. :param attribute_name: Name of the attribute to check. :param attribute_type: Type of the attribute to check for (plain, property, method). :param sub_package_name: Name of the sub-package to restrict the check to (optional). @@ -72,6 +38,9 @@ def apply_class_check_to_package( if sub_package_name: package_name = f"{package_name}.{sub_package_name}" + if exclude_modules_packages is None: + exclude_modules_packages = tuple() + if inherits_from: instance_of_module = importlib.import_module( ".".join(inherits_from.split(".")[:-1]) @@ -88,18 +57,82 @@ def apply_class_check_to_package( warnings = [] # Iterate through all modules in the specified package - for _, module_name, is_pkg in pkgutil.walk_packages( + for xxx, module_name, is_pkg in pkgutil.walk_packages( package.__path__, package.__name__ + "." ): + skip_this = False + for e in exclude_modules_packages: + if e in module_name: + skip_this = True + break + if skip_this is True: + continue if not is_pkg: module = importlib.import_module(module_name) # Iterate through all members of the module for name, obj in inspect.getmembers(module): # Check if the object is a class if inspect.isclass(obj): - if inherits_from is not None and instance_of_class not in obj.mro(): + try: + if inherits_from is not None and instance_of_class not in obj.mro(): + continue + w = check_func(obj, **func_kwargs) + if w is not None: + warnings.append(w) + except TypeError: + print(f"Could not check class {repr(obj)}") continue - w = check_func(obj, **func_kwargs) - if w is not None: - warnings.append(w) return warnings + + +def get_attribute_type(attribute): + if callable(attribute): + return "method" + elif isinstance(attribute, property): + return "property" + else: + return "plain" + + +def check_if_class_has_attribute(cls, attribute_name=None, attribute_type=None): + """Check if the class has the desired attribute""" + if attribute_name is None: + raise ValueError("kwarg 'attribute_name' is required. ") + warning = None + if hasattr(cls, attribute_name): + attribute = getattr(cls, attribute_name) + if attribute_type is not None: + found_attribute_type = get_attribute_type(attribute) + if found_attribute_type != attribute_type: + base_msg = ( + f"Class {cls.__name__} in module {cls.__module__} " + f"has the attribute '{attribute_name}', " + "but it is not a " + ) + if found_attribute_type in ("method", "property"): + warning = base_msg + f"{attribute_type}." + elif found_attribute_type == "plain": + warning = base_msg + "plain attribute." + else: + warning = ( + f"Class {cls.__name__} in module {cls.__module__} " + f"does NOT have the attribute '{attribute_name}'." + ) + return warning + + +def find_unprocessed_gateobject_classes(): + def check_if_class_has_been_processed(cls): + if cls.has_been_processed(): + return None + else: + return repr(cls) + + print("Checking if there are any classes in opengate that inherit from GateObject " + "and that are not properly processed by a call to process_cls() ...") + return set(apply_class_check_to_package( + check_if_class_has_been_processed, + package_name="opengate", + inherits_from="opengate.base.GateObject", + exclude_modules_packages=('opengate.bin', 'opengate.tests.src', 'opengate.postprocessors') + )) \ No newline at end of file From 82968fc899ecf6df4e941a5240e0dfaf83fd68a8 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 23:28:26 +0200 Subject: [PATCH 097/183] Add test079_check_classes_are_processed.py --- .../test079_check_classes_are_processed.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 opengate/tests/src/test079_check_classes_are_processed.py diff --git a/opengate/tests/src/test079_check_classes_are_processed.py b/opengate/tests/src/test079_check_classes_are_processed.py new file mode 100755 index 000000000..59d644ed9 --- /dev/null +++ b/opengate/tests/src/test079_check_classes_are_processed.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from opengate.devtools import find_unprocessed_gateobject_classes +from opengate.exception import GateImplementationError +import opengate.tests.utility as utility + + +if __name__ == "__main__": + is_ok = True + exceptions = [] + + unprocessed_classes = find_unprocessed_gateobject_classes() + if len(unprocessed_classes) > 0: + is_ok = False + s = "\n".join([f"{i}) {w}" for i, w in enumerate(unprocessed_classes)]) + exceptions.append( + GateImplementationError( + f"{len(unprocessed_classes)} GateObjects are not processed upon import: \n{s}" + ) + ) + else: + print("All classes inheriting from GateObject are properly processed .") + utility.test_ok(is_ok, exceptions=exceptions) + From 786b6f98a2c25c76d037fc683b5d6a0c0a8c37f8 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 23:29:30 +0200 Subject: [PATCH 098/183] Add explicit process_cls calls where they were missing --- opengate/actors/actoroutput.py | 14 +++++++++++++- opengate/actors/base.py | 6 +++++- opengate/actors/digitizers.py | 13 +++++++++++++ opengate/actors/dynamicactors.py | 9 ++++++++- opengate/actors/filters.py | 10 +++++++++- opengate/actors/miscactors.py | 9 +++++++++ opengate/base.py | 4 ++++ opengate/managers.py | 3 +++ 8 files changed, 64 insertions(+), 4 deletions(-) diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index 011716734..14ba60947 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -1,7 +1,7 @@ from box import Box from typing import Optional -from ..base import GateObject +from ..base import GateObject, process_cls from ..utility import insert_suffix_before_extension, ensure_filename_is_str from ..exception import warning, fatal, GateImplementationError from .dataitems import ( @@ -831,3 +831,15 @@ def initialize_cpp_parameters(self): self.belongs_to_actor.SetOutputPath( self.name, self.get_output_path_as_string() ) + + +process_cls(ActorOutputBase) +process_cls(MergeableActorOutput) +process_cls(ActorOutputUsingDataItemContainer) +process_cls(ActorOutputImage) +process_cls(ActorOutputSingleImage) +process_cls(ActorOutputSingleMeanImage) +process_cls(ActorOutputSingleImageWithVariance) +process_cls(ActorOutputQuotientImage) +process_cls(ActorOutputQuotientMeanImage) +process_cls(ActorOutputRoot) diff --git a/opengate/actors/base.py b/opengate/actors/base.py index ad579ca81..92c869bbe 100644 --- a/opengate/actors/base.py +++ b/opengate/actors/base.py @@ -3,7 +3,7 @@ from ..definitions import __world_name__ from ..exception import fatal, GateImplementationError -from ..base import GateObject +from ..base import GateObject, process_cls from ..utility import insert_suffix_before_extension from .actoroutput import ActorOutputRoot @@ -451,3 +451,7 @@ def StartSimulationAction(self): def EndSimulationAction(self): """Default virtual method for inheritance""" pass + + +process_cls(ActorBase) + diff --git a/opengate/actors/digitizers.py b/opengate/actors/digitizers.py index 6dbbdd6d6..db42d9c74 100644 --- a/opengate/actors/digitizers.py +++ b/opengate/actors/digitizers.py @@ -4,6 +4,7 @@ from scipy.spatial.transform import Rotation import opengate_core as g4 +from ..base import process_cls from .base import ActorBase from ..exception import fatal from ..definitions import fwhm_to_sigma @@ -1119,3 +1120,15 @@ def EndSimulationAction(self): f"Empty output, no particles stored in {self.get_output_path()}" ) g4.GatePhaseSpaceActor.EndSimulationAction(self) + + +process_cls(DigitizerBase) +process_cls(DigitizerAdderActor) +process_cls(DigitizerBlurringActor) +process_cls(DigitizerSpatialBlurringActor) +process_cls(DigitizerEfficiencyActor) +process_cls(DigitizerEnergyWindowsActor) +process_cls(DigitizerHitsCollectionActor) +process_cls(DigitizerProjectionActor) +process_cls(DigitizerReadoutActor) +process_cls(PhaseSpaceActor) diff --git a/opengate/actors/dynamicactors.py b/opengate/actors/dynamicactors.py index 6b8f02083..a1af157a0 100644 --- a/opengate/actors/dynamicactors.py +++ b/opengate/actors/dynamicactors.py @@ -2,7 +2,7 @@ import opengate_core as g4 from ..definitions import __world_name__ -from ..base import GateObject +from ..base import GateObject, process_cls from ..geometry.utility import rot_np_as_g4, vec_np_as_g4 from ..exception import fatal from .base import ActorBase @@ -261,3 +261,10 @@ def initialize(self): def apply_change(self, run_id): self.g4_physical_volume.SetRotationHepRep3x3(self.g4_rotations[run_id]) + + +process_cls(DynamicGeometryActor) +process_cls(GeometryChanger) +process_cls(VolumeImageChanger) +process_cls(VolumeTranslationChanger) +process_cls(VolumeRotationChanger) \ No newline at end of file diff --git a/opengate/actors/filters.py b/opengate/actors/filters.py index 4d36faa03..26ae02a37 100644 --- a/opengate/actors/filters.py +++ b/opengate/actors/filters.py @@ -2,7 +2,7 @@ from typing import Optional import opengate_core as g4 -from ..base import GateObject +from ..base import GateObject, process_cls from ..exception import fatal @@ -185,3 +185,11 @@ def get_filter_class(f): fatal( f"Unknown filter '{f}'. Known filters are: {list(filter_classes.keys())}." ) + + +process_cls(FilterBase) +process_cls(ParticleFilter) +process_cls(KineticEnergyFilter) +process_cls(TrackCreatorProcessFilter) +process_cls(ThresholdAttributeFilter) +process_cls(UnscatteredPrimaryFilter) \ No newline at end of file diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 1396dfb89..d6825aa92 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -6,6 +6,7 @@ from .actoroutput import ActorOutputBase from ..serialization import dump_json from ..exception import warning +from ..base import process_cls def _setter_hook_stats_actor_output_filename(self, output_filename): @@ -431,3 +432,11 @@ def initialize(self): SplittingActorBase.initialize(self) self.InitializeUserInput(self.user_info) self.InitializeCpp() + + +process_cls(ActorOutputStatisticsActor) +process_cls(SimulationStatisticsActor) +process_cls(KillActor) +process_cls(SplittingActorBase) +process_cls(ComptSplittingActor) +process_cls(BremSplittingActor) \ No newline at end of file diff --git a/opengate/base.py b/opengate/base.py index 8f8870bfc..4a8356fb5 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -909,3 +909,7 @@ def create_gate_object_from_dict(dct): name=dct["user_info"]["name"] ) return obj + + +process_cls(GateObject) +process_cls(DynamicGateObject) diff --git a/opengate/managers.py b/opengate/managers.py index 425b11cf4..38940a9fe 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1847,4 +1847,7 @@ def create_sim_from_json(path): process_cls(PhysicsManager) process_cls(PhysicsListManager) +process_cls(VolumeManager) +process_cls(ActorManager) +process_cls(PostProcessingManager) process_cls(Simulation) From 4082c9cfe2ea6fba7d54cbad813f29a49c7441bb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 21:34:04 +0000 Subject: [PATCH 099/183] [pre-commit.ci] Automatic python and c++ formatting --- opengate/actors/base.py | 1 - opengate/actors/dynamicactors.py | 2 +- opengate/actors/filters.py | 2 +- opengate/actors/miscactors.py | 2 +- opengate/devtools.py | 29 +++++++++++++------ opengate/tests/src/test045_speedup_all_wip.py | 2 -- .../test079_check_classes_are_processed.py | 1 - 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/opengate/actors/base.py b/opengate/actors/base.py index 92c869bbe..1fadd086f 100644 --- a/opengate/actors/base.py +++ b/opengate/actors/base.py @@ -454,4 +454,3 @@ def EndSimulationAction(self): process_cls(ActorBase) - diff --git a/opengate/actors/dynamicactors.py b/opengate/actors/dynamicactors.py index a1af157a0..c17bf321c 100644 --- a/opengate/actors/dynamicactors.py +++ b/opengate/actors/dynamicactors.py @@ -267,4 +267,4 @@ def apply_change(self, run_id): process_cls(GeometryChanger) process_cls(VolumeImageChanger) process_cls(VolumeTranslationChanger) -process_cls(VolumeRotationChanger) \ No newline at end of file +process_cls(VolumeRotationChanger) diff --git a/opengate/actors/filters.py b/opengate/actors/filters.py index 26ae02a37..e59e16300 100644 --- a/opengate/actors/filters.py +++ b/opengate/actors/filters.py @@ -192,4 +192,4 @@ def get_filter_class(f): process_cls(KineticEnergyFilter) process_cls(TrackCreatorProcessFilter) process_cls(ThresholdAttributeFilter) -process_cls(UnscatteredPrimaryFilter) \ No newline at end of file +process_cls(UnscatteredPrimaryFilter) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index d6825aa92..f580f6abc 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -439,4 +439,4 @@ def initialize(self): process_cls(KillActor) process_cls(SplittingActorBase) process_cls(ComptSplittingActor) -process_cls(BremSplittingActor) \ No newline at end of file +process_cls(BremSplittingActor) diff --git a/opengate/devtools.py b/opengate/devtools.py index e72a0aa0f..dca263eb5 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -74,7 +74,10 @@ def apply_class_check_to_package( # Check if the object is a class if inspect.isclass(obj): try: - if inherits_from is not None and instance_of_class not in obj.mro(): + if ( + inherits_from is not None + and instance_of_class not in obj.mro() + ): continue w = check_func(obj, **func_kwargs) if w is not None: @@ -128,11 +131,19 @@ def check_if_class_has_been_processed(cls): else: return repr(cls) - print("Checking if there are any classes in opengate that inherit from GateObject " - "and that are not properly processed by a call to process_cls() ...") - return set(apply_class_check_to_package( - check_if_class_has_been_processed, - package_name="opengate", - inherits_from="opengate.base.GateObject", - exclude_modules_packages=('opengate.bin', 'opengate.tests.src', 'opengate.postprocessors') - )) \ No newline at end of file + print( + "Checking if there are any classes in opengate that inherit from GateObject " + "and that are not properly processed by a call to process_cls() ..." + ) + return set( + apply_class_check_to_package( + check_if_class_has_been_processed, + package_name="opengate", + inherits_from="opengate.base.GateObject", + exclude_modules_packages=( + "opengate.bin", + "opengate.tests.src", + "opengate.postprocessors", + ), + ) + ) diff --git a/opengate/tests/src/test045_speedup_all_wip.py b/opengate/tests/src/test045_speedup_all_wip.py index f7c570389..39640248b 100755 --- a/opengate/tests/src/test045_speedup_all_wip.py +++ b/opengate/tests/src/test045_speedup_all_wip.py @@ -21,7 +21,6 @@ # so the results are not reproducible (yet) seed = 812365478 - def run(param): print("run ", param) cmd_line = ( @@ -40,7 +39,6 @@ def run(param): print("Output ", out) return out - # output output = [] diff --git a/opengate/tests/src/test079_check_classes_are_processed.py b/opengate/tests/src/test079_check_classes_are_processed.py index 59d644ed9..a914c369e 100755 --- a/opengate/tests/src/test079_check_classes_are_processed.py +++ b/opengate/tests/src/test079_check_classes_are_processed.py @@ -21,4 +21,3 @@ else: print("All classes inheriting from GateObject are properly processed .") utility.test_ok(is_ok, exceptions=exceptions) - From 55f7b10c8a8153ae10bc11dac0a4fa610730505f Mon Sep 17 00:00:00 2001 From: Maxime Toussaint Date: Tue, 8 Oct 2024 17:52:52 -0400 Subject: [PATCH 100/183] Forgot to test case where none are available... Now it should work! --- core/opengate_core/g4DataSetup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/opengate_core/g4DataSetup.py b/core/opengate_core/g4DataSetup.py index 6970c0cb1..c40e69ab0 100644 --- a/core/opengate_core/g4DataSetup.py +++ b/core/opengate_core/g4DataSetup.py @@ -85,7 +85,7 @@ def download_g4_data(missing_g4_Data=None): folder_names_from_tar = set() if missing_g4_Data is None: - data_packages_needed = data_packages + data_packages_needed = list(data_packages.values()) else: data_packages_needed = [ package From 251934d466c9b8bf0c922ef982e996a2cb363e82 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Wed, 9 Oct 2024 01:18:59 +0200 Subject: [PATCH 101/183] Improve apply_class_check_to_package() --- opengate/devtools.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index dca263eb5..137955d1d 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -60,13 +60,9 @@ def apply_class_check_to_package( for xxx, module_name, is_pkg in pkgutil.walk_packages( package.__path__, package.__name__ + "." ): - skip_this = False - for e in exclude_modules_packages: - if e in module_name: - skip_this = True - break - if skip_this is True: + if any([e in module_name for e in exclude_modules_packages]): continue + if not is_pkg: module = importlib.import_module(module_name) # Iterate through all members of the module From 47c091b6140512b70323689e79ae0e94df43a352 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Wed, 9 Oct 2024 01:19:27 +0200 Subject: [PATCH 102/183] Implement automatic pyi file generation in devtools.py --- opengate/devtools.py | 112 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 17 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index 137955d1d..6a71de1c1 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -1,6 +1,10 @@ -import inspect import pkgutil +import os +import inspect import importlib +from typing import get_type_hints +from functools import partial +from pathlib import Path def apply_class_check_to_package( @@ -127,19 +131,93 @@ def check_if_class_has_been_processed(cls): else: return repr(cls) - print( - "Checking if there are any classes in opengate that inherit from GateObject " - "and that are not properly processed by a call to process_cls() ..." - ) - return set( - apply_class_check_to_package( - check_if_class_has_been_processed, - package_name="opengate", - inherits_from="opengate.base.GateObject", - exclude_modules_packages=( - "opengate.bin", - "opengate.tests.src", - "opengate.postprocessors", - ), - ) - ) + print("Checking if there are any classes in opengate that inherit from GateObject " + "and that are not properly processed by a call to process_cls() ...") + return set(apply_class_check_to_package( + check_if_class_has_been_processed, + package_name="opengate", + inherits_from="opengate.base.GateObject", + exclude_modules_packages=('opengate.bin', 'opengate.tests.src', 'opengate.postprocessors') + )) + + +def generate_pyi_for_module(module, output_dir): + # Get all classes defined in the module + classes = inspect.getmembers(module, inspect.isclass) + + module_name = module.__name__.split('.')[-1] # Just the module name, not full path + output_path = os.path.join(output_dir, f"{module_name}.pyi") + with open(output_path, "w") as stub_file: + for class_name, cls in classes: + # Skip classes not defined in the module itself (e.g., imported ones) + if cls.__module__ != module.__name__: + continue + + # Write the class definition + stub_file.write(f"class {class_name}:\n") + + # Get manual type hints using get_type_hints + type_hints = get_type_hints(cls) + + # Write properties with manual type hints + for name, hint in type_hints.items(): + stub_file.write(f" {name}: {hint.__name__}\n") + + # Get dynamically added attributes + for name, value in cls.__dict__.items(): + if name not in type_hints and not name.startswith('_'): + stub_file.write(f" {name}: {type(value).__name__}\n") + + # Add a newline between classes + stub_file.write("\n") + + +def walk_package_and_generate_pyi(package_name, package_dir, output_dir, exclude_modules_packages=None): + """ + Walk through the package and generate pyi files for all modules. + - package_name: The name of the package (e.g., 'mypackage'). + - package_dir: The root directory of the package (e.g., './mypackage'). + - output_dir: The directory where the .pyi files should be written. + """ + + if exclude_modules_packages is None: + exclude_modules_packages = tuple() + + for root, _, files in os.walk(package_dir): + # For each .py file in the directory (ignoring __init__.py for now) + for file in files: + if file.endswith(".py") and file != "__init__.py": + # Get the module name relative to the package root + module_path = Path(root) / file + relative_module_path = module_path.relative_to(package_dir) + # relative_module_path = os.path.relpath(module_path, package_dir) + module_name = relative_module_path.with_suffix('').as_posix().replace('/', '.') + # module_name = relative_module_path.replace(os.sep, ".").rstrip(".py") + + if any([e in module_name for e in exclude_modules_packages]): + continue + + # Import the module dynamically + try: + module = importlib.import_module(f"{package_name}.{module_name}") + # Generate the .pyi file for the module + generate_pyi_for_module(module, output_dir) + print(f"Generated .pyi for module: {module_name}") + except Exception as e: + print(f"Failed to generate .pyi for module: {module_name}. Error: {e}") + + # Generate __init__.pyi for directories + if "__init__.py" in files: + init_module_name = os.path.relpath(root, package_dir).replace(os.sep, ".") + try: + init_module = importlib.import_module(f"{package_name}.{init_module_name}") + generate_pyi_for_module(init_module, output_dir) + print(f"Generated .pyi for module: {init_module_name} (init)") + except Exception as e: + print(f"Failed to generate .pyi for module: {init_module_name}. Error: {e}") + + +generate_pyi_files_for_opengate = partial( + walk_package_and_generate_pyi, + package_name='opengate', + exclude_modules_packages=('tests.src', )) From ff90357d9708bb74a924dfe99dbedb43788defc5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 23:39:35 +0000 Subject: [PATCH 103/183] [pre-commit.ci] Automatic python and c++ formatting --- opengate/devtools.py | 53 ++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index 6a71de1c1..54df97054 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -131,21 +131,29 @@ def check_if_class_has_been_processed(cls): else: return repr(cls) - print("Checking if there are any classes in opengate that inherit from GateObject " - "and that are not properly processed by a call to process_cls() ...") - return set(apply_class_check_to_package( - check_if_class_has_been_processed, - package_name="opengate", - inherits_from="opengate.base.GateObject", - exclude_modules_packages=('opengate.bin', 'opengate.tests.src', 'opengate.postprocessors') - )) + print( + "Checking if there are any classes in opengate that inherit from GateObject " + "and that are not properly processed by a call to process_cls() ..." + ) + return set( + apply_class_check_to_package( + check_if_class_has_been_processed, + package_name="opengate", + inherits_from="opengate.base.GateObject", + exclude_modules_packages=( + "opengate.bin", + "opengate.tests.src", + "opengate.postprocessors", + ), + ) + ) def generate_pyi_for_module(module, output_dir): # Get all classes defined in the module classes = inspect.getmembers(module, inspect.isclass) - module_name = module.__name__.split('.')[-1] # Just the module name, not full path + module_name = module.__name__.split(".")[-1] # Just the module name, not full path output_path = os.path.join(output_dir, f"{module_name}.pyi") with open(output_path, "w") as stub_file: for class_name, cls in classes: @@ -165,14 +173,16 @@ def generate_pyi_for_module(module, output_dir): # Get dynamically added attributes for name, value in cls.__dict__.items(): - if name not in type_hints and not name.startswith('_'): + if name not in type_hints and not name.startswith("_"): stub_file.write(f" {name}: {type(value).__name__}\n") # Add a newline between classes stub_file.write("\n") -def walk_package_and_generate_pyi(package_name, package_dir, output_dir, exclude_modules_packages=None): +def walk_package_and_generate_pyi( + package_name, package_dir, output_dir, exclude_modules_packages=None +): """ Walk through the package and generate pyi files for all modules. - package_name: The name of the package (e.g., 'mypackage'). @@ -191,7 +201,9 @@ def walk_package_and_generate_pyi(package_name, package_dir, output_dir, exclude module_path = Path(root) / file relative_module_path = module_path.relative_to(package_dir) # relative_module_path = os.path.relpath(module_path, package_dir) - module_name = relative_module_path.with_suffix('').as_posix().replace('/', '.') + module_name = ( + relative_module_path.with_suffix("").as_posix().replace("/", ".") + ) # module_name = relative_module_path.replace(os.sep, ".").rstrip(".py") if any([e in module_name for e in exclude_modules_packages]): @@ -204,20 +216,27 @@ def walk_package_and_generate_pyi(package_name, package_dir, output_dir, exclude generate_pyi_for_module(module, output_dir) print(f"Generated .pyi for module: {module_name}") except Exception as e: - print(f"Failed to generate .pyi for module: {module_name}. Error: {e}") + print( + f"Failed to generate .pyi for module: {module_name}. Error: {e}" + ) # Generate __init__.pyi for directories if "__init__.py" in files: init_module_name = os.path.relpath(root, package_dir).replace(os.sep, ".") try: - init_module = importlib.import_module(f"{package_name}.{init_module_name}") + init_module = importlib.import_module( + f"{package_name}.{init_module_name}" + ) generate_pyi_for_module(init_module, output_dir) print(f"Generated .pyi for module: {init_module_name} (init)") except Exception as e: - print(f"Failed to generate .pyi for module: {init_module_name}. Error: {e}") + print( + f"Failed to generate .pyi for module: {init_module_name}. Error: {e}" + ) generate_pyi_files_for_opengate = partial( walk_package_and_generate_pyi, - package_name='opengate', - exclude_modules_packages=('tests.src', )) + package_name="opengate", + exclude_modules_packages=("tests.src",), +) From ec00a079bc94416f1210b2d99a117ff11b6e5d1f Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Wed, 9 Oct 2024 01:40:56 +0200 Subject: [PATCH 104/183] Fixes in automatic pyi file generation in devtools.py --- opengate/devtools.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index 6a71de1c1..f48011bea 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -142,6 +142,9 @@ def check_if_class_has_been_processed(cls): def generate_pyi_for_module(module, output_dir): + # Ensure output_dir exists + output_dir.mkdir(parents=True, exist_ok=True) + # Get all classes defined in the module classes = inspect.getmembers(module, inspect.isclass) @@ -184,11 +187,20 @@ def walk_package_and_generate_pyi(package_name, package_dir, output_dir, exclude exclude_modules_packages = tuple() for root, _, files in os.walk(package_dir): + + root_path = Path(root) + + # Calculate the relative path of the current directory to package_dir + relative_path = root_path.relative_to(package_dir) + + # Calculate the corresponding output directory + current_output_dir = output_dir / relative_path + # For each .py file in the directory (ignoring __init__.py for now) for file in files: if file.endswith(".py") and file != "__init__.py": # Get the module name relative to the package root - module_path = Path(root) / file + module_path = root_path / file relative_module_path = module_path.relative_to(package_dir) # relative_module_path = os.path.relpath(module_path, package_dir) module_name = relative_module_path.with_suffix('').as_posix().replace('/', '.') @@ -201,20 +213,20 @@ def walk_package_and_generate_pyi(package_name, package_dir, output_dir, exclude try: module = importlib.import_module(f"{package_name}.{module_name}") # Generate the .pyi file for the module - generate_pyi_for_module(module, output_dir) + generate_pyi_for_module(module, current_output_dir) print(f"Generated .pyi for module: {module_name}") except Exception as e: print(f"Failed to generate .pyi for module: {module_name}. Error: {e}") # Generate __init__.pyi for directories if "__init__.py" in files: - init_module_name = os.path.relpath(root, package_dir).replace(os.sep, ".") + init_module_name = relative_path.as_posix().replace('/', ".") + '.__init__' try: init_module = importlib.import_module(f"{package_name}.{init_module_name}") - generate_pyi_for_module(init_module, output_dir) - print(f"Generated .pyi for module: {init_module_name} (init)") + generate_pyi_for_module(init_module, current_output_dir) + print(f"Generated __init__.pyi for module: {init_module_name} (init)") except Exception as e: - print(f"Failed to generate .pyi for module: {init_module_name}. Error: {e}") + print(f"Failed to generate __init__.pyi for module: {init_module_name}. Error: {e}") generate_pyi_files_for_opengate = partial( From 0dffbe3a1112670236e33210557478bd2a3ff760 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 23:43:09 +0000 Subject: [PATCH 105/183] [pre-commit.ci] Automatic python and c++ formatting --- opengate/devtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/devtools.py b/opengate/devtools.py index 45229c5bc..a15f46379 100644 --- a/opengate/devtools.py +++ b/opengate/devtools.py @@ -234,7 +234,7 @@ def walk_package_and_generate_pyi( # Generate __init__.pyi for directories if "__init__.py" in files: - init_module_name = relative_path.as_posix().replace('/', ".") + '.__init__' + init_module_name = relative_path.as_posix().replace("/", ".") + ".__init__" try: init_module = importlib.import_module( f"{package_name}.{init_module_name}" From 7b4239504b6d21f74dd97800c9bb5be86ddbd257 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 9 Oct 2024 10:14:13 +0200 Subject: [PATCH 106/183] added test for g4 version --- opengate/bin/opengate_tests.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index c27a8cc44..801366f43 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -23,6 +23,7 @@ from opengate.exception import fatal, colored, color_ok, color_error, color_warning from opengate_core.testsDataSetup import check_tests_data_folder from opengate.bin.opengate_library_path import return_tests_path +from opengate_core import GateInfo CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @@ -71,6 +72,9 @@ def go( run_previously_failed_jobs, num_processes, ): + if not check_g4_version("geant4-11-01"): + print(False) + return 0 path_tests_src = return_tests_path() # returns the path to the tests/src dir test_dir_path = path_tests_src.parent @@ -221,6 +225,18 @@ def get_files_to_run(): return files_to_run, files_to_ignore +def check_g4_version(g4_version: str): + v = GateInfo.get_G4Version().replace("$Name: ", "") + v = v.replace("$", "") + print(f"Detected Geant4 version: {v}") + print(f"Required Geant4 version: {g4_version}") + if g4_version in v: + print(colored.stylize(" OK", color_ok), end="\n") + return True + else: + return False + + def select_files(files_to_run, test_id, end_id, random_tests): pattern = re.compile(r"^test([0-9]+)") From 98695f470bb930916ee40736b302197722016563 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 9 Oct 2024 10:23:07 +0200 Subject: [PATCH 107/183] converted test053_phid_6_it_model to _mt as it has dependency on _mt --- opengate/tests/src/test053_phid_05_it_ref_mt.py | 5 +++-- ...06_it_model.py => test053_phid_06_it_model_mt.py} | 3 +++ opengate/tests/src/test053_phid_08_ar_ref_mt.py | 2 +- opengate/tests/src/test053_phid_helpers2.py | 12 +++++++++--- opengate/tests/src/test068_rotation_source.py | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) rename opengate/tests/src/{test053_phid_06_it_model.py => test053_phid_06_it_model_mt.py} (95%) diff --git a/opengate/tests/src/test053_phid_05_it_ref_mt.py b/opengate/tests/src/test053_phid_05_it_ref_mt.py index c51a561c5..8a3d53384 100755 --- a/opengate/tests/src/test053_phid_05_it_ref_mt.py +++ b/opengate/tests/src/test053_phid_05_it_ref_mt.py @@ -20,10 +20,11 @@ sim = gate.Simulation() sim_name = f"{nuclide.nuclide}_5_ref" + # sim_name = 'andreas' create_sim_test053(sim, sim_name) # sources - sim.number_of_threads = 4 + sim.number_of_threads = 8 activity_in_Bq = 1000 add_source_generic(sim, z, a, activity_in_Bq) @@ -49,7 +50,7 @@ # compare with reference root file warning(f"check root files") root_model = sim.get_actor("phsp").get_output_path() - root_ref = paths.output_ref / os.path.basename(root_model) + root_ref = paths.output_ref / Path(root_model).name keys = ["KineticEnergy", "TrackCreatorModelIndex"] tols = [0.001, 0.02] img = paths.output / str(root_model).replace(".root", ".png") diff --git a/opengate/tests/src/test053_phid_06_it_model.py b/opengate/tests/src/test053_phid_06_it_model_mt.py similarity index 95% rename from opengate/tests/src/test053_phid_06_it_model.py rename to opengate/tests/src/test053_phid_06_it_model_mt.py index 74087987b..6fa9761b8 100755 --- a/opengate/tests/src/test053_phid_06_it_model.py +++ b/opengate/tests/src/test053_phid_06_it_model_mt.py @@ -47,6 +47,7 @@ def main(dependency="test053_phid_05_it_ref_mt.py"): print(f"Duration {duration / sec}") print(f"Ions {activity_in_Bq * duration / sec:.0f}") sim.run_timing_intervals = [[start_time, end_time]] + sim.number_of_threads = 8 # go sim.run(start_new_process=True) @@ -59,6 +60,8 @@ def main(dependency="test053_phid_05_it_ref_mt.py"): warning(f"check root files") root_ref = paths.output / f"test053_{nuclide.nuclide}_5_ref.root" root_model = sim.get_actor("phsp").get_output_path() + print(f"{root_ref = }") + print(f"{root_model = }") is_ok = compare_root_energy( root_ref, root_model, start_time, end_time, model_index=130, tol=0.09 ) diff --git a/opengate/tests/src/test053_phid_08_ar_ref_mt.py b/opengate/tests/src/test053_phid_08_ar_ref_mt.py index fa6d1f9a2..88cd95dc1 100755 --- a/opengate/tests/src/test053_phid_08_ar_ref_mt.py +++ b/opengate/tests/src/test053_phid_08_ar_ref_mt.py @@ -28,7 +28,7 @@ print(phsp.output_filename) mm = g4_units.mm - sim.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" sim.physics_manager.global_production_cuts.all = 1 * mm # sources diff --git a/opengate/tests/src/test053_phid_helpers2.py b/opengate/tests/src/test053_phid_helpers2.py index d72736edc..ef3b5ab03 100644 --- a/opengate/tests/src/test053_phid_helpers2.py +++ b/opengate/tests/src/test053_phid_helpers2.py @@ -25,7 +25,7 @@ def create_sim_test053(sim, sim_name, output=paths.output): world.material = "G4_WATER" # physics - sim.physics_list_name = "QGSP_BERT_EMZ" + sim.physics_manager.physics_list_name = "QGSP_BERT_EMZ" sim.physics_manager.enable_decay = True sim.physics_manager.global_production_cuts.all = 1e6 * mm sim.g4_commands_after_init.append("/process/em/pixeXSmodel ECPSSR_ANSTO") @@ -57,8 +57,13 @@ def create_sim_test053(sim, sim_name, output=paths.output): if "ref" in sim_name: f = sim.add_filter("TrackCreatorProcessFilter", "f2") - # f.process_name = "RadioactiveDecay" # G4 11.1 - f.process_name = "Radioactivation" # G4 11.2 + gi = g4.GateInfo + v = gi.get_G4Version().replace("$Name: ", "") + v = v.replace("$", "") + if "geant4-11-01" in v: + f.process_name = "RadioactiveDecay" # G4 11.1 + else: + f.process_name = "Radioactivation" # G4 11.2 # phsp.debug = True phsp.filters.append(f) @@ -78,6 +83,7 @@ def add_source_generic(sim, z, a, activity_in_Bq=1000): s1.direction.type = "iso" s1.activity = activity s1.half_life = nuclide.half_life("s") * sec + print(f"{s1.name = }") print(f"Half Life is {s1.half_life / sec:.2f} sec") return s1 diff --git a/opengate/tests/src/test068_rotation_source.py b/opengate/tests/src/test068_rotation_source.py index 7fc01bed3..6e084894e 100755 --- a/opengate/tests/src/test068_rotation_source.py +++ b/opengate/tests/src/test068_rotation_source.py @@ -173,7 +173,7 @@ def test068(tab, nb_run, nb_part, nb_source): # go ! sim.run() - + print(f"{phsp.get_output_path() = }") # Validation test f_phsp = uproot.open(phsp.get_output_path()) arr = f_phsp["PhaseSpace"].arrays() From 9f79bea7c35704823e7245be05fc7d025bce313b Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 9 Oct 2024 11:09:27 +0200 Subject: [PATCH 108/183] made all test53 depending on a mt test to mt themselves --- opengate/tests/src/test053_phid_06_it_model_mt.py | 1 - opengate/tests/src/test053_phid_08_ar_ref_mt.py | 1 - ...3_phid_09_ar_model.py => test053_phid_09_ar_model_mt.py} | 0 opengate/tests/src/test053_phid_10_all_ref_mt.py | 1 - ...phid_11_all_model.py => test053_phid_11_all_model_mt.py} | 2 +- opengate/tests/src/test053_phid_helpers2.py | 6 +++++- 6 files changed, 6 insertions(+), 5 deletions(-) rename opengate/tests/src/{test053_phid_09_ar_model.py => test053_phid_09_ar_model_mt.py} (100%) rename opengate/tests/src/{test053_phid_11_all_model.py => test053_phid_11_all_model_mt.py} (98%) diff --git a/opengate/tests/src/test053_phid_06_it_model_mt.py b/opengate/tests/src/test053_phid_06_it_model_mt.py index 6fa9761b8..a73a0b24e 100755 --- a/opengate/tests/src/test053_phid_06_it_model_mt.py +++ b/opengate/tests/src/test053_phid_06_it_model_mt.py @@ -47,7 +47,6 @@ def main(dependency="test053_phid_05_it_ref_mt.py"): print(f"Duration {duration / sec}") print(f"Ions {activity_in_Bq * duration / sec:.0f}") sim.run_timing_intervals = [[start_time, end_time]] - sim.number_of_threads = 8 # go sim.run(start_new_process=True) diff --git a/opengate/tests/src/test053_phid_08_ar_ref_mt.py b/opengate/tests/src/test053_phid_08_ar_ref_mt.py index 88cd95dc1..1854cd57d 100755 --- a/opengate/tests/src/test053_phid_08_ar_ref_mt.py +++ b/opengate/tests/src/test053_phid_08_ar_ref_mt.py @@ -32,7 +32,6 @@ sim.physics_manager.global_production_cuts.all = 1 * mm # sources - sim.number_of_threads = 4 activity_in_Bq = 1000 add_source_generic(sim, z, a, activity_in_Bq) sim.random_seed = 123456 diff --git a/opengate/tests/src/test053_phid_09_ar_model.py b/opengate/tests/src/test053_phid_09_ar_model_mt.py similarity index 100% rename from opengate/tests/src/test053_phid_09_ar_model.py rename to opengate/tests/src/test053_phid_09_ar_model_mt.py diff --git a/opengate/tests/src/test053_phid_10_all_ref_mt.py b/opengate/tests/src/test053_phid_10_all_ref_mt.py index 9c5adc0b3..54ea3d186 100755 --- a/opengate/tests/src/test053_phid_10_all_ref_mt.py +++ b/opengate/tests/src/test053_phid_10_all_ref_mt.py @@ -31,7 +31,6 @@ sim.physics_manager.global_production_cuts.all = 1 * mm # sources - sim.number_of_threads = 4 activity_in_Bq = 500 add_source_generic(sim, z, a, activity_in_Bq) diff --git a/opengate/tests/src/test053_phid_11_all_model.py b/opengate/tests/src/test053_phid_11_all_model_mt.py similarity index 98% rename from opengate/tests/src/test053_phid_11_all_model.py rename to opengate/tests/src/test053_phid_11_all_model_mt.py index c90e665fd..dbe453d0e 100755 --- a/opengate/tests/src/test053_phid_11_all_model.py +++ b/opengate/tests/src/test053_phid_11_all_model_mt.py @@ -24,7 +24,7 @@ def main(dependency="test053_phid_10_all_ref_mt.py"): if not os.path.exists(root_ref): # ignore on windows if os.name == "nt": - test_ok(True) + test_ok(False) sys.exit(0) cmd = "python " + str(paths.current / dependency) r = os.system(cmd) diff --git a/opengate/tests/src/test053_phid_helpers2.py b/opengate/tests/src/test053_phid_helpers2.py index ef3b5ab03..98f784305 100644 --- a/opengate/tests/src/test053_phid_helpers2.py +++ b/opengate/tests/src/test053_phid_helpers2.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from test053_phid_helpers1 import * +import os paths = get_default_test_paths(__file__, "", output_folder="test053") @@ -15,7 +16,10 @@ def create_sim_test053(sim, sim_name, output=paths.output): ui = sim.user_info ui.g4_verbose = False ui.g4_verbose_level = 1 - ui.number_of_threads = 1 + n_threads = int(os.cpu_count() / 2) + if os.name == "nt": + n_threads = 1 + ui.number_of_threads = n_threads ui.visu = False ui.random_seed = 123654 From 4389b3aeeb0adf5073f399e808fc862a2c02d09b Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Oct 2024 11:53:20 +0200 Subject: [PATCH 109/183] Enable Position Independent Code and update README Enabled Position Independent Code (PIC) in `CMakeLists.txt` by setting `CMAKE_POSITION_INDEPENDENT_CODE` to ON. Updated the README to address issues related to multithreading and Qt visualization on Windows, Python version compatibility, and added warnings for specific errors users might encounter. --- README.md | 15 +++++++++++---- core/CMakeLists.txt | 8 +++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 319c20fa2..9450f91a4 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ See the [User Guide](https://opengate-python.readthedocs.io/en/latest/user_guide ### How to install (short version) -*Compatible with Python 3.9, 3.10, 3.11, 3.12. On Windows multithreading, Qt visualization and the "spawn new subprocess" are not (yet) available.* +*Compatible with Python 3.9, 3.10, 3.11, 3.12. On Windows multithreading and Qt visualization are not (yet) available.* First, create an environment (not mandatory but highly advised) @@ -23,7 +23,7 @@ source opengate_env/bin/activate or you can use the conda environment. ``` -conda create --name opengate_env python=3.9 +conda create --name opengate_env python=3.12 conda activate opengate_env ``` @@ -33,6 +33,8 @@ pip install --upgrade pip pip install --pre opengate ``` +*Warning* while it is still beta, the `--pre` option is needed. + If you already installed the packages and want to upgrade to the latest version: ``` @@ -44,19 +46,24 @@ Once installed, you can run all tests: opengate_tests ```` -**WARNING** The first time you run this command, the geant4 data and the test data will be downloaded. If the download fails (on some systems), try to add the following command before running opengate_tests: +**WARNING (1)** The first time you run this command, the geant4 data and the test data will be downloaded. If the download fails (on some systems), try to add the following command before running opengate_tests: ```` export GIT_SSL_NO_VERIFY=1 ```` All tests are in the folder [here](https://github.com/OpenGATE/opengate/tree/master/opengate/tests/src). The test data (binary files) are stored, for technical reasons, in this git: https://gitlab.in2p3.fr/opengamgate/gam_tests_data (which is stored as a git submodule). -**WARNING** Some tests (e.g. test034) needs [gaga-phsp](https://github.com/dsarrut/gaga-phsp) which needs [pytorch](https://pytorch.org/) that cannot really be automatically installed by the previous pip install (at least we don't know how to do). So, in order to run those tests, you will have to install both PyTorch and gaga-phsp first with +**WARNING (2)** Some tests (e.g. test034) needs [gaga-phsp](https://github.com/dsarrut/gaga-phsp) which needs [pytorch](https://pytorch.org/) that cannot really be automatically installed by the previous pip install (at least we don't know how to do). So, in order to run those tests, you will have to install both PyTorch and gaga-phsp first with ```` pip install torch pip install gaga-phsp ```` +**WARNING (3)** With some linux systems (not all), you may encounter an error similar to “cannot allocate memory in static TLS block”. In that case, you must add a specific path to the linker as follows: +```` +export LD_PRELOAD=::${LD_PRELOAD} +```` + The documentation is here: https://opengate-python.readthedocs.io/en/latest/user_guide.html The test history can be visualized here: https://opengate.github.io/opengate_tests_results/ diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index bd30553d6..f7cb20681 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -14,6 +14,9 @@ project(opengate_core) set(CMAKE_CXX_STANDARD 17) +# Enable Position Independent Code for all targets (-fPIC) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + # Need Geant4 find_package(Geant4 REQUIRED OPTIONAL_COMPONENTS qt) include(${Geant4_USE_FILE}) @@ -103,13 +106,8 @@ if (WIN32) endif () endif () -# correct one on OSX -#target_link_libraries(opengate_core PRIVATE pybind11::module ${Geant4_LIBRARIES} Threads::Threads ${ITK_LIBRARIES}) - # additional utilities target_include_directories(opengate_core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/external/) #set(CMAKE_VERBOSE_MAKEFILE on) target_link_libraries(opengate_core PRIVATE pybind11::module ${Geant4_LIBRARIES} Threads::Threads ${ITK_LIBRARIES} fmt::fmt-header-only) - -# Do not not add ${PYTHON_LIBRARIES}) here (seg fault) From 9ed8f88880d477e61b2dc6d5b842571a2f6dbc76 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Oct 2024 11:53:32 +0200 Subject: [PATCH 110/183] Restore original overlap check flag after modifications Preserved the original `check_volumes_overlap` flag after temporarily setting it for overlap checking. Additionally, renamed an old, unused function for clarity. --- opengate/contrib/phantoms/nemaiec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/opengate/contrib/phantoms/nemaiec.py b/opengate/contrib/phantoms/nemaiec.py index 0831c11c6..721a20ae5 100644 --- a/opengate/contrib/phantoms/nemaiec.py +++ b/opengate/contrib/phantoms/nemaiec.py @@ -39,6 +39,7 @@ def add_iec_phantom( create_material(simulation) # check overlap only for debug + original_check_overlap_flag = simulation.check_volumes_overlap simulation.check_volumes_overlap = check_overlap # Outside structure @@ -64,6 +65,7 @@ def add_iec_phantom( simulation, name, thickness_z, sphere_starting_angle, toggle_sphere_order ) + simulation.check_volumes_overlap = original_check_overlap_flag return iec @@ -544,7 +546,7 @@ def get_n_samples_from_ratio(n, ratio): return n_samples -def compute_sphere_centers_and_volumes(sim, name): +def compute_sphere_centers_and_volumes_OLD_NEVER_CALLED(sim, name): spheres_diam = [10, 13, 17, 22, 28, 37] centers = [] volumes = [] From 2518f8ec18300cd9c7d66028d0dad7a60a3120dd Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Oct 2024 11:53:46 +0200 Subject: [PATCH 111/183] Add Jaszczak phantom with background source in simulation Introduce functions to define the Jaszczak phantom and its materials in simulations. Enable background source addition and volume configuration for accurate setups in phantom studies. --- opengate/contrib/phantoms/jaszczak.py | 85 +++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 opengate/contrib/phantoms/jaszczak.py diff --git a/opengate/contrib/phantoms/jaszczak.py b/opengate/contrib/phantoms/jaszczak.py new file mode 100644 index 000000000..5f8ba24fc --- /dev/null +++ b/opengate/contrib/phantoms/jaszczak.py @@ -0,0 +1,85 @@ +from opengate.contrib.phantoms.nemaiec import dump_bg_activity +from opengate.utility import g4_units + + +def add_phantom_material(simulation): + elems = ["H", "C", "O"] + w = [0.080538, 0.599848, 0.319614] + gcm3 = g4_units.g_cm3 + simulation.volume_manager.material_database.add_material_weights( + "JASZCZAK_PMMA", elems, w, 1.19 * gcm3 + ) + + +def add_jaszczak_phantom(simulation, name="jaszczak", check_overlap=False): + # https://www.meditest.fr/documentation/4.pdf + # unit + add_phantom_material(simulation) + + # check overlap only for debug + original_check_overlap_flag = simulation.check_volumes_overlap + simulation.check_volumes_overlap = check_overlap + + # Outside structure + jaszczak, internal_cylinder = add_jaszczak_body(simulation, name) + jaszczak.material = "JASZCZAK_PMMA" + + simulation.check_volumes_overlap = original_check_overlap_flag + return jaszczak, internal_cylinder + + +def add_jaszczak_body(sim, name): + cm = g4_units.cm + mm = g4_units.mm + deg = g4_units.deg + white = [1, 1, 1, 1] + thickness = 3.2 * mm + + # main cylinder interior + external_cylinder = sim.add_volume("Tubs", name=f"{name}_cylinder") + external_cylinder.rmax = 21.6 * cm / 2.0 + thickness + external_cylinder.rmin = 0 + external_cylinder.dz = 18.6 * cm / 2 + thickness * 2 + external_cylinder.sphi = 0 * deg + external_cylinder.dphi = 360 * deg + external_cylinder.material = "JASZCZAK_PMMA" + external_cylinder.color = white + + # main cylinder interior + internal_cylinder = sim.add_volume("Tubs", name=f"{name}_internal_cylinder") + internal_cylinder.mother = external_cylinder.name + internal_cylinder.rmax = 21.6 * cm / 2 + internal_cylinder.rmin = 0 + internal_cylinder.dz = 18.6 * cm / 2 + internal_cylinder.sphi = 0 * deg + internal_cylinder.dphi = 360 * deg + internal_cylinder.material = "G4_AIR" + + return external_cylinder, internal_cylinder + + +def add_background_source( + simulation, jaszczak_name, src_name, activity_bqml, verbose=False +): + # source + bg = simulation.add_source("GenericSource", f"{jaszczak_name}_{src_name}") + bg.mother = f"{jaszczak_name}_internal_cylinder" + v = simulation.volume_manager.volumes[bg.mother] + v_info = v.solid_info + # (1 cm3 = 1 mL) + """ + # no need to confine yet, nothing inside the cylinder -> should be done later + bg.position.type = "box" + bg.position.size = simulation.volume_manager.volumes[bg.mother].bounding_box_size + # this source is confined only within the mother volume, it does not include daughter volumes + # it is a tubs inside the box + bg.position.confine = bg.mother + """ + bg.particle = "e+" + bg.energy.type = "F18" + bg.activity = activity_bqml * v_info.cubic_volume + + if verbose: + s = dump_bg_activity(simulation, jaszczak_name, src_name) + print(s) + return bg, v_info.cubic_volume From a0d0ebe7f000c0efd5d886fb666ad262e312bd9b Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Oct 2024 11:54:19 +0200 Subject: [PATCH 112/183] linux: workaround to remove TLS bug Added a function to set and verify GLIBC_TUNABLES environment variable, restarting the process if needed. This ensures better configuration management on Linux systems, . --- opengate/__init__.py | 67 +++++++++++++------------------------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/opengate/__init__.py b/opengate/__init__.py index df806a5e4..8d2e68bd1 100644 --- a/opengate/__init__.py +++ b/opengate/__init__.py @@ -1,56 +1,27 @@ # This file handles the way opengate is imported. +import os +import sys -""" -import colored -import threading -print( - colored.stylize( - f"Importing opengate (thread " f"{threading.get_native_id()}) ... ", - colored.fore("dark_gray"), - ), - end="", - flush=True, -) -print(colored.stylize("done", colored.fore("dark_gray"))) -""" -# the following modules are imported respecting the package structure -# they will be available via -# `import opengate` -# `opengate.xxx.yyy` -# Modules that are mainly for internal use, such as runtiming.py or uisessions.py -# are not automatically imported. If a user needs them, s/he must import -# them specifically, e.g. `import opengate.uisessions` +def restart_with_glibc_tunables(): + """ + Restart the current process with GLIBC_TUNABLES set. + """ + # tunables_value = "glibc.rtld.optional_static_tls=2048000" + tunables_value = "glibc.rtld.optional_static_tls=1500000" -# subpackages -import opengate.sources + # Check if the environment variable is already set correctly + if os.environ.get("GLIBC_TUNABLES") != tunables_value: + # Set the environment variable + new_env = os.environ.copy() + new_env["GLIBC_TUNABLES"] = tunables_value -import opengate.geometry -import opengate.geometry.materials -import opengate.geometry.solids -import opengate.geometry.utility -import opengate.geometry.volumes + # Restart the process with the new environment + os.execve(sys.executable, [sys.executable] + sys.argv, new_env) -import opengate.actors -import opengate.contrib + # Exit the current process + sys.exit() -# modules directly under /opengate/ -import opengate.managers -import opengate.utility -import opengate.logger -import opengate.exception -import opengate.runtiming -import opengate.definitions -import opengate.userhooks -import opengate.image -import opengate.physics -import opengate.base -import opengate.engines -# import opengate.postprocessors - -# These objects are imported at the top level of the package -# because users will frequently use them -from opengate.managers import Simulation -from opengate.managers import create_sim_from_json -from opengate.utility import g4_units +if sys.platform.startswith("linux"): + restart_with_glibc_tunables() From e361f57041033d66cface485dc2576a0f77bc81f Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Oct 2024 13:18:22 +0200 Subject: [PATCH 113/183] redo import that were removed by mistake --- opengate/__init__.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/opengate/__init__.py b/opengate/__init__.py index 8d2e68bd1..6dab941ea 100644 --- a/opengate/__init__.py +++ b/opengate/__init__.py @@ -25,3 +25,34 @@ def restart_with_glibc_tunables(): if sys.platform.startswith("linux"): restart_with_glibc_tunables() + +# subpackages +import opengate.sources +import opengate.geometry +import opengate.geometry.materials +import opengate.geometry.solids +import opengate.geometry.utility +import opengate.geometry.volumes +import opengate.actors +import opengate.contrib + +# modules directly under /opengate/ +import opengate.managers +import opengate.utility +import opengate.logger +import opengate.exception +import opengate.runtiming +import opengate.definitions +import opengate.userhooks +import opengate.image +import opengate.physics +import opengate.base +import opengate.engines + +# import opengate.postprocessors + +# These objects are imported at the top level of the package +# because users will frequently use them +from opengate.managers import Simulation +from opengate.managers import create_sim_from_json +from opengate.utility import g4_units From 45359a1e5fd09008472961ebca5a642576b5253d Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 9 Oct 2024 14:00:07 +0200 Subject: [PATCH 114/183] fixed bug in starting failing jobs? --- opengate/bin/opengate_tests.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 801366f43..da7aad9b1 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -88,19 +88,26 @@ def go( + str(sys.version_info[1]) + ".json" ) - files_to_run_avail, files_to_ignore = get_files_to_run() if not run_previously_failed_jobs: + files_to_run_avail, files_to_ignore = get_files_to_run() files_to_run = select_files(files_to_run_avail, start_id, end_id, random_tests) download_data_at_first_run(files_to_run_avail[0]) + dashboard_dict_out = {k: [""] for k in files_to_run_avail} else: with open(fpath_dashboard_output, "r") as fp: - dashboard_dict_previously = json.load(fp) - files_to_run = [k for k, v in dashboard_dict_previously.items() if not v[0]] - + dashboard_dict_out = json.load(fp) + files_to_run = [k for k, v in dashboard_dict_out.items() if not v[0]] + # files_to_run = ['test049_pet_digit_blurring_v3.py', + # 'test028_ge_nm670_spect_3_proj_blur.py', + # 'test049_pet_digit_blurring_v2_mt.py', + # 'test040_gan_phsp_pet_training_dataset.py', + # 'test036_adder_depth_param.py'] + # print(f"{' ,'.join(files_to_run)}") files_to_run_part1, files_to_run_part2_depending_on_part1 = ( filter_files_with_dependencies(files_to_run, path_tests_src) ) + # print(f"{' ,'.join(files_to_run_part1)}") if len(files_to_run_part2_depending_on_part1) > 0: print( f"Found test cases with mutual dependencies, going to split evaluation into two sets. {len(files_to_run_part2_depending_on_part1)} tests will start right after first eval round." @@ -131,7 +138,6 @@ def go( runs_status_info, files_to_run_part1, no_log_on_fail ) - dashboard_dict_out = {k: [""] for k in files_to_run_avail} dashboard_dict_out.update(dashboard_dict) if fpath_dashboard_output: os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) From 0a003d93650dc4477024922361bd723a81e83766 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 9 Oct 2024 14:02:09 +0200 Subject: [PATCH 115/183] fixed mp problem: all tests writing to the identical file; changed to individual root file per test --- .../src/test049_pet_digit_blurring_helpers.py | 21 ++++++++++++------- .../src/test049_pet_digit_blurring_v1.py | 2 +- .../src/test049_pet_digit_blurring_v2_mt.py | 2 +- .../src/test049_pet_digit_blurring_v3.py | 2 +- .../src/test049_pet_digit_blurring_v4.py | 2 +- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/opengate/tests/src/test049_pet_digit_blurring_helpers.py b/opengate/tests/src/test049_pet_digit_blurring_helpers.py index c8fe7c70b..42f484113 100644 --- a/opengate/tests/src/test049_pet_digit_blurring_helpers.py +++ b/opengate/tests/src/test049_pet_digit_blurring_helpers.py @@ -16,7 +16,7 @@ paths = utility.get_default_test_paths(__file__, "gate_test049_pet_blur", "test049") -def create_simulation(sim, threads=1, singles_name="Singles"): +def create_simulation(sim, threads=1, singles_name="Singles", fname_suffix=""): # main options sim.visu = False sim.number_of_threads = threads @@ -36,7 +36,10 @@ def create_simulation(sim, threads=1, singles_name="Singles"): # add a PET Biograph pet = pet_biograph.add_pet(sim, "pet") singles = pet_biograph.add_digitizer( - sim, pet.name, paths.output / f"test049_pet.root", singles_name=singles_name + sim, + pet.name, + paths.output / f"test049_pet_{fname_suffix}.root", + singles_name=singles_name, ) # add NECR phantom @@ -131,8 +134,10 @@ def check_timing( ref_root_file, root_file, ): - times_ref = uproot.open(ref_root_file)["Hits"].arrays(library="numpy")["time"] * 1e9 - times = uproot.open(root_file)["Hits"].arrays(library="numpy")["GlobalTime"] + with uproot.open(ref_root_file) as h: + times_ref = h["Hits"].arrays(library="numpy")["time"] * 1e9 + with uproot.open(root_file) as h: + times = h["Hits"].arrays(library="numpy")["GlobalTime"] def rel_d(a, b, norm, tol): r = np.fabs(a - b) / norm * 100 @@ -154,10 +159,10 @@ def compare_stat(ref, val, tol): utility.print_test(b, f"Hits timing ref:\n{s}, Passed? {b}") is_ok = b - times_ref = ( - uproot.open(ref_root_file)["Singles"].arrays(library="numpy")["time"] * 1e9 - ) - times = uproot.open(root_file)["Singles"].arrays(library="numpy")["GlobalTime"] + with uproot.open(ref_root_file) as h: + times_ref = h["Singles"].arrays(library="numpy")["time"] * 1e9 + with uproot.open(root_file) as h: + times = h["Singles"].arrays(library="numpy")["GlobalTime"] print() s, b = compare_stat(times_ref, times, tol) diff --git a/opengate/tests/src/test049_pet_digit_blurring_v1.py b/opengate/tests/src/test049_pet_digit_blurring_v1.py index f6270c9d3..1360a9e7d 100755 --- a/opengate/tests/src/test049_pet_digit_blurring_v1.py +++ b/opengate/tests/src/test049_pet_digit_blurring_v1.py @@ -22,7 +22,7 @@ # create the simulation sim = gate.Simulation() - t49.create_simulation(sim) + t49.create_simulation(sim, fname_suffix="_v1") # start simulation sim.run() diff --git a/opengate/tests/src/test049_pet_digit_blurring_v2_mt.py b/opengate/tests/src/test049_pet_digit_blurring_v2_mt.py index ba596060e..09457fd19 100755 --- a/opengate/tests/src/test049_pet_digit_blurring_v2_mt.py +++ b/opengate/tests/src/test049_pet_digit_blurring_v2_mt.py @@ -22,7 +22,7 @@ # create the simulation sim = gate.Simulation() nb_threads = 2 - t49.create_simulation(sim, nb_threads) + t49.create_simulation(sim, nb_threads, fname_suffix="_v2mt") # start simulation sim.run() diff --git a/opengate/tests/src/test049_pet_digit_blurring_v3.py b/opengate/tests/src/test049_pet_digit_blurring_v3.py index 5f5a643a0..5800fbf26 100755 --- a/opengate/tests/src/test049_pet_digit_blurring_v3.py +++ b/opengate/tests/src/test049_pet_digit_blurring_v3.py @@ -22,7 +22,7 @@ # create the simulation sim = gate.Simulation() - t49.create_simulation(sim, singles_name="Singles_readout") + t49.create_simulation(sim, singles_name="Singles_readout", fname_suffix="_v3") # const ns = gate.g4_units.ns diff --git a/opengate/tests/src/test049_pet_digit_blurring_v4.py b/opengate/tests/src/test049_pet_digit_blurring_v4.py index 64a653a95..205fee5b9 100755 --- a/opengate/tests/src/test049_pet_digit_blurring_v4.py +++ b/opengate/tests/src/test049_pet_digit_blurring_v4.py @@ -22,7 +22,7 @@ # create the simulation sim = gate.Simulation() - t49.create_simulation(sim, singles_name="Singles_readout") + t49.create_simulation(sim, singles_name="Singles_readout", fname_suffix="_v4") # const ns = gate.g4_units.ns From 0679fd200709a751f8fcb29cf0e1982baaaaf76e Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Wed, 9 Oct 2024 14:11:38 +0200 Subject: [PATCH 116/183] Towards version 10.0beta09 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index c634985fa..cddb9a880 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.0beta08 +10.0beta09 From 61f647eaedb80ce04174190efb5014107eb92ba4 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 9 Oct 2024 17:00:32 +0200 Subject: [PATCH 117/183] fixed file paths test 28 --- .../src/test028_ge_nm670_spect_2_helpers.py | 20 +++++++++++-------- .../src/test028_ge_nm670_spect_2_hits.py | 2 +- .../src/test028_ge_nm670_spect_2_hits_mt.py | 2 +- .../src/test028_ge_nm670_spect_3_proj.py | 4 ++-- .../src/test028_ge_nm670_spect_3_proj_blur.py | 6 ++++-- .../src/test028_ge_nm670_spect_3_proj_mt.py | 4 ++-- ...est028_ge_nm670_spect_4_acc_angle_aa_se.py | 7 ++++++- ...028_ge_nm670_spect_4_acc_angle_aa_se_mt.py | 7 ++++++- ...est028_ge_nm670_spect_4_acc_angle_aa_ze.py | 7 ++++++- ...t028_ge_nm670_spect_4_acc_angle_helpers.py | 15 ++++++++------ ...test028_ge_nm670_spect_4_acc_angle_noaa.py | 5 ++++- 11 files changed, 53 insertions(+), 26 deletions(-) diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py index cfdde0d37..fdd05fee0 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py @@ -8,9 +8,10 @@ import opengate.contrib.spect.ge_discovery_nm670 as gate_spect from opengate.userhooks import check_production_cuts from opengate.tests import utility +from pathlib import Path -def create_spect_simu(sim, paths, number_of_threads=1): +def create_spect_simu(sim, paths, number_of_threads=1, version=""): # main options sim.g4_verbose = False sim.visu = False @@ -119,7 +120,7 @@ def create_spect_simu(sim, paths, number_of_threads=1): crystal = v hc.attached_to = crystal.name print("Crystal :", crystal.name) - hc.output_filename = "test028.root" + hc.output_filename = f"test028{version}.root" print(hc.output_filename) hc.attributes = [ "PostPosition", @@ -172,7 +173,7 @@ def create_spect_simu(sim, paths, number_of_threads=1): return spect -def test_add_proj(sim): +def test_add_proj(sim, fname_suffix): mm = gate.g4_units.mm for k, v in sim.volume_manager.volumes.items(): if "crystal" in k: @@ -185,7 +186,7 @@ def test_add_proj(sim): proj.spacing = [4.41806 * mm, 4.41806 * mm] proj.size = [128, 128] # proj.plane = 'XY' # not implemented yet # FIXME - proj.output_filename = "proj028.mhd" + proj.output_filename = f"proj028{fname_suffix}.mhd" # by default, the origin of the images are centered # set to False here to keep compatible with previous version proj.origin_as_image_center = False @@ -341,18 +342,21 @@ def test_spect_proj(sim, paths, proj, version="3"): print() print("Compare images (old spacing/origin)") # read image and force change the offset to be similar to old Gate - img = itk.imread(str(paths.output / "proj028.mhd")) + fname = sim.get_actor("Projection").get_output_path() + fname_stem = Path(fname).stem + fname_offset = fname_stem + "_offset.mhd" + img = itk.imread(str(paths.output / fname)) spacing = np.array(proj.projection.image.GetSpacing()) # user_info.spacing) origin = spacing / 2.0 origin[2] = 0.5 spacing[2] = 1 img.SetSpacing(spacing) img.SetOrigin(origin) - itk.imwrite(img, str(paths.output / "proj028_offset.mhd")) + itk.imwrite(img, str(paths.output / fname_offset)) is_ok = ( utility.assert_images( paths.gate_output / f"projection{version}.mhd", - paths.output / "proj028_offset.mhd", + paths.output / fname_offset, stats, tolerance=16, ignore_value=0, @@ -372,7 +376,7 @@ def test_spect_proj(sim, paths, proj, version="3"): is_ok = ( utility.assert_images( paths.output_ref / "proj028_ref.mhd", - paths.output / "proj028.mhd", + paths.output / fname, stats, tolerance=14, ignore_value=0, diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_hits.py b/opengate/tests/src/test028_ge_nm670_spect_2_hits.py index bc3f78075..480823b67 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_2_hits.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_hits.py @@ -15,7 +15,7 @@ sim = gate.Simulation() # main description - test028.create_spect_simu(sim, paths) + test028.create_spect_simu(sim, paths, version="_2_hits") # mono thread sim.number_of_threads = 1 diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_hits_mt.py b/opengate/tests/src/test028_ge_nm670_spect_2_hits_mt.py index cb5195a7c..c6653c81a 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_2_hits_mt.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_hits_mt.py @@ -15,7 +15,7 @@ sim = gate.Simulation() # main description - test028.create_spect_simu(sim, paths, number_of_threads=3) + test028.create_spect_simu(sim, paths, number_of_threads=3, version="_2_hits_mt") # go sim.run() diff --git a/opengate/tests/src/test028_ge_nm670_spect_3_proj.py b/opengate/tests/src/test028_ge_nm670_spect_3_proj.py index 62e615eb0..e57598e18 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_3_proj.py +++ b/opengate/tests/src/test028_ge_nm670_spect_3_proj.py @@ -17,8 +17,8 @@ cm = gate.g4_units.cm # main description - spect = test028.create_spect_simu(sim, paths, 1) - test028.test_add_proj(sim) + spect = test028.create_spect_simu(sim, paths, 1, version="_3_proj") + test028.test_add_proj(sim, fname_suffix="_3_proj") # rotate spect psd = 6.11 * cm diff --git a/opengate/tests/src/test028_ge_nm670_spect_3_proj_blur.py b/opengate/tests/src/test028_ge_nm670_spect_3_proj_blur.py index d24f32d3b..40fa52bcd 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_3_proj_blur.py +++ b/opengate/tests/src/test028_ge_nm670_spect_3_proj_blur.py @@ -14,8 +14,10 @@ sim = gate.Simulation() # main description - spect = test028.create_spect_simu(sim, paths, number_of_threads=1) - proj = test028.test_add_proj(sim) + spect = test028.create_spect_simu( + sim, paths, number_of_threads=1, version="_3_blur" + ) + proj = test028.test_add_proj(sim, fname_suffix="_3_blur") # change the digitizer to add blurring between the adder and the energy window mm = gate.g4_units.mm diff --git a/opengate/tests/src/test028_ge_nm670_spect_3_proj_mt.py b/opengate/tests/src/test028_ge_nm670_spect_3_proj_mt.py index 594c333b4..9429e2812 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_3_proj_mt.py +++ b/opengate/tests/src/test028_ge_nm670_spect_3_proj_mt.py @@ -16,8 +16,8 @@ cm = gate.g4_units.cm # main description - spect = test028.create_spect_simu(sim, paths, 4) - proj = test028.test_add_proj(sim) + spect = test028.create_spect_simu(sim, paths, 4, version="_3_proj_mt") + proj = test028.test_add_proj(sim, fname_suffix="_3_proj_mt") # rotate spect psd = 6.11 * cm diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_se.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_se.py index bb132ab69..035c763c1 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_se.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_se.py @@ -17,6 +17,7 @@ activity_kBq=1000, aa_enabled=True, aa_mode="SkipEvents", + version="_4_acc_angle_aa_se", ) # go @@ -24,6 +25,10 @@ # check is_ok = test028.compare_result( - sim, proj, "test028_aa_skip_events.png", sum_tolerance=20 + sim, + proj, + "test028_aa_skip_events.png", + sum_tolerance=20, + version="_4_acc_angle_aa_se", ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_se_mt.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_se_mt.py index 954a0bede..140fae247 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_se_mt.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_se_mt.py @@ -17,6 +17,7 @@ activity_kBq=1000, aa_enabled=True, aa_mode="SkipEvents", + version="_4_acc_angle_aa_se_mt", ) # go @@ -24,6 +25,10 @@ # check is_ok = test028.compare_result( - sim, proj, "test028_aa_skip_events.png", sum_tolerance=25 + sim, + proj, + "test028_aa_skip_events.png", + sum_tolerance=25, + version="_4_acc_angle_aa_se_mt", ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_ze.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_ze.py index 9cf576488..7f5427021 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_ze.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_aa_ze.py @@ -17,6 +17,7 @@ activity_kBq=1000, aa_enabled=True, aa_mode="ZeroEnergy", + version="_4_acc_angle_aa_ze", ) # go @@ -26,6 +27,10 @@ # check is_ok = test028.compare_result( - sim, proj, "test028_aa_zero_energy.png", sum_tolerance=17 + sim, + proj, + "test028_aa_zero_energy.png", + sum_tolerance=17, + version="_4_acc_angle_aa_ze", ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py index 1c338c7c0..0b6d15efb 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py @@ -19,6 +19,7 @@ def create_spect_simu( activity_kBq=300, aa_enabled=True, aa_mode="SkipEnergy", + version="", ): # main options sim.g4_verbose = False @@ -131,7 +132,9 @@ def create_spect_simu( if "crystal" in k: crystal = v hc.attached_to = crystal.name - hc.output_filename = "test028_4.root" # No output paths.output / 'test028.root' + hc.output_filename = ( + f"test028_4{version}.root" # No output paths.output / 'test028.root' + ) hc.attributes = [ "PostPosition", "TotalEnergyDeposit", @@ -173,7 +176,7 @@ def create_spect_simu( proj.spacing = [4.41806 * mm, 4.41806 * mm] proj.size = [128, 128] # proj.plane = 'XY' # not implemented yet - proj.output_filename = "proj028_colli.mhd" + proj.output_filename = f"proj028_colli{version}.mhd" print("proj filename", proj.output_filename) print("proj output path", proj.get_output_path()) @@ -189,7 +192,7 @@ def create_spect_simu( return spect, proj -def compare_result(sim, proj, fig_name, sum_tolerance=8): +def compare_result(sim, proj, fig_name, sum_tolerance=8, version=""): gate.exception.warning("Compare acceptance angle skipped particles") stats = sim.get_actor("Stats") @@ -249,7 +252,7 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8): # read image and force change the offset to be similar to old Gate gate.exception.warning("Compare projection image") - img = itk.imread(str(paths.output / "proj028_colli.mhd")) + img = itk.imread(str(paths.output / f"proj028_colli{version}.mhd")) spacing = np.array([proj.spacing[0], proj.spacing[1], 1]) print("spacing", spacing) origin = spacing / 2.0 @@ -257,12 +260,12 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8): spacing[2] = 1 img.SetSpacing(spacing) img.SetOrigin(origin) - itk.imwrite(img, str(paths.output / "proj028_colli_offset.mhd")) + itk.imwrite(img, str(paths.output / f"proj028_colli_offset{version}.mhd")) # There are not enough event to make a proper comparison, so the tol is very high is_ok = ( utility.assert_images( paths.gate_output / "projection4.mhd", - paths.output / "proj028_colli_offset.mhd", + paths.output / f"proj028_colli_offset{version}.mhd", stats, tolerance=85, ignore_value=0, diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_noaa.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_noaa.py index b36d7feea..5f7df71c3 100755 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_noaa.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_noaa.py @@ -17,11 +17,14 @@ number_of_threads=1, activity_kBq=1000, aa_enabled=False, + version="_4_acc_angle_noaa", ) # go sim.run() # check - is_ok = test028.compare_result(sim, proj, "test028_aa_noaa.png") + is_ok = test028.compare_result( + sim, proj, "test028_aa_noaa.png", version="_4_acc_angle_noaa" + ) utility.test_ok(is_ok) From efc5dbeddcfe46f0987f296b30367c8d8f7b6ee6 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Wed, 9 Oct 2024 17:00:53 +0200 Subject: [PATCH 118/183] minor change --- opengate/bin/opengate_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 801366f43..0dc2a8f7d 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -72,7 +72,7 @@ def go( run_previously_failed_jobs, num_processes, ): - if not check_g4_version("geant4-11-01"): + if not check_g4_version("geant4-11-01-patch-02"): print(False) return 0 From abd2e2c7807c6be519fbaa300d0bb802d2ce2045 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Thu, 10 Oct 2024 06:50:13 +0200 Subject: [PATCH 119/183] converted to unique filenames for tests --- .../test015_iec_4_stats.txt?ref_type=heads | 72 +++++++++++++++++++ .../test019_linac_elekta_versa_with_mlc.py | 4 +- ...linac_elekta_versa_with_mlc_phsp_source.py | 6 +- opengate/tests/src/test019_linac_phsp.py | 2 +- .../tests/src/test019_linac_phsp_helpers.py | 10 +-- opengate/tests/src/test019_linac_phsp_mt.py | 2 +- .../tests/src/test019_linac_phsp_pdgcode.py | 2 +- .../tests/src/test019_linac_phsp_source.py | 2 +- .../tests/src/test019_linac_phsp_source_mt.py | 2 +- opengate/tests/src/test019_linac_phsp_visu.py | 2 +- opengate/tests/src/test022_half_life.py | 12 ++-- 11 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 opengate/tests/src/test015_iec_4_stats.txt?ref_type=heads diff --git a/opengate/tests/src/test015_iec_4_stats.txt?ref_type=heads b/opengate/tests/src/test015_iec_4_stats.txt?ref_type=heads new file mode 100644 index 000000000..9d7550c6b --- /dev/null +++ b/opengate/tests/src/test015_iec_4_stats.txt?ref_type=heads @@ -0,0 +1,72 @@ +{ + "runs": { + "value": 1, + "unit": null + }, + "events": { + "value": 26840, + "unit": null + }, + "tracks": { + "value": 26840, + "unit": null + }, + "steps": { + "value": 775044, + "unit": null + }, + "init": { + "value": 2.58237, + "unit": "s" + }, + "duration": { + "value": 1.27068, + "unit": "s" + }, + "pps": { + "value": 21122, + "unit": null + }, + "tps": { + "value": 21122, + "unit": null + }, + "sps": { + "value": 609942, + "unit": null + }, + "start_time": { + "value": "Wed Oct 2 17:59:31 2024", + "unit": null + }, + "stop_time": { + "value": "Wed Oct 2 17:59:35 2024", + "unit": null + }, + "sim_start_time": { + "value": 0.0, + "unit": "ps" + }, + "sim_stop_time": { + "value": 1.0, + "unit": "s" + }, + "threads": { + "value": 1, + "unit": null + }, + "arch": { + "value": "Darwin", + "unit": null + }, + "python": { + "value": "3.12.5", + "unit": null + }, + "track_types": { + "value": { + "alpha": 26840 + }, + "unit": null + } +} \ No newline at end of file diff --git a/opengate/tests/src/test019_linac_elekta_versa_with_mlc.py b/opengate/tests/src/test019_linac_elekta_versa_with_mlc.py index 640277771..53afb8f4c 100755 --- a/opengate/tests/src/test019_linac_elekta_versa_with_mlc.py +++ b/opengate/tests/src/test019_linac_elekta_versa_with_mlc.py @@ -171,8 +171,8 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): print(stats) # end - f_phsp = uproot.open(phsp.get_output_path()) - arr = f_phsp["phsp"].arrays() + with uproot.open(phsp.get_output_path()) as f_phsp: + arr = f_phsp["phsp"].arrays() is_ok = is_ok_test019(arr, x_field, y_field) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test019_linac_elekta_versa_with_mlc_phsp_source.py b/opengate/tests/src/test019_linac_elekta_versa_with_mlc_phsp_source.py index d6f6da912..546ea3339 100755 --- a/opengate/tests/src/test019_linac_elekta_versa_with_mlc_phsp_source.py +++ b/opengate/tests/src/test019_linac_elekta_versa_with_mlc_phsp_source.py @@ -165,7 +165,7 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): "PDGCode", "ParticleName", ] - phsp.output_filename = "phsp_versa_mlc.root" + phsp.output_filename = "phsp_versa_mlc_wsrc.root" # start simulation sim.run() @@ -174,7 +174,7 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): print(stats) # end - f_phsp = uproot.open(paths.output / "phsp_versa_mlc.root") - arr = f_phsp["phsp"].arrays() + with uproot.open(phsp.get_output_path()) as f_phsp: + arr = f_phsp["phsp"].arrays() is_ok = is_ok_test019(arr, x_field, y_field) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test019_linac_phsp.py b/opengate/tests/src/test019_linac_phsp.py index b0a9f8c61..5876cb7e6 100755 --- a/opengate/tests/src/test019_linac_phsp.py +++ b/opengate/tests/src/test019_linac_phsp.py @@ -4,7 +4,7 @@ import test019_linac_phsp_helpers as test019 if __name__ == "__main__": - sim = test019.init_test019(1) + sim = test019.init_test019(1, version="linacphsp") s = sim.volume_manager.print_volume_tree() source = sim.get_source_user_info("Default") diff --git a/opengate/tests/src/test019_linac_phsp_helpers.py b/opengate/tests/src/test019_linac_phsp_helpers.py index c9c8c5a31..5386d57bb 100644 --- a/opengate/tests/src/test019_linac_phsp_helpers.py +++ b/opengate/tests/src/test019_linac_phsp_helpers.py @@ -13,7 +13,7 @@ ) -def init_test019(nt): +def init_test019(nt, version): # create the simulation sim = gate.Simulation() @@ -89,7 +89,7 @@ def init_test019(nt): "EventPosition", "PDGCode", ] - ta2.output_filename = "test019_hits.root" + ta2.output_filename = f"test019_hits_{version}.root" ta2.debug = False ta2.steps_to_store = "exiting" f = sim.add_filter("ParticleFilter", "f") @@ -175,7 +175,7 @@ def run_test019(sim): utility.test_ok(is_ok) -def create_simu_test019_phsp_source(sim): +def create_simu_test019_phsp_source(sim, version): # main options sim.g4_verbose = False # sim.visu = True @@ -261,7 +261,7 @@ def create_simu_test019_phsp_source(sim): "LocalTime", "EventPosition", ] - ta1.output_filename = "test019_hits_phsp_source_local.root" + ta1.output_filename = "test019_hits_phsp_source_local{version}.root" ta1.debug = False ta1.steps_to_store = "exiting" f = sim.add_filter("ParticleFilter", "f") @@ -286,7 +286,7 @@ def create_simu_test019_phsp_source(sim): "LocalTime", "EventPosition", ] - ta2.output_filename = "test019_hits_phsp_source_global.root" + ta2.output_filename = "test019_hits_phsp_source_global{version}.root" ta2.debug = False f = sim.add_filter("ParticleFilter", "f2") f.particle = "gamma" diff --git a/opengate/tests/src/test019_linac_phsp_mt.py b/opengate/tests/src/test019_linac_phsp_mt.py index b42ef3fb7..a1cf403a8 100755 --- a/opengate/tests/src/test019_linac_phsp_mt.py +++ b/opengate/tests/src/test019_linac_phsp_mt.py @@ -4,5 +4,5 @@ import test019_linac_phsp_helpers as test019 if __name__ == "__main__": - sim = test019.init_test019(3) + sim = test019.init_test019(3, "linacphsp_mt") test019.run_test019(sim) diff --git a/opengate/tests/src/test019_linac_phsp_pdgcode.py b/opengate/tests/src/test019_linac_phsp_pdgcode.py index f9bc347ee..e3dc0ccab 100755 --- a/opengate/tests/src/test019_linac_phsp_pdgcode.py +++ b/opengate/tests/src/test019_linac_phsp_pdgcode.py @@ -5,7 +5,7 @@ from opengate.tests import utility if __name__ == "__main__": - sim = test019.init_test019(1) + sim = test019.init_test019(1, "pdgcode") # start simulation sim.run() diff --git a/opengate/tests/src/test019_linac_phsp_source.py b/opengate/tests/src/test019_linac_phsp_source.py index 56f332143..724f7aa7c 100755 --- a/opengate/tests/src/test019_linac_phsp_source.py +++ b/opengate/tests/src/test019_linac_phsp_source.py @@ -8,7 +8,7 @@ if __name__ == "__main__": # create sim sim = gate.Simulation() - test019.create_simu_test019_phsp_source(sim) + test019.create_simu_test019_phsp_source(sim, "_phsp_src") # start simulation sim.run() diff --git a/opengate/tests/src/test019_linac_phsp_source_mt.py b/opengate/tests/src/test019_linac_phsp_source_mt.py index 70478d724..3d047378f 100755 --- a/opengate/tests/src/test019_linac_phsp_source_mt.py +++ b/opengate/tests/src/test019_linac_phsp_source_mt.py @@ -8,7 +8,7 @@ if __name__ == "__main__": # create sim sim = gate.Simulation() - test019.create_simu_test019_phsp_source(sim) + test019.create_simu_test019_phsp_source(sim, "_phsp_src_mt") # make it MT sim.number_of_threads = nt = 4 diff --git a/opengate/tests/src/test019_linac_phsp_visu.py b/opengate/tests/src/test019_linac_phsp_visu.py index 9dc72bff6..6049bb2ab 100755 --- a/opengate/tests/src/test019_linac_phsp_visu.py +++ b/opengate/tests/src/test019_linac_phsp_visu.py @@ -5,7 +5,7 @@ import opengate as gate if __name__ == "__main__": - sim = test019.init_test019(1) + sim = test019.init_test019(1, "linacphsp_visu") sim.visu = True sim.visu_type = "vrml" diff --git a/opengate/tests/src/test022_half_life.py b/opengate/tests/src/test022_half_life.py index a3d86e05d..4426e696f 100755 --- a/opengate/tests/src/test022_half_life.py +++ b/opengate/tests/src/test022_half_life.py @@ -85,7 +85,7 @@ def test022_half_life(n_threads=1): ta = sim.add_actor("PhaseSpaceActor", "PhaseSpace") ta.attached_to = "detector" ta.attributes = ["KineticEnergy", "GlobalTime"] - ta.output_filename = "test022_half_life.root" + ta.output_filename = f"test022_half_lifev_{n_threads}.root" # timing sim.run_timing_intervals = [ @@ -100,11 +100,11 @@ def test022_half_life(n_threads=1): print(stats) # read phsp - root = uproot.open(ta.get_output_path()) - branch = root["PhaseSpace"]["GlobalTime"] - time = branch.array(library="numpy") / sec - branch = root["PhaseSpace"]["KineticEnergy"] - E = branch.array(library="numpy") + with uproot.open(ta.get_output_path()) as root: + branch = root["PhaseSpace"]["GlobalTime"] + time = branch.array(library="numpy") / sec + branch = root["PhaseSpace"]["KineticEnergy"] + E = branch.array(library="numpy") # consider time of arrival for both sources time1 = time[E < 110 * keV] From 9bd2c7fd1ae6740ffb80c60f8a883a53d7f00ffa Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Thu, 10 Oct 2024 11:06:38 +0200 Subject: [PATCH 120/183] disentangled paths in last tests --- opengate/bin/opengate_tests.py | 14 ++++++-------- .../tests/src/test029_volume_time_rotation_1.py | 2 +- .../src/test029_volume_time_rotation_1_process.py | 2 +- .../tests/src/test029_volume_time_rotation_2.py | 2 +- .../src/test029_volume_time_rotation_helpers.py | 8 ++++---- .../tests/src/test030_dose_motion_dynamic_param.py | 2 +- .../test030_dose_motion_dynamic_param_custom.py | 2 +- opengate/tests/src/test036_adder_depth_helpers.py | 4 ++-- opengate/tests/src/test036_adder_depth_param.py | 2 +- opengate/tests/src/test036_adder_depth_repeat.py | 2 +- opengate/tests/src/test036_proj_param_1.py | 7 ++++--- .../tests/src/test036_proj_param_2_with_index.py | 2 +- .../src/test036_proj_param_3_no_parent_repeat.py | 5 +++-- .../test036_proj_param_4_not_attached_to_param.py | 4 ++-- .../src/test038_gan_phsp_spect_gan_helpers.py | 8 ++++---- .../tests/src/test038_gan_phsp_spect_gan_mt.py | 2 +- .../tests/src/test038_gan_phsp_spect_gan_se.py | 2 +- .../tests/src/test038_gan_phsp_spect_gan_ze.py | 2 +- ...060_phsp_source_particletype_fromphs_pdgcode.py | 4 ++-- 19 files changed, 38 insertions(+), 38 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index ca240876b..f79b7cde3 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -72,6 +72,7 @@ def go( run_previously_failed_jobs, num_processes, ): + start = time.time() if not check_g4_version("geant4-11-01-patch-02"): print(False) return 0 @@ -98,12 +99,7 @@ def go( with open(fpath_dashboard_output, "r") as fp: dashboard_dict_out = json.load(fp) files_to_run = [k for k, v in dashboard_dict_out.items() if not v[0]] - # files_to_run = ['test049_pet_digit_blurring_v3.py', - # 'test028_ge_nm670_spect_3_proj_blur.py', - # 'test049_pet_digit_blurring_v2_mt.py', - # 'test040_gan_phsp_pet_training_dataset.py', - # 'test036_adder_depth_param.py'] - # print(f"{' ,'.join(files_to_run)}") + files_to_run_part1, files_to_run_part2_depending_on_part1 = ( filter_files_with_dependencies(files_to_run, path_tests_src) ) @@ -137,7 +133,9 @@ def go( dashboard_dict, failure = status_summary_report( runs_status_info, files_to_run_part1, no_log_on_fail ) - + end = time.time() + run_time = timedelta(seconds=end - start) + print(f"Evaluation took in total: {run_time.seconds /60 :5.1f} min ") dashboard_dict_out.update(dashboard_dict) if fpath_dashboard_output: os.makedirs(str(fpath_dashboard_output.parent), exist_ok=True) @@ -415,7 +413,7 @@ def run_test_cases( end = time.time() run_time = timedelta(seconds=end - start) - print(f"Evaluation took in total: {run_time.seconds /60 :5.1f} min ") + print(f"Running tests took: {run_time.seconds /60 :5.1f} min ") return runs_status_info diff --git a/opengate/tests/src/test029_volume_time_rotation_1.py b/opengate/tests/src/test029_volume_time_rotation_1.py index 31a341b15..7fd41e0ac 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1.py +++ b/opengate/tests/src/test029_volume_time_rotation_1.py @@ -14,7 +14,7 @@ sim = gate.Simulation() # create sim without AA - test029.create_simulation(sim, False, paths) + test029.create_simulation(sim, False, paths, "_trot1") # for later reference, get the actors that were created by the helper function above proj_actor = sim.actor_manager.get_actor("Projection") diff --git a/opengate/tests/src/test029_volume_time_rotation_1_process.py b/opengate/tests/src/test029_volume_time_rotation_1_process.py index 13e3edd66..bda95c12b 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1_process.py +++ b/opengate/tests/src/test029_volume_time_rotation_1_process.py @@ -14,7 +14,7 @@ sim = gate.Simulation() # create sim without AA - test029.create_simulation(sim, False, paths) + test029.create_simulation(sim, False, paths, "trot1proc") # for later reference, get the actors that were created by the helper function above proj_actor = sim.actor_manager.get_actor("Projection") diff --git a/opengate/tests/src/test029_volume_time_rotation_2.py b/opengate/tests/src/test029_volume_time_rotation_2.py index d3a9b4ac0..a3538ce77 100755 --- a/opengate/tests/src/test029_volume_time_rotation_2.py +++ b/opengate/tests/src/test029_volume_time_rotation_2.py @@ -15,7 +15,7 @@ sim = gate.Simulation() # create sim without AA - test029.create_simulation(sim, True, paths) + test029.create_simulation(sim, True, paths, "_trot2") # for later reference, get the actors that were created by the helper function above proj_actor = sim.actor_manager.get_actor("Projection") diff --git a/opengate/tests/src/test029_volume_time_rotation_helpers.py b/opengate/tests/src/test029_volume_time_rotation_helpers.py index 1ec5b0b96..a5b258a46 100644 --- a/opengate/tests/src/test029_volume_time_rotation_helpers.py +++ b/opengate/tests/src/test029_volume_time_rotation_helpers.py @@ -7,7 +7,7 @@ from scipy.spatial.transform import Rotation -def create_simulation(sim, aa_flag, paths): +def create_simulation(sim, aa_flag, paths, version): # main options sim.g4_verbose = False # sim.visu = True @@ -102,12 +102,12 @@ def create_simulation(sim, aa_flag, paths): # add stat actor stat = sim.add_actor("SimulationStatisticsActor", "Stats") - stat.output_filename = "stats029.txt" + stat.output_filename = f"stats029{version}.txt" # hits collection hc = sim.add_actor("DigitizerHitsCollectionActor", "Hits") hc.attached_to = "spect_crystal" - hc.output_filename = "test029.root" + hc.output_filename = f"test029{version}.root" if sim.number_of_threads == 1: hc.root_output.write_to_disk = False hc.attributes = [ @@ -141,7 +141,7 @@ def create_simulation(sim, aa_flag, paths): proj.spacing = [4.41806 * mm, 4.41806 * mm] proj.size = [128, 128] proj.origin_as_image_center = False - proj.output_filename = "proj029.mhd" + proj.output_filename = "proj029{version}.mhd" translations = [] rotations = [] diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param.py b/opengate/tests/src/test030_dose_motion_dynamic_param.py index c4a398a9d..0fa78e1d3 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param.py @@ -65,7 +65,7 @@ # add dose actor dose = sim.add_actor("DoseActor", "dose") - dose.output_filename = "test030.mhd" + dose.output_filename = "test030-dyn.mhd" dose.attached_to = waterbox dose.size = [99, 99, 99] mm = gate.g4_units.mm diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py index b4602ad1f..eb5f0f474 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py @@ -131,7 +131,7 @@ def apply_change(self, run_id): # add dose actor dose = sim.add_actor("DoseActor", "dose") - dose.output_filename = "test030-edep.mhd" + dose.output_filename = "test030-custdyn-edep.mhd" dose.attached_to = "waterbox" dose.size = [99, 99, 99] mm = gate.g4_units.mm diff --git a/opengate/tests/src/test036_adder_depth_helpers.py b/opengate/tests/src/test036_adder_depth_helpers.py index e56185600..4ed077c0b 100644 --- a/opengate/tests/src/test036_adder_depth_helpers.py +++ b/opengate/tests/src/test036_adder_depth_helpers.py @@ -8,7 +8,7 @@ from opengate.tests import utility -def create_simulation(geom, paths): +def create_simulation(geom, paths, version): # create the simulation sim = gate.Simulation() @@ -119,7 +119,7 @@ def create_simulation(geom, paths): hc = sim.add_actor("DigitizerHitsCollectionActor", "Hits") hc.attached_to = crystal.name hc.authorize_repeated_volumes = True - hc.output_filename = "test036.root" + hc.output_filename = f"test036{version}.root" hc.attributes = [ "KineticEnergy", "PostPosition", diff --git a/opengate/tests/src/test036_adder_depth_param.py b/opengate/tests/src/test036_adder_depth_param.py index 73dd9d260..2d42ebd2f 100755 --- a/opengate/tests/src/test036_adder_depth_param.py +++ b/opengate/tests/src/test036_adder_depth_param.py @@ -10,7 +10,7 @@ ) # create and run the simulation - sim = t036.create_simulation("param", paths) + sim = t036.create_simulation("param", paths, "_par") # start simulation sim.run() diff --git a/opengate/tests/src/test036_adder_depth_repeat.py b/opengate/tests/src/test036_adder_depth_repeat.py index 7dd97eeb2..a440b13b4 100755 --- a/opengate/tests/src/test036_adder_depth_repeat.py +++ b/opengate/tests/src/test036_adder_depth_repeat.py @@ -11,7 +11,7 @@ ) # create and run the simulation - sim = t036.create_simulation("repeat", paths) + sim = t036.create_simulation("repeat", paths, "_depth_repeat") # start simulation sim.run() diff --git a/opengate/tests/src/test036_proj_param_1.py b/opengate/tests/src/test036_proj_param_1.py index 82450b199..b06786741 100755 --- a/opengate/tests/src/test036_proj_param_1.py +++ b/opengate/tests/src/test036_proj_param_1.py @@ -12,7 +12,7 @@ # create and run the simulation mm = g4_units.mm - sim = t036.create_simulation("param", paths) + sim = t036.create_simulation("param", paths, "_par1") # sim.visu = True sim.visu_type = "qt" @@ -35,7 +35,8 @@ # add a proj actor proj = sim.add_actor("DigitizerProjectionActor", "proj") proj.attached_to = "crystal" - proj.output_filename = "proj1.mha" + fname = "proj1.mha" + proj.output_filename = fname.replace(".mha", "-1.mha") proj.size = [128, 128] proj.spacing = [5 * mm, 5 * mm] @@ -45,7 +46,7 @@ # test the output stats = sim.get_actor("Stats") is_ok = utility.assert_images( - paths.output_ref / proj.output_filename, + paths.output_ref / fname, proj.get_output_path(), stats, tolerance=38, diff --git a/opengate/tests/src/test036_proj_param_2_with_index.py b/opengate/tests/src/test036_proj_param_2_with_index.py index fa999108f..05e5c0581 100755 --- a/opengate/tests/src/test036_proj_param_2_with_index.py +++ b/opengate/tests/src/test036_proj_param_2_with_index.py @@ -12,7 +12,7 @@ # create and run the simulation mm = g4_units.mm - sim = t036.create_simulation("param", paths) + sim = t036.create_simulation("param", paths, "_windx") # enlarge the source source = sim.source_manager.get_source_info("src2") diff --git a/opengate/tests/src/test036_proj_param_3_no_parent_repeat.py b/opengate/tests/src/test036_proj_param_3_no_parent_repeat.py index 2058af678..c79c513c2 100755 --- a/opengate/tests/src/test036_proj_param_3_no_parent_repeat.py +++ b/opengate/tests/src/test036_proj_param_3_no_parent_repeat.py @@ -12,12 +12,13 @@ # create and run the simulation mm = g4_units.mm - sim = t036.create_simulation("param", paths) + sim = t036.create_simulation("param", paths, "_no_parent_rep") # add a proj actor: it should not run because one of its parent is repeated proj = sim.add_actor("DigitizerProjectionActor", "proj") proj.attached_to = "crystal" - proj.output_filename = "proj1.mha" + fname = "proj1.mha" + proj.output_filename = fname.replace(".mha", "-3_no_parent_repeat.mha") proj.size = [128, 128] proj.spacing = [5 * mm, 5 * mm] diff --git a/opengate/tests/src/test036_proj_param_4_not_attached_to_param.py b/opengate/tests/src/test036_proj_param_4_not_attached_to_param.py index a8fac4949..837fb0475 100755 --- a/opengate/tests/src/test036_proj_param_4_not_attached_to_param.py +++ b/opengate/tests/src/test036_proj_param_4_not_attached_to_param.py @@ -12,7 +12,7 @@ # create and run the simulation mm = g4_units.mm - sim = t036.create_simulation("param", paths) + sim = t036.create_simulation("param", paths, "_notattached") # sim.visu = True sim.visu_type = "qt" @@ -25,7 +25,7 @@ # test another case that should fail proj2 = sim.add_actor("DigitizerProjectionActor", "proj2") proj2.attached_to = "crystal_pixel" - proj2.output_filename = "proj2.mha" + proj2.output_filename = "proj2-onlyexistsiffail.mha" proj2.size = [128, 128] proj2.spacing = [5 * mm, 5 * mm] diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_helpers.py b/opengate/tests/src/test038_gan_phsp_spect_gan_helpers.py index d0f549a20..1d0d9bd89 100644 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_helpers.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_helpers.py @@ -40,7 +40,7 @@ def generate_condition(self, n): return cond -def create_simulation(sim, paths, colli="lehr"): +def create_simulation(sim, paths, colli="lehr", version=""): # units m = gate.g4_units.m cm = gate.g4_units.cm @@ -167,7 +167,7 @@ def create_simulation(sim, paths, colli="lehr"): # add stat actor stat = sim.add_actor("SimulationStatisticsActor", "Stats") - stat.output_filename = "test038_gan_stats.txt" + stat.output_filename = f"test038_gan_stats{version}.txt" # add default digitizer (it is easy to change parameters if needed) gate_spect.add_simplified_digitizer_tc99m( @@ -175,7 +175,7 @@ def create_simulation(sim, paths, colli="lehr"): ) # gate_spect.add_ge_nm670_spect_simplified_digitizer(sim, 'spect2_crystal', paths.output / 'test033_proj_2.mhd') singles_actor = sim.actor_manager.get_actor(f"Singles_spect1_crystal") - singles_actor.output_filename = "test038_gan_singles.root" + singles_actor.output_filename = f"test038_gan_singles{version}.root" # motion of the spect, create also the run time interval """heads = [spect1] # [spect1, spect2] @@ -202,7 +202,7 @@ def create_simulation(sim, paths, colli="lehr"): "EventDirection", "EventKineticEnergy", ] - phsp_actor.output_filename = "test038_gan_phsp.root" + phsp_actor.output_filename = f"test038_gan_phsp{version}.root" return condition_generator diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_mt.py b/opengate/tests/src/test038_gan_phsp_spect_gan_mt.py index bf4127fdc..4a11987b4 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_mt.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_mt.py @@ -13,7 +13,7 @@ # create the simulation sim = gate.Simulation() sim.number_of_threads = 2 - condition_generator = t38.create_simulation(sim, paths) + condition_generator = t38.create_simulation(sim, paths, version="_spect_gan_mt") # go (cannot be spawn in another process) sim.run(start_new_process=False) diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_se.py b/opengate/tests/src/test038_gan_phsp_spect_gan_se.py index a1b90b562..edddd1767 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_se.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_se.py @@ -13,7 +13,7 @@ # create the simulation sim = gate.Simulation() sim.progress_bar = True - condition_generator = t38.create_simulation(sim, paths) + condition_generator = t38.create_simulation(sim, paths, version="_spect_can_se") gsource = sim.get_source_user_info("gaga") gsource.skip_policy = "SkipEvents" # this is SkipEvents by default diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_ze.py b/opengate/tests/src/test038_gan_phsp_spect_gan_ze.py index fb215b212..66bcaecba 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_ze.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_ze.py @@ -12,7 +12,7 @@ # create the simulation sim = gate.Simulation() - condition_generator = t38.create_simulation(sim, paths) + condition_generator = t38.create_simulation(sim, paths, version="_spect_gan_ze") gsource = sim.get_source_user_info("gaga") gsource.skip_policy = "ZeroEnergy" # this is SkipEvents by default diff --git a/opengate/tests/src/test060_phsp_source_particletype_fromphs_pdgcode.py b/opengate/tests/src/test060_phsp_source_particletype_fromphs_pdgcode.py index 60b3b492f..63691a7c2 100755 --- a/opengate/tests/src/test060_phsp_source_particletype_fromphs_pdgcode.py +++ b/opengate/tests/src/test060_phsp_source_particletype_fromphs_pdgcode.py @@ -24,13 +24,13 @@ def main(): print("create reference PhS file") t.create_test_phs( particle="proton", - phs_name=paths.output / "test_proton_offset", + phs_name=paths.output / "test_proton_offset_1", number_of_particles=1, translation=[10 * cm, 5 * cm, 0 * mm], ) print("Testing PhS source particle name") t.test_source_particle_info_from_phs( - source_file_name=paths.output / "test_proton_offset.root", + source_file_name=paths.output / "test_proton_offset_1.root", phs_file_name_out=paths.output / "test_source_PDG_proton.root", ) is_ok = t.check_value_from_root_file( From 7c7bead5fc0961071b1afaf9fed67ab865f26491 Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Thu, 10 Oct 2024 12:32:08 +0200 Subject: [PATCH 121/183] deleted wrong file --- .../test015_iec_4_stats.txt?ref_type=heads | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 opengate/tests/src/test015_iec_4_stats.txt?ref_type=heads diff --git a/opengate/tests/src/test015_iec_4_stats.txt?ref_type=heads b/opengate/tests/src/test015_iec_4_stats.txt?ref_type=heads deleted file mode 100644 index 9d7550c6b..000000000 --- a/opengate/tests/src/test015_iec_4_stats.txt?ref_type=heads +++ /dev/null @@ -1,72 +0,0 @@ -{ - "runs": { - "value": 1, - "unit": null - }, - "events": { - "value": 26840, - "unit": null - }, - "tracks": { - "value": 26840, - "unit": null - }, - "steps": { - "value": 775044, - "unit": null - }, - "init": { - "value": 2.58237, - "unit": "s" - }, - "duration": { - "value": 1.27068, - "unit": "s" - }, - "pps": { - "value": 21122, - "unit": null - }, - "tps": { - "value": 21122, - "unit": null - }, - "sps": { - "value": 609942, - "unit": null - }, - "start_time": { - "value": "Wed Oct 2 17:59:31 2024", - "unit": null - }, - "stop_time": { - "value": "Wed Oct 2 17:59:35 2024", - "unit": null - }, - "sim_start_time": { - "value": 0.0, - "unit": "ps" - }, - "sim_stop_time": { - "value": 1.0, - "unit": "s" - }, - "threads": { - "value": 1, - "unit": null - }, - "arch": { - "value": "Darwin", - "unit": null - }, - "python": { - "value": "3.12.5", - "unit": null - }, - "track_types": { - "value": { - "alpha": 26840 - }, - "unit": null - } -} \ No newline at end of file From ff8bc7c99409c87bbd41fcc8a42cc36b7363905f Mon Sep 17 00:00:00 2001 From: David Date: Thu, 10 Oct 2024 15:27:51 +0200 Subject: [PATCH 122/183] Set specific random seed and update sum_tolerance Changed the random seed from "auto" to 87456321 for reproducibility. Adjusted the sum_tolerance from 1.0 to 1.1 in the y-axis profile test to increase tolerance limit. --- opengate/tests/src/test015_iec_phantom_4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opengate/tests/src/test015_iec_phantom_4.py b/opengate/tests/src/test015_iec_phantom_4.py index 45d9696a9..90ce94b35 100755 --- a/opengate/tests/src/test015_iec_phantom_4.py +++ b/opengate/tests/src/test015_iec_phantom_4.py @@ -22,7 +22,7 @@ # main options sim.check_volumes_overlap = True - sim.random_seed = "auto" # 123456789 + sim.random_seed = 87456321 sim.output_dir = paths.output sim.progress_bar = True @@ -74,7 +74,7 @@ axis="y", tolerance=50, ignore_value=0, - sum_tolerance=1.0, + sum_tolerance=1.1, sad_profile_tolerance=3.0, ) From 539c7b43f5dabbd702c16c23eba2e1c4de8a5e6a Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Fri, 11 Oct 2024 10:13:49 +0200 Subject: [PATCH 123/183] read the expected g4 version from github workflow --- opengate/bin/opengate_tests.py | 48 +++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index f79b7cde3..8005a3508 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -12,6 +12,8 @@ from pathlib import Path import subprocess from multiprocessing import Pool +import yaml +from re import findall # from functools import partial from box import Box @@ -63,6 +65,12 @@ is_flag=True, help="Run only the tests that failed in the previous evaluation.", ) +@click.option( + "--g4_version", + "-v", + default="", + help="Only for developers: overwrite the used geant4 version str to pass the check, style: v11.2.1", +) def go( start_id, end_id, @@ -71,14 +79,20 @@ def go( processes_run, run_previously_failed_jobs, num_processes, + g4_version, ): + + path_tests_src = return_tests_path() # returns the path to the tests/src dir + test_dir_path = path_tests_src.parent start = time.time() - if not check_g4_version("geant4-11-01-patch-02"): + print(f"{g4_version=}") + if not g4_version: + print("hi") + g4_version = get_required_g4_version(path_tests_src) + if not check_g4_version(g4_version): print(False) return 0 - path_tests_src = return_tests_path() # returns the path to the tests/src dir - test_dir_path = path_tests_src.parent path_output_dashboard = test_dir_path / "output_dashboard" fpath_dashboard_output = path_output_dashboard / ( "dashboard_output_" @@ -229,18 +243,44 @@ def get_files_to_run(): return files_to_run, files_to_ignore +def get_required_g4_version(tests_dir: Path, rel_fpath=".github/workflows/main.yml"): + fpath = tests_dir.parents[2] / rel_fpath + with open(fpath) as f: + githubworfklow = yaml.safe_load(f) + g4 = githubworfklow["jobs"]["build_wheel"]["env"]["GEANT4_VERSION"] + return g4 + + def check_g4_version(g4_version: str): v = GateInfo.get_G4Version().replace("$Name: ", "") v = v.replace("$", "") print(f"Detected Geant4 version: {v}") print(f"Required Geant4 version: {g4_version}") - if g4_version in v: + g4_should = decompose_g4_versioning(g4_version) + g4_is = decompose_g4_versioning(v) + if g4_should == g4_is: print(colored.stylize(" OK", color_ok), end="\n") return True else: + print(f'{" ".join(map(str,g4_should))}') + print(f'{" ".join(map(str,g4_is))}') return False +def decompose_g4_versioning(g4str): + g4str = g4str.lower().replace("-patch", "") + # Regular expression pattern to match integers separated by . - _ or p + pattern = r"\d+(?=[._\-p ])|\d+$" + + # Find all matches + matches = re.findall(pattern, g4str) + g4_version = [int(k) for k in matches] + # removing 4 from "geant4" + if g4_version and g4_version[0] == int(4): + g4_version.pop(0) + return g4_version + + def select_files(files_to_run, test_id, end_id, random_tests): pattern = re.compile(r"^test([0-9]+)") From 36f90902883e63a2e69b52f624c590b229fa634e Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Mon, 14 Oct 2024 09:04:38 +0200 Subject: [PATCH 124/183] Remove LD_PRELOAD in the CI and in the init --- .github/workflows/main.yml | 4 --- core/opengate_core/__init__.py | 46 ++++++++++++---------------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f7f18c846..aafb2c768 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -397,10 +397,6 @@ jobs: if [[ ${{ matrix.os }} == "ubuntu-latest" ]]; then path=`opengate_library_path.py -p site_packages` export LD_LIBRARY_PATH="${path}/opengate_core.libs":${LD_LIBRARY_PATH} - path=`opengate_library_path.py -p libG4processes` - export LD_PRELOAD=${path}:${LD_PRELOAD} - path=`opengate_library_path.py -p libG4geometry` - export LD_PRELOAD=${path}:${LD_PRELOAD} fi if [[ ${{ matrix.os }} == "windows-latest" ]]; then path=`opengate_library_path.py -p site_packages` diff --git a/core/opengate_core/__init__.py b/core/opengate_core/__init__.py index ecc53ba5d..a28b44d72 100644 --- a/core/opengate_core/__init__.py +++ b/core/opengate_core/__init__.py @@ -41,37 +41,23 @@ def get_libG4_path(lib): "site-packages" in pathCurrentFile ): # opengate_core is installed using wheel (for "pip install -e .", the paths are different) reloadPython = False - if ( - "LD_LIBRARY_PATH" not in os.environ - or os.path.join(get_site_packages_dir(), "opengate_core.libs") - not in os.environ["LD_LIBRARY_PATH"] - ): - reloadPython = True + #if ( + # "LD_LIBRARY_PATH" not in os.environ + # or os.path.join(get_site_packages_dir(), "opengate_core.libs") + # not in os.environ["LD_LIBRARY_PATH"] + #): + # reloadPython = True - if ( - "LD_PRELOAD" not in os.environ - or get_libG4_path("processes") not in os.environ["LD_PRELOAD"] - or get_libG4_path("geometry") not in os.environ["LD_PRELOAD"] - ): - reloadPython = True - - if reloadPython: - print( - "opengate_core is not detected. Be sure to execute these lines before to run python:" - ) - print( - "export LD_LIBRARY_PATH=" - + os.path.join(get_site_packages_dir(), "opengate_core.libs") - + ":${LD_LIBRARY_PATH}" - ) - print( - "export LD_PRELOAD=" - + get_libG4_path("processes") - + ":" - + get_libG4_path("geometry") - + ":${LD_PRELOAD}" - ) - sys.exit(-1) + #if reloadPython: + # print( + # "opengate_core is not detected. Be sure to execute these lines before to run python:" + # ) + # print( + # "export LD_LIBRARY_PATH=" + # + os.path.join(get_site_packages_dir(), "opengate_core.libs") + # + ":${LD_LIBRARY_PATH}" + # ) + # sys.exit(-1) elif sys.platform == "win32": print(os.path.dirname(pathCurrentFile)) os.add_dll_directory(os.path.dirname(pathCurrentFile)) From f48b2ff7cd21efc7e14edd79ee5f46c1094152bb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 07:05:04 +0000 Subject: [PATCH 125/183] [pre-commit.ci] Automatic python and c++ formatting --- core/opengate_core/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/opengate_core/__init__.py b/core/opengate_core/__init__.py index a28b44d72..1ec9fc4fe 100644 --- a/core/opengate_core/__init__.py +++ b/core/opengate_core/__init__.py @@ -41,14 +41,14 @@ def get_libG4_path(lib): "site-packages" in pathCurrentFile ): # opengate_core is installed using wheel (for "pip install -e .", the paths are different) reloadPython = False - #if ( + # if ( # "LD_LIBRARY_PATH" not in os.environ # or os.path.join(get_site_packages_dir(), "opengate_core.libs") # not in os.environ["LD_LIBRARY_PATH"] - #): + # ): # reloadPython = True - #if reloadPython: + # if reloadPython: # print( # "opengate_core is not detected. Be sure to execute these lines before to run python:" # ) From 6f0adb25debd92e9286141b3b85df30aacc927eb Mon Sep 17 00:00:00 2001 From: Andreas Resch Date: Mon, 14 Oct 2024 11:32:47 +0200 Subject: [PATCH 126/183] put read planned g4 version into try except statement if not available --- opengate/bin/opengate_tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 8005a3508..8832a09a6 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -85,10 +85,11 @@ def go( path_tests_src = return_tests_path() # returns the path to the tests/src dir test_dir_path = path_tests_src.parent start = time.time() - print(f"{g4_version=}") if not g4_version: - print("hi") - g4_version = get_required_g4_version(path_tests_src) + try: + g4_version = get_required_g4_version(path_tests_src) + except: + g4_version = "v11.2.1" if not check_g4_version(g4_version): print(False) return 0 From baeea9ed01252e8f297a3cd4393d180c44404c16 Mon Sep 17 00:00:00 2001 From: tbaudier Date: Mon, 14 Oct 2024 17:18:19 +0200 Subject: [PATCH 127/183] Update G4 version in developer_guide.md --- docs/source/developer_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/developer_guide.md b/docs/source/developer_guide.md index 784b967c0..8db7c2045 100644 --- a/docs/source/developer_guide.md +++ b/docs/source/developer_guide.md @@ -46,7 +46,7 @@ If you wish to use QT, you must install qt5 **before** installing Geant4 so that For **Geant4**, you need to compile with the following options: ```bash -git clone --branch v11.1.1 https://github.com/Geant4/geant4.git --depth 1 +git clone --branch v11.2.1 https://github.com/Geant4/geant4.git --depth 1 mkdir geant4.11-build cd geant4.11-build cmake -DCMAKE_CXX_FLAGS=-std=c++17 \ @@ -61,7 +61,7 @@ make -j 32 Change the QT flag (GEANT4_USE_QT) to OFF if you did not install QT. -WARNING : since June 2023, [Geant4 11.1.1](https://geant4.web.cern.ch/download/11.1.1.html) is needed. +WARNING : since June 2024, [Geant4 11.2.1](https://geant4.web.cern.ch/download/11.2.1.html) is needed. #### STEP 2 - ITK From c9acd5c456d7fada97b6c69e9ee5b2442ab1a97b Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Wed, 16 Oct 2024 10:32:14 +0200 Subject: [PATCH 128/183] Git ignore .pyi files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 856bb7270..c9f52b97c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__ .DS_Store *.jpeg *.save +*.pyi /.idea/ .idea From 926087ab9909b128f566f84462eebd74515ed957 Mon Sep 17 00:00:00 2001 From: Jing Zhang Date: Wed, 16 Oct 2024 11:11:41 +0200 Subject: [PATCH 129/183] Update user_guide_2_4_actors.md update `DigitizerEnergyWindowsActor` and `DigitizerProjectionActor`. --- docs/source/user_guide_2_4_actors.md | 48 +++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/docs/source/user_guide_2_4_actors.md b/docs/source/user_guide_2_4_actors.md index 5a2c9816c..23ec78502 100644 --- a/docs/source/user_guide_2_4_actors.md +++ b/docs/source/user_guide_2_4_actors.md @@ -262,13 +262,53 @@ warning: if blur leads to point outside volume (keep_in_solid_limits option). Us #### DigitizerEnergyWindowsActor -(documentation TODO) -for spect, test028 +The `DigitizerEnergyWindowsActor` is used in both PET and SPECT simulations to define energy windows, which limit the range of particle energies that are accepted for further processing. This actor helps to filter out noise or unwanted events by selecting only particles within a specified energy range, which is crucial for ensuring accurate imaging results. + +In PET simulations, the energy window is typically centered around the 511 keV annihilation photon. The energy range can be set to ensure only relevant events are processed: + +```python +# EnergyWindows for PET +ew = sim.add_actor("DigitizerEnergyWindowsActor", "EnergyWindows") +ew.attached_to = hc.attached_to +ew.input_digi_collection = "Singles" +ew.channels = [{"name": ew.name, "min": 425 * keV, "max": 650 * keV}] # 511 keV window +ew.output_filename = root_name +``` + +For SPECT simulations, the energy windows can be more complex, with multiple energy channels to capture different energy peaks, such as a photopeak and scatter events: + +```python +# EnergyWindows for SPECT +ew = sim.add_actor("DigitizerEnergyWindowsActor", "EnergyWindows") +ew.attached_to = hc.attached_to +ew.input_digi_collection = "Singles" +ew.channels = [ + {"name": "scatter", "min": 114 * keV, "max": 126 * keV}, + {"name": "peak140", "min": 126 * keV, "max": 154.55 * keV}, # Typical for Tc-99m +] +ew.output_filename = hc.output_filename +``` + +In both cases, `DigitizerEnergyWindowsActor` ensures that only particles with energies falling within the defined channels are accepted, aiding in improving image quality by reducing unwanted events. + +For SPECT, please refer to test028; for PET, please refer to test037. #### DigitizerProjectionActor -(documentation TODO) -for spect, test028 +The `DigitizerProjectionActor` generates 2D projections from digitized particle hits in SPECT or PET simulations. It takes input collections (e.g., scatter or peak energy channels) and creates a projection image based on predefined grid spacing and size. The result is saved in a specified output file. + +```python +# DigitizerProjectionActor setup +proj = sim.add_actor("DigitizerProjectionActor", "Projection") +proj.attached_to = hc.attached_to # Attach to crystal volume +proj.input_digi_collections = ["scatter", "peak140", "Singles"] # Use multiple energy channels +proj.spacing = [4.41806 * mm, 4.41806 * mm] # Set pixel spacing in mm +proj.size = [128, 128] # Image size in pixels (128x128) +proj.origin_as_image_center = False # Origin is not at image center +proj.output_filename = 'projection.mhd' # Output file path +``` + +For SPECT, please refer to test028. #### DigitizerEfficiencyActor From 6968db55d86776d0c29c28d90de47472b152b91d Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Wed, 16 Oct 2024 11:25:07 +0200 Subject: [PATCH 130/183] Use macos13 instead of macos12 Brew do not support macos12 anymore --- .github/workflows/main.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aafb2c768..f53480a2d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-12, windows-latest] + os: [ubuntu-latest, macos-13, windows-latest] python-version: [3.9, '3.10', '3.11', '3.12'] env: @@ -33,7 +33,7 @@ jobs: if [[ ${{ matrix.os }} == "ubuntu-latest" ]]; then export GIT_SSL_NO_VERIFY=1 git submodule update --init --recursive - elif [[ ${{ matrix.os }} == "macos-12" ]]; then + elif [[ ${{ matrix.os }} == "macos-13" ]]; then export GIT_SSL_NO_VERIFY=1 git submodule update --init --recursive else @@ -53,7 +53,7 @@ jobs: varOS=`cat /etc/os-release | grep "VERSION=" | grep -oP '(?<=\").*?(?=\")'` varOS=($varOS) echo "release=${varOS[0]}" >> $GITHUB_OUTPUT - elif [[ ${{ matrix.os }} == "macos-12" ]]; then + elif [[ ${{ matrix.os }} == "macos-13" ]]; then varOS=`sw_vers | grep "ProductVersion:"` varOS="${varOS#*:}" echo "release=${varOS:1}" >> $GITHUB_OUTPUT @@ -103,18 +103,18 @@ jobs: mv dist_opengate/* dist/ fi - uses: conda-incubator/setup-miniconda@v3 - if: (matrix.os == 'macos-12') || (matrix.os == 'windows-latest') + if: (matrix.os == 'macos-13') || (matrix.os == 'windows-latest') with: miniconda-version: "latest" auto-update-conda: true activate-environment: opengate_core python-version: ${{ matrix.python-version }} - name: Set up Homebrew - if: matrix.os == 'macos-12' + if: matrix.os == 'macos-13' id: set-up-homebrew uses: Homebrew/actions/setup-homebrew@master - name: Create opengate_core Wheel Mac - if: matrix.os == 'macos-12' + if: matrix.os == 'macos-13' shell: bash -l {0} run: | brew install python@3.12 || true @@ -191,7 +191,7 @@ jobs: delocate-listdeps --all fixed_wheels/*.whl mv fixed_wheels dist cd dist - find . -name '*whl' -exec bash -c ' mv $0 ${0/macosx_12_0/macosx_10_9}' {} \; + find . -name '*whl' -exec bash -c ' mv $0 ${0/macosx_13_0/macosx_10_9}' {} \; cd ../.. mv core/dist . - name: Create opengate_core Wheel Windows @@ -299,7 +299,7 @@ jobs: # env: # GEANT4_VERSION: 'v11.2.1' # ITK_VERSION: 'v5.2.1' -# runs-on: macos-12 +# runs-on: macos-13 # steps: # - name: Checkout github repo # uses: actions/checkout@v4 @@ -348,7 +348,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-12, windows-latest] + os: [ubuntu-latest, macos-13, windows-latest] python-version: [3.9, '3.10', '3.11', '3.12'] steps: - uses: actions/download-artifact@v4 @@ -377,7 +377,7 @@ jobs: export OSNAME="manylinux" export PLATFORM="x86_" pip install torch --extra-index-url https://download.pytorch.org/whl/cpu - elif [[ ${{ matrix.os }} == "macos-12" ]]; then + elif [[ ${{ matrix.os }} == "macos-13" ]]; then export OSNAME="macosx" export PLATFORM="x86_" which python From 3ed7129e742f3ebaf757809653694d43a055b72f Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Wed, 16 Oct 2024 16:16:42 +0200 Subject: [PATCH 131/183] Add pyyaml dependency for opengate_tests --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index bc0a8372f..e303bf599 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ "jsonpickle", "pandas", "requests", + "PyYAML", ] + install_requires_windows, ) From 842bd3c6f0874af23041acbdd3be084bd6cbfe8e Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Wed, 16 Oct 2024 16:17:00 +0200 Subject: [PATCH 132/183] Clearer print --- opengate/bin/opengate_tests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index 8832a09a6..f35343a6c 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -295,7 +295,10 @@ def select_files(files_to_run, test_id, end_id, random_tests): if f_test_id >= test_id and f_test_id <= end_id: files_new.append(f) else: - print(f"Ignoring: {f:<40} (< {test_id}) ") + if f_test_id < test_id: + print(f"Ignoring: {f:<40} (< {test_id}) ") + else: + print(f"Ignoring: {f:<40} (> {end_id}) ") files_to_run = files_new elif random_tests: files_new = files_to_run[-10:] From f9428e09f300c756e2994c9fbcb6b2976ef9059e Mon Sep 17 00:00:00 2001 From: Jing Zhang Date: Wed, 16 Oct 2024 16:36:20 +0200 Subject: [PATCH 133/183] Update user_guide_2_0_simulation.md --- docs/source/user_guide_2_0_simulation.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/source/user_guide_2_0_simulation.md b/docs/source/user_guide_2_0_simulation.md index 0957e49dc..30198b66b 100644 --- a/docs/source/user_guide_2_0_simulation.md +++ b/docs/source/user_guide_2_0_simulation.md @@ -81,6 +81,27 @@ The **verbosity**, i.e. the messages printed on the screen, are controlled via v - `ui.g4_verbose_level`: level of the Geant4 verbose system - `ui.visu_verbose`: enable or disable Geant4 verbose during visualisation +Examples in code: +``` +from opengate.userhooks import check_production_cuts + +sim = gate.Simulation() +sim.user_hook_after_init = check_production_cuts # cuts length (millimeter) of gamma, electron, proton, positron + +# geant4 verbose +sim.g4_commands_after_init.append("/tracking/verbose 1") # track particles. 0: no info, 1,2,3: detailed level +sim.g4_verbose_level_tracking = 1 # same as above + +sim.g4_verbose = True # similar as above, default is False +sim.g4_verbose_level = 0 # when True, 0: general info; 1,2,3: detailed level, default is 1 + +# opengate verbose +sim.verbose_level = gate.logger.DEBUG # actors info +sim.verbose_level = gate.logger.INFO # default output +sim.running_verbose_level = gate.logger.EVENT # events info +sim.running_verbose_level = gate.logger.RUN # cpu threads info +``` + #### Visualisation **Visualisation** is enabled with `ui.visu = True`. Then, you have the choice to choose between qt, vrml or gdml interface. From 6b53a2fd4ec3491ed8eb25a05967188ec7d0597c Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Wed, 9 Oct 2024 16:13:25 +0200 Subject: [PATCH 134/183] Create an opengate_core wheel whithout visu option For cluster User manylinux2014 for more compatibility --- .../workflows/Dockerfile_opengate_core_novis | 36 +++++ .github/workflows/createWheelLinux_novis.sh | 16 +++ .github/workflows/main.yml | 130 +++++++++++++++--- docs/source/user_guide_1_intro.md | 7 + pyproject.toml | 3 + setup.py | 2 +- 6 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/Dockerfile_opengate_core_novis create mode 100755 .github/workflows/createWheelLinux_novis.sh diff --git a/.github/workflows/Dockerfile_opengate_core_novis b/.github/workflows/Dockerfile_opengate_core_novis new file mode 100644 index 000000000..06301f3fb --- /dev/null +++ b/.github/workflows/Dockerfile_opengate_core_novis @@ -0,0 +1,36 @@ +#Docker for opengate_core +#systemctl start docker +#login: docker login +#build: docker build -t tbaudier/opengate_core -f Dockerfile_opengate_core_novis . +#push: docker push tbaudier/opengate_core +#run: docker run --rm -e "PYTHONFOLDER=${PYTHONFOLDERMANYLINUX}" -v $(Pipeline.Workspace)/software:/home tbaudier/opengate_core /home/opengate_core/azureCreateWheelLinux.sh +#interactive: docker run -ti --rm -v $(Pipeline.Workspace)/software:/home quay.io/pypa/manylinux2014_x86_64 /bin/bash + +FROM quay.io/pypa/manylinux2014_x86_64 +MAINTAINER Thomas Baudier +#Install packages +RUN yum install -y gcc wget git expat-devel fftw-devel freeglut-devel libXmu-devel xerces-c-devel \ + +#Create folder + && mkdir -p /software/cmake /software/geant4/src /software/geant4/bin /software/geant4/data /software/itk/src /software/itk/bin /software/wheelhouse \ + +#Install cmake + && cd /software/cmake \ + && wget https://github.com/Kitware/CMake/releases/download/v3.18.2/cmake-3.18.2-Linux-x86_64.tar.gz \ + && tar xzvf cmake-3.18.2-Linux-x86_64.tar.gz \ + && export PATH=/software/cmake/cmake-3.18.2-Linux-x86_64/bin/:${PATH} \ + +#Compile Geant4 + && cd /software/geant4 \ + && git clone --branch v11.2.1 https://github.com/Geant4/geant4.git --depth 1 src \ + && cd bin \ + && cmake -DCMAKE_CXX_FLAGS=-std=c++17 -DGEANT4_BUILD_MULTITHREADED=ON -DGEANT4_USE_GDML=OFF -DGEANT4_INSTALL_DATA=OFF -DGEANT4_INSTALL_DATADIR=/sofware/geant4/data -DGEANT4_USE_QT=OFF -DGEANT4_USE_OPENGL_X11=OFF ../src \ + && make -j10 \ + && source /software/geant4/bin/geant4make.sh \ + +#Compile ITK + && cd /software/itk \ + && git clone --branch v5.2.1 https://github.com/InsightSoftwareConsortium/ITK.git --depth 1 src \ + && cd bin \ + && cmake -DCMAKE_CXX_FLAGS=-std=c++17 -DBUILD_TESTING=OFF -DITK_USE_FFTWD=ON -DITK_USE_FFTWF=ON -DITK_USE_SYSTEM_FFTW:BOOL=ON ../src \ + && make -j10 diff --git a/.github/workflows/createWheelLinux_novis.sh b/.github/workflows/createWheelLinux_novis.sh new file mode 100755 index 000000000..3260067d2 --- /dev/null +++ b/.github/workflows/createWheelLinux_novis.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e -x +echo ${PYTHONFOLDER} +cd /home/core/ +rm -rf build opengate_core.egg-info opengate_core/plugins opengate_core/opengate_core.cpython*.so +sed -i 's/name="opengate-core"/name="opengate-core-novis"/' setup.py +export PATH=/software/cmake/cmake-3.18.2-Linux-x86_64/bin/:${PATH} +source /software/geant4/bin/geant4make.sh +export CMAKE_PREFIX_PATH=/software/geant4/bin:/software/itk/bin/:${CMAKE_PREFIX_PATH} +/opt/python/${PYTHONFOLDER}/bin/pip install wget colored +/opt/python/${PYTHONFOLDER}/bin/python setup.py sdist bdist_wheel +auditwheel repair /home/core/dist/*.whl -w /software/wheelhouse/ --plat "manylinux2014_x86_64" +cp -r /software/wheelhouse /home/ +#/opt/python/${PYTHONFOLDER}/bin/pip install twine +#/opt/python/${PYTHONFOLDER}/bin/twine upload --repository-url https://test.pypi.org/legacy/ wheelhouse/*manylinux2014*.whl diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f53480a2d..51cc6cf4d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,39 @@ on: workflow_dispatch: jobs: - build_wheel: + build_opengate_wheel: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: [3.9] + steps: + - name: Checkout github repo + uses: actions/checkout@v4 + - name: Checkout submodules + shell: bash -l {0} + run: | + export GIT_SSL_NO_VERIFY=1 + git submodule update --init --recursive + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: 'x64' + - name: Create opengate Wheel + run: | + rm -rf $GITHUB_WORKSPACE/opengate/tests/data + cp $GITHUB_WORKSPACE/.git/modules/gam-tests/data/HEAD $GITHUB_WORKSPACE/opengate/tests/ + pip install build + python -m build + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: dist-opengate + path: dist/ + + build_opengate_core_wheel: runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -68,16 +100,6 @@ jobs: path: ~/software key: ${{ runner.os }}-${{ steps.get-os-version.outputs.release }}_geant4_${{ env.GEANT4_VERSION }}_itk_${{ env.ITK_VERSION }}_build2 restore-keys: ${{ runner.os }}-${{ steps.get-os-version.outputs.release }}_geant4_${{ env.GEANT4_VERSION }}_itk_${{ env.ITK_VERSION }}_build2 - - name: Create opengate Wheel - if: matrix.os == 'ubuntu-latest' - run: | - if [ ${{ matrix.python-version }} == "3.9" ]; then - rm -rf $GITHUB_WORKSPACE/opengate/tests/data - cp $GITHUB_WORKSPACE/.git/modules/gam-tests/data/HEAD $GITHUB_WORKSPACE/opengate/tests/ - pip install build - python -m build - mv dist dist_opengate - fi - name: Create opengate_core Wheel Linux if: matrix.os == 'ubuntu-latest' run: | @@ -96,12 +118,6 @@ jobs: rm -rf dist mv wheelhouse dist sudo chown -R runner:docker dist - if [ ${{ matrix.python-version }} == "3.9" ]; then - ls -lrt . - ls -lrt dist/ - ls -lrt dist_opengate/ - mv dist_opengate/* dist/ - fi - uses: conda-incubator/setup-miniconda@v3 if: (matrix.os == 'macos-13') || (matrix.os == 'windows-latest') with: @@ -259,12 +275,75 @@ jobs: - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: dist-${{ matrix.os }}-${{ matrix.python-version }} + name: dist-${{ matrix.os }}-${{ matrix.python-version }}-opengate-core + path: dist/ + + build_opengate_core_novis_wheel: + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: [3.9, '3.10', '3.11', '3.12'] + + env: + GEANT4_VERSION: 'v11.2.1' + ITK_VERSION: 'v5.2.1' + + steps: + - name: Checkout github repo + uses: actions/checkout@v4 + - name: Checkout submodules + shell: bash -l {0} + run: | + export GIT_SSL_NO_VERIFY=1 + git submodule update --init --recursive + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: 'x64' + - name: Get OS version + id: get-os-version + shell: bash -l {0} + run: | + varOS=`cat /etc/os-release | grep "VERSION=" | grep -oP '(?<=\").*?(?=\")'` + varOS=($varOS) + echo "release=${varOS[0]}" >> $GITHUB_OUTPUT + - name: Cache modules + id: cache_opengate_core_dependencies + uses: actions/cache@v4 + with: + path: ~/software + key: ${{ runner.os }}-${{ steps.get-os-version.outputs.release }}_geant4_${{ env.GEANT4_VERSION }}_itk_${{ env.ITK_VERSION }}_build2 + restore-keys: ${{ runner.os }}-${{ steps.get-os-version.outputs.release }}_geant4_${{ env.GEANT4_VERSION }}_itk_${{ env.ITK_VERSION }}_build2 + - name: Create opengate_core_novis Wheel Linux + run: | + if [ ${{ matrix.python-version }} == "3.9" ]; then + export PYTHONFOLDER="cp39-cp39" + elif [ ${{ matrix.python-version }} == "3.10" ]; then + export PYTHONFOLDER="cp310-cp310" + elif [ ${{ matrix.python-version }} == "3.11" ]; then + export PYTHONFOLDER="cp311-cp311" + elif [ ${{ matrix.python-version }} == "3.12" ]; then + export PYTHONFOLDER="cp312-cp312" + fi + mkdir -p $HOME/software + docker run --rm -e "PYTHONFOLDER=${PYTHONFOLDER}" -v $GITHUB_WORKSPACE:/home tbaudier/opengate_core:${{ env.GEANT4_VERSION }}_novis /home/.github/workflows/createWheelLinux_novis.sh + ls wheelhouse + rm -rf dist + mv wheelhouse dist + sudo chown -R runner:docker dist + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: dist-${{ matrix.os }}-${{ matrix.python-version }}-opengate-core-novis path: dist/ publish_wheel: runs-on: ubuntu-latest - needs: [build_wheel] + needs: [build_opengate_wheel, build_opengate_core_wheel, build_opengate_core_novis_wheel] steps: - name: Checkout github repo uses: actions/checkout@v4 @@ -275,9 +354,18 @@ jobs: path: dist/ - name: Separate the wheels run: | - mkdir dist_opengate_core dist_opengate + mkdir dist_opengate_core dist_opengate dist_opengate_core_novis + mv dist/opengate_core_novis-* dist_opengate_core_novis/ || true mv dist/opengate_core-* dist_opengate_core/ mv dist/opengate-* dist_opengate/ + - name: Publish to PyPI opengate_core_novis + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_OPENGATE_CORE }} + packages_dir: dist_opengate_core_novis/ + skip_existing: true - name: Publish to PyPI opengate_core if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') uses: pypa/gh-action-pypi-publish@release/v1 @@ -344,7 +432,7 @@ jobs: test_wheel: runs-on: ${{ matrix.os }} - needs: [build_wheel] + needs: [build_opengate_wheel, build_opengate_core_wheel, build_opengate_core_novis_wheel] strategy: fail-fast: false matrix: diff --git a/docs/source/user_guide_1_intro.md b/docs/source/user_guide_1_intro.md index d05a9f176..2c02a48f3 100644 --- a/docs/source/user_guide_1_intro.md +++ b/docs/source/user_guide_1_intro.md @@ -79,6 +79,13 @@ Once installed, we recommend to check the installation by printing GATE informat The libraries (libG4processes and libG4geometry) are usually found in the Geant4 folder, something like ```~/build-geant4.11.0.2/BuildProducts/lib64```. +### Cluster / no-OpenGL version + +For some systems (espacialy clusters or older computers), the main opengate_core cannot be run due to the lack of libGL, or other librairies. For linux system, we offer a version without visualization pro +perties and using older librairies. You can install it with: + + pip install --force-reinstall "opengate[novis]" + ### Additional command lines tools There is some additional commands lines tools that can also be used, see the [addons section](user_guide_3_addons.md). diff --git a/pyproject.toml b/pyproject.toml index 701fd4394..f6575a626 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,9 @@ classifiers = [ ] requires-python = ">=3.9" +[project.optional-dependencies] +novis = ["opengate-core-novis"] + [project.scripts] opengate_tests = "opengate.bin.opengate_tests:go" opengate_info = "opengate.bin.opengate_info:go" diff --git a/setup.py b/setup.py index bc0a8372f..81c5e4af1 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( install_requires=[ "colored>1.5", - "opengate_core==" + version, + "opengate-core==" + version, "gatetools", "click", "python-box<7.0.0", From 5e18fa320066cdab868fff7cc46ec650c3cc7e8f Mon Sep 17 00:00:00 2001 From: David Sarrut Date: Thu, 17 Oct 2024 10:36:51 +0200 Subject: [PATCH 135/183] Update user_guide_2_0_simulation.md --- docs/source/user_guide_2_0_simulation.md | 55 +++++++++++------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/docs/source/user_guide_2_0_simulation.md b/docs/source/user_guide_2_0_simulation.md index 30198b66b..a68e77e93 100644 --- a/docs/source/user_guide_2_0_simulation.md +++ b/docs/source/user_guide_2_0_simulation.md @@ -26,15 +26,15 @@ Any simulation starts by defining the (unique) `Simulation` object. The generic sim = gate.Simulation() ui = sim.user_info print(ui) -ui.verbose_level = gate.LOG_DEBUG -ui.running_verbose_level = gate.LOG_EVENT -ui.g4_verbose = False -ui.g4_verbose_level = 1 -ui.visu = False -ui.visu_verbose = False -ui.random_engine = 'MersenneTwister' -ui.random_seed = 'auto' -ui.number_of_threads = 1 +sim.verbose_level = gate.LOG_DEBUG +sim.running_verbose_level = gate.LOG_EVENT +sim.g4_verbose = False +sim.g4_verbose_level = 1 +sim.visu = False +sim.visu_verbose = False +sim.random_engine = 'MersenneTwister' +sim.random_seed = 'auto' +sim.number_of_threads = 1 ``` A simulation must contains 4 main elements that define a complete simulation: @@ -54,7 +54,7 @@ output = sim.start() #### Random Number Generator -The RNG can be set with `ui.random_engine = "MersenneTwister"`. The default one is "MixMaxRng" and not "MersenneTwister" because it is recommended by Geant4 for MT. +The RNG can be set with `sim.random_engine = "MersenneTwister"`. The default one is "MixMaxRng" and not "MersenneTwister" because it is recommended by Geant4 for MT. The seed of the RNG can be set with `self.random_seed = 123456789`, with any number. If you run two times a simulation with the same seed, the results will be exactly the same. There are some exception to that behavior, for example when using PyTorch-based GAN. By default, it is set to "auto", which means that the seed is randomly chosen. @@ -75,40 +75,37 @@ sim.run_timing_intervals = [ The **verbosity**, i.e. the messages printed on the screen, are controlled via various parameters. -- `ui.verbose_level`: can be `DEBUG` or `INFO`. Will display more or less messages during initialization -- `ui.running_verbose_level`: can be `RUN` or `EVENT`. Will display message during simulation run -- `ui.g4_verbose`: (bool) enable or disable the Geant4 verbose system -- `ui.g4_verbose_level`: level of the Geant4 verbose system -- `ui.visu_verbose`: enable or disable Geant4 verbose during visualisation +- `sim.verbose_level`: can be `DEBUG` or `INFO`. Will display more or less messages during initialization +- `sim.running_verbose_level`: can be `RUN` or `EVENT`. Will display message during simulation run +- `sim.g4_verbose`: (bool) enable or disable the Geant4 verbose system +- `sim.g4_verbose_level`: level of the Geant4 verbose system +- `sim.visu_verbose`: enable or disable Geant4 verbose during visualisation Examples in code: ``` -from opengate.userhooks import check_production_cuts - sim = gate.Simulation() -sim.user_hook_after_init = check_production_cuts # cuts length (millimeter) of gamma, electron, proton, positron # geant4 verbose sim.g4_commands_after_init.append("/tracking/verbose 1") # track particles. 0: no info, 1,2,3: detailed level sim.g4_verbose_level_tracking = 1 # same as above -sim.g4_verbose = True # similar as above, default is False +sim.g4_verbose = True # Geant4 verbose, default is False sim.g4_verbose_level = 0 # when True, 0: general info; 1,2,3: detailed level, default is 1 # opengate verbose -sim.verbose_level = gate.logger.DEBUG # actors info -sim.verbose_level = gate.logger.INFO # default output -sim.running_verbose_level = gate.logger.EVENT # events info -sim.running_verbose_level = gate.logger.RUN # cpu threads info +sim.verbose_level = gate.logger.DEBUG # print some debug +sim.verbose_level = gate.logger.INFO # print misc info +sim.running_verbose_level = gate.logger.EVENT # print info for every generated events +sim.running_verbose_level = gate.logger.RUN # print info when each run starts ``` #### Visualisation -**Visualisation** is enabled with `ui.visu = True`. Then, you have the choice to choose between qt, vrml or gdml interface. +**Visualisation** is enabled with `sim.visu = True`. Then, you have the choice to choose between qt, vrml or gdml interface. ##### QT -It will start a Qt interface with `ui.visu_type = "qt"`. By default, the Geant4 visualisation commands are the ones provided in the file `opengate\mac\default_visu_commands_qt.mac`. It can be changed with `self.visu_commands = gate.read_mac_file_to_commands('my_visu_commands.mac')`. +It will start a Qt interface with `sim.visu_type = "qt"`. By default, the Geant4 visualisation commands are the ones provided in the file `opengate\mac\default_visu_commands_qt.mac`. It can be changed with `self.visu_commands = gate.read_mac_file_to_commands('my_visu_commands.mac')`. The visualisation with qt is still work in progress. First, it does not work on some linux systems (we don't know why yet). With MacOS Qt6 is working but sometimes you need to set the library path properly before running python with: ```export DYLD_LIBRARY_PATH=//lib/python3.9/site-packages/opengate_core/plugins:$DYLD_LIBRARY_PATH```. When a CT image is inserted in the simulation, every voxel should be drawn which is highly inefficient and cannot really be used. @@ -117,19 +114,19 @@ The visualisation with qt is still work in progress. First, it does not work on ![](figures/visu_vrml.png) -You can choose vrml visualization with `ui.visu_type = "vrml"`. Opengate uses `pyvista` for the GUI, so you need to install it before with `pip install pyvista`. Alternatively, if you want to use an external VRML viewer, you can save a VRML file with `ui.visu_type = "vrml_file_only"`. In such case, the GUI is not open, and you do not need pyvista. In both cases, you need to set `ui.visu_filename = "geant4VisuFile.wrl"` to save the VRML file. +You can choose vrml visualization with `sim.visu_type = "vrml"`. Opengate uses `pyvista` for the GUI, so you need to install it before with `pip install pyvista`. Alternatively, if you want to use an external VRML viewer, you can save a VRML file with `sim.visu_type = "vrml_file_only"`. In such case, the GUI is not open, and you do not need pyvista. In both cases, you need to set `sim.visu_filename = "geant4VisuFile.wrl"` to save the VRML file. -If you want to personalized the pyvista GUI, you can set `ui.visu_type = "vrml_file_only"` and execute you own code in your python script. You can find an example in [test004_simple_visu_vrml.py](https://github.com/OpenGATE/opengate/blob/master/opengate/tests/src/test004_simple_visu_vrml.py#L69-L90) +If you want to personalized the pyvista GUI, you can set `sim.visu_type = "vrml_file_only"` and execute you own code in your python script. You can find an example in [test004_simple_visu_vrml.py](https://github.com/OpenGATE/opengate/blob/master/opengate/tests/src/test004_simple_visu_vrml.py#L69-L90) ##### GDML ![](figures/visu_gdml.png) -With GDML visualization, you can only view the geometry, not the paths of the particles. It is enabled with `ui.visu_type = "gdml"`. GDML visualization needs to be enabled in Geant4 with `GEANT4_USE_GDML=ON` during the compilation but you need to have xerces-c available on your computer (install it with yum, brew, or apt-get, ...). Opengate uses `pyg4ometry` for the GUI, so you need to install it with `pip install pyg4ometry`. `pyg4ometry` uses opencascade librairy, so install opencascade with your package manager. If you want to use an external GDML viewer, you can save the visualization to a GDML file with `ui.visu_type = "gdml_file_only"`. In such case, the GUI is not open, and you do not need pyg4ometry. In both cases, you need to set `ui.visu_filename = "geant4VisuFile.gdml"` to save the GDML file. +With GDML visualization, you can only view the geometry, not the paths of the particles. It is enabled with `sim.visu_type = "gdml"`. GDML visualization needs to be enabled in Geant4 with `GEANT4_USE_GDML=ON` during the compilation but you need to have xerces-c available on your computer (install it with yum, brew, or apt-get, ...). Opengate uses `pyg4ometry` for the GUI, so you need to install it with `pip install pyg4ometry`. `pyg4ometry` uses opencascade librairy, so install opencascade with your package manager. If you want to use an external GDML viewer, you can save the visualization to a GDML file with `sim.visu_type = "gdml_file_only"`. In such case, the GUI is not open, and you do not need pyg4ometry. In both cases, you need to set `sim.visu_filename = "geant4VisuFile.gdml"` to save the GDML file. #### Multithreading -**Multithreading** is enabled with `ui.number_of_threads = 4` (larger than 1). When MT is enabled, there will one run for each thread, running in parallel. +**Multithreading** is enabled with `sim.number_of_threads = 4` (larger than 1). When MT is enabled, there will one run for each thread, running in parallel. Warning, the speedup is not optimal in all situations. First, it takes time to start a new thread, so it the simulation is short, MT does not bring any speedup. Second, if the simulation contains several runs (for moving volumes for example), all runs will be synchronized, i.e. the master thread will wait for all threads to terminate the run before starting another one. This synchronisation takes times and impact the speedup. From 72eb0a4aafb668463d232b074b250eeaabdb57c4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:37:30 +0000 Subject: [PATCH 136/183] [pre-commit.ci] Automatic python and c++ formatting --- docs/source/user_guide_2_0_simulation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/user_guide_2_0_simulation.md b/docs/source/user_guide_2_0_simulation.md index a68e77e93..3a360f456 100644 --- a/docs/source/user_guide_2_0_simulation.md +++ b/docs/source/user_guide_2_0_simulation.md @@ -94,7 +94,7 @@ sim.g4_verbose_level = 0 # when True, 0: general info; 1 # opengate verbose sim.verbose_level = gate.logger.DEBUG # print some debug -sim.verbose_level = gate.logger.INFO # print misc info +sim.verbose_level = gate.logger.INFO # print misc info sim.running_verbose_level = gate.logger.EVENT # print info for every generated events sim.running_verbose_level = gate.logger.RUN # print info when each run starts ``` From 40852b50467bcb0f54f6d805a857b5bf74a794bf Mon Sep 17 00:00:00 2001 From: David Sarrut Date: Thu, 17 Oct 2024 12:59:27 +0200 Subject: [PATCH 137/183] Revert "Create an opengate_core wheel whithout visu option" --- .../workflows/Dockerfile_opengate_core_novis | 36 ----- .github/workflows/createWheelLinux_novis.sh | 16 --- .github/workflows/main.yml | 130 +++--------------- docs/source/user_guide_1_intro.md | 7 - pyproject.toml | 3 - setup.py | 2 +- 6 files changed, 22 insertions(+), 172 deletions(-) delete mode 100644 .github/workflows/Dockerfile_opengate_core_novis delete mode 100755 .github/workflows/createWheelLinux_novis.sh diff --git a/.github/workflows/Dockerfile_opengate_core_novis b/.github/workflows/Dockerfile_opengate_core_novis deleted file mode 100644 index 06301f3fb..000000000 --- a/.github/workflows/Dockerfile_opengate_core_novis +++ /dev/null @@ -1,36 +0,0 @@ -#Docker for opengate_core -#systemctl start docker -#login: docker login -#build: docker build -t tbaudier/opengate_core -f Dockerfile_opengate_core_novis . -#push: docker push tbaudier/opengate_core -#run: docker run --rm -e "PYTHONFOLDER=${PYTHONFOLDERMANYLINUX}" -v $(Pipeline.Workspace)/software:/home tbaudier/opengate_core /home/opengate_core/azureCreateWheelLinux.sh -#interactive: docker run -ti --rm -v $(Pipeline.Workspace)/software:/home quay.io/pypa/manylinux2014_x86_64 /bin/bash - -FROM quay.io/pypa/manylinux2014_x86_64 -MAINTAINER Thomas Baudier -#Install packages -RUN yum install -y gcc wget git expat-devel fftw-devel freeglut-devel libXmu-devel xerces-c-devel \ - -#Create folder - && mkdir -p /software/cmake /software/geant4/src /software/geant4/bin /software/geant4/data /software/itk/src /software/itk/bin /software/wheelhouse \ - -#Install cmake - && cd /software/cmake \ - && wget https://github.com/Kitware/CMake/releases/download/v3.18.2/cmake-3.18.2-Linux-x86_64.tar.gz \ - && tar xzvf cmake-3.18.2-Linux-x86_64.tar.gz \ - && export PATH=/software/cmake/cmake-3.18.2-Linux-x86_64/bin/:${PATH} \ - -#Compile Geant4 - && cd /software/geant4 \ - && git clone --branch v11.2.1 https://github.com/Geant4/geant4.git --depth 1 src \ - && cd bin \ - && cmake -DCMAKE_CXX_FLAGS=-std=c++17 -DGEANT4_BUILD_MULTITHREADED=ON -DGEANT4_USE_GDML=OFF -DGEANT4_INSTALL_DATA=OFF -DGEANT4_INSTALL_DATADIR=/sofware/geant4/data -DGEANT4_USE_QT=OFF -DGEANT4_USE_OPENGL_X11=OFF ../src \ - && make -j10 \ - && source /software/geant4/bin/geant4make.sh \ - -#Compile ITK - && cd /software/itk \ - && git clone --branch v5.2.1 https://github.com/InsightSoftwareConsortium/ITK.git --depth 1 src \ - && cd bin \ - && cmake -DCMAKE_CXX_FLAGS=-std=c++17 -DBUILD_TESTING=OFF -DITK_USE_FFTWD=ON -DITK_USE_FFTWF=ON -DITK_USE_SYSTEM_FFTW:BOOL=ON ../src \ - && make -j10 diff --git a/.github/workflows/createWheelLinux_novis.sh b/.github/workflows/createWheelLinux_novis.sh deleted file mode 100755 index 3260067d2..000000000 --- a/.github/workflows/createWheelLinux_novis.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -e -x -echo ${PYTHONFOLDER} -cd /home/core/ -rm -rf build opengate_core.egg-info opengate_core/plugins opengate_core/opengate_core.cpython*.so -sed -i 's/name="opengate-core"/name="opengate-core-novis"/' setup.py -export PATH=/software/cmake/cmake-3.18.2-Linux-x86_64/bin/:${PATH} -source /software/geant4/bin/geant4make.sh -export CMAKE_PREFIX_PATH=/software/geant4/bin:/software/itk/bin/:${CMAKE_PREFIX_PATH} -/opt/python/${PYTHONFOLDER}/bin/pip install wget colored -/opt/python/${PYTHONFOLDER}/bin/python setup.py sdist bdist_wheel -auditwheel repair /home/core/dist/*.whl -w /software/wheelhouse/ --plat "manylinux2014_x86_64" -cp -r /software/wheelhouse /home/ -#/opt/python/${PYTHONFOLDER}/bin/pip install twine -#/opt/python/${PYTHONFOLDER}/bin/twine upload --repository-url https://test.pypi.org/legacy/ wheelhouse/*manylinux2014*.whl diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 51cc6cf4d..f53480a2d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,39 +12,7 @@ on: workflow_dispatch: jobs: - build_opengate_wheel: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - python-version: [3.9] - steps: - - name: Checkout github repo - uses: actions/checkout@v4 - - name: Checkout submodules - shell: bash -l {0} - run: | - export GIT_SSL_NO_VERIFY=1 - git submodule update --init --recursive - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - architecture: 'x64' - - name: Create opengate Wheel - run: | - rm -rf $GITHUB_WORKSPACE/opengate/tests/data - cp $GITHUB_WORKSPACE/.git/modules/gam-tests/data/HEAD $GITHUB_WORKSPACE/opengate/tests/ - pip install build - python -m build - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: dist-opengate - path: dist/ - - build_opengate_core_wheel: + build_wheel: runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -100,6 +68,16 @@ jobs: path: ~/software key: ${{ runner.os }}-${{ steps.get-os-version.outputs.release }}_geant4_${{ env.GEANT4_VERSION }}_itk_${{ env.ITK_VERSION }}_build2 restore-keys: ${{ runner.os }}-${{ steps.get-os-version.outputs.release }}_geant4_${{ env.GEANT4_VERSION }}_itk_${{ env.ITK_VERSION }}_build2 + - name: Create opengate Wheel + if: matrix.os == 'ubuntu-latest' + run: | + if [ ${{ matrix.python-version }} == "3.9" ]; then + rm -rf $GITHUB_WORKSPACE/opengate/tests/data + cp $GITHUB_WORKSPACE/.git/modules/gam-tests/data/HEAD $GITHUB_WORKSPACE/opengate/tests/ + pip install build + python -m build + mv dist dist_opengate + fi - name: Create opengate_core Wheel Linux if: matrix.os == 'ubuntu-latest' run: | @@ -118,6 +96,12 @@ jobs: rm -rf dist mv wheelhouse dist sudo chown -R runner:docker dist + if [ ${{ matrix.python-version }} == "3.9" ]; then + ls -lrt . + ls -lrt dist/ + ls -lrt dist_opengate/ + mv dist_opengate/* dist/ + fi - uses: conda-incubator/setup-miniconda@v3 if: (matrix.os == 'macos-13') || (matrix.os == 'windows-latest') with: @@ -275,75 +259,12 @@ jobs: - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: dist-${{ matrix.os }}-${{ matrix.python-version }}-opengate-core - path: dist/ - - build_opengate_core_novis_wheel: - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - python-version: [3.9, '3.10', '3.11', '3.12'] - - env: - GEANT4_VERSION: 'v11.2.1' - ITK_VERSION: 'v5.2.1' - - steps: - - name: Checkout github repo - uses: actions/checkout@v4 - - name: Checkout submodules - shell: bash -l {0} - run: | - export GIT_SSL_NO_VERIFY=1 - git submodule update --init --recursive - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - architecture: 'x64' - - name: Get OS version - id: get-os-version - shell: bash -l {0} - run: | - varOS=`cat /etc/os-release | grep "VERSION=" | grep -oP '(?<=\").*?(?=\")'` - varOS=($varOS) - echo "release=${varOS[0]}" >> $GITHUB_OUTPUT - - name: Cache modules - id: cache_opengate_core_dependencies - uses: actions/cache@v4 - with: - path: ~/software - key: ${{ runner.os }}-${{ steps.get-os-version.outputs.release }}_geant4_${{ env.GEANT4_VERSION }}_itk_${{ env.ITK_VERSION }}_build2 - restore-keys: ${{ runner.os }}-${{ steps.get-os-version.outputs.release }}_geant4_${{ env.GEANT4_VERSION }}_itk_${{ env.ITK_VERSION }}_build2 - - name: Create opengate_core_novis Wheel Linux - run: | - if [ ${{ matrix.python-version }} == "3.9" ]; then - export PYTHONFOLDER="cp39-cp39" - elif [ ${{ matrix.python-version }} == "3.10" ]; then - export PYTHONFOLDER="cp310-cp310" - elif [ ${{ matrix.python-version }} == "3.11" ]; then - export PYTHONFOLDER="cp311-cp311" - elif [ ${{ matrix.python-version }} == "3.12" ]; then - export PYTHONFOLDER="cp312-cp312" - fi - mkdir -p $HOME/software - docker run --rm -e "PYTHONFOLDER=${PYTHONFOLDER}" -v $GITHUB_WORKSPACE:/home tbaudier/opengate_core:${{ env.GEANT4_VERSION }}_novis /home/.github/workflows/createWheelLinux_novis.sh - ls wheelhouse - rm -rf dist - mv wheelhouse dist - sudo chown -R runner:docker dist - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: dist-${{ matrix.os }}-${{ matrix.python-version }}-opengate-core-novis + name: dist-${{ matrix.os }}-${{ matrix.python-version }} path: dist/ publish_wheel: runs-on: ubuntu-latest - needs: [build_opengate_wheel, build_opengate_core_wheel, build_opengate_core_novis_wheel] + needs: [build_wheel] steps: - name: Checkout github repo uses: actions/checkout@v4 @@ -354,18 +275,9 @@ jobs: path: dist/ - name: Separate the wheels run: | - mkdir dist_opengate_core dist_opengate dist_opengate_core_novis - mv dist/opengate_core_novis-* dist_opengate_core_novis/ || true + mkdir dist_opengate_core dist_opengate mv dist/opengate_core-* dist_opengate_core/ mv dist/opengate-* dist_opengate/ - - name: Publish to PyPI opengate_core_novis - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_OPENGATE_CORE }} - packages_dir: dist_opengate_core_novis/ - skip_existing: true - name: Publish to PyPI opengate_core if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') uses: pypa/gh-action-pypi-publish@release/v1 @@ -432,7 +344,7 @@ jobs: test_wheel: runs-on: ${{ matrix.os }} - needs: [build_opengate_wheel, build_opengate_core_wheel, build_opengate_core_novis_wheel] + needs: [build_wheel] strategy: fail-fast: false matrix: diff --git a/docs/source/user_guide_1_intro.md b/docs/source/user_guide_1_intro.md index 2c02a48f3..d05a9f176 100644 --- a/docs/source/user_guide_1_intro.md +++ b/docs/source/user_guide_1_intro.md @@ -79,13 +79,6 @@ Once installed, we recommend to check the installation by printing GATE informat The libraries (libG4processes and libG4geometry) are usually found in the Geant4 folder, something like ```~/build-geant4.11.0.2/BuildProducts/lib64```. -### Cluster / no-OpenGL version - -For some systems (espacialy clusters or older computers), the main opengate_core cannot be run due to the lack of libGL, or other librairies. For linux system, we offer a version without visualization pro -perties and using older librairies. You can install it with: - - pip install --force-reinstall "opengate[novis]" - ### Additional command lines tools There is some additional commands lines tools that can also be used, see the [addons section](user_guide_3_addons.md). diff --git a/pyproject.toml b/pyproject.toml index f6575a626..701fd4394 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,6 @@ classifiers = [ ] requires-python = ">=3.9" -[project.optional-dependencies] -novis = ["opengate-core-novis"] - [project.scripts] opengate_tests = "opengate.bin.opengate_tests:go" opengate_info = "opengate.bin.opengate_info:go" diff --git a/setup.py b/setup.py index 0d9461639..e303bf599 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( install_requires=[ "colored>1.5", - "opengate-core==" + version, + "opengate_core==" + version, "gatetools", "click", "python-box<7.0.0", From 225c24d73ffcc9c5bdd2e5156d810d7107193cf4 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:43:52 +0200 Subject: [PATCH 138/183] Improve ActorOutputUsingDataItemContainer.get_active() --- opengate/actors/actoroutput.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index 14ba60947..8bf55c5ac 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -504,6 +504,8 @@ def set_active(self, value, item=0): self.data_item_config[i]["active"] = bool(value) def get_active(self, item=0): + if item == 'any': + item = 'all' items = self._collect_item_identifiers(item) return any([self.data_item_config[k]["active"] is True for k in items]) From 8cf7e344d43c2599b7db6b2df96d0e69da2ba805 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:44:13 +0200 Subject: [PATCH 139/183] Add FIXME --- opengate/actors/actoroutput.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index 8bf55c5ac..68805af60 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -537,6 +537,7 @@ def get_item_suffix(self, item=0, **kwargs): ) else: try: + # FIXME: the .get() method implicitly defines a default value, but it should not. Is this a workaround? return self.data_item_config[item].get("suffix", str(item)) except KeyError: self._fatal_unknown_item(item) From 3b318ed36428645c28596bf21a6c80315d6ce498 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:47:10 +0200 Subject: [PATCH 140/183] Rename method copy_user_info to configure_like --- opengate/base.py | 2 +- opengate/contrib/phantoms/nemaiec.py | 4 ++-- opengate/geometry/volumes.py | 2 +- .../tests/src/test033_rotation_spect_aa_helpers.py | 2 +- opengate/tests/src/test059_tpsource_gantry_rot.py | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/opengate/base.py b/opengate/base.py index 4a8356fb5..1535fec01 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -586,7 +586,7 @@ def release_g4_references(self): """Dummy implementation for inherited classes which do not implement this method.""" pass - def copy_user_info(self, other_obj): + def configure_like(self, other_obj): for k in self.user_info.keys(): if k not in ["name", "_name"]: try: diff --git a/opengate/contrib/phantoms/nemaiec.py b/opengate/contrib/phantoms/nemaiec.py index 721a20ae5..bf19f9a16 100644 --- a/opengate/contrib/phantoms/nemaiec.py +++ b/opengate/contrib/phantoms/nemaiec.py @@ -99,7 +99,7 @@ def add_iec_body(simulation, name, thickness=0.0, thickness_z=0.0): bottom_right_shell = opengate.geometry.volumes.TubsVolume( name=f"{name}_bottom_right_shell" ) - bottom_right_shell.copy_user_info(bottom_left_shell) + bottom_right_shell.configure_like(bottom_left_shell) bottom_right_shell.sphi = 180 * deg bottom_right_shell.dphi = 90 * deg @@ -246,7 +246,7 @@ def add_iec_one_sphere( # capillary outer shell caps = sim.add_volume("Tubs", f"{name}_capillary_shell_{d}") - caps.copy_user_info(cap) + caps.configure_like(cap) caps.material = iec_plastic caps.rmax = cap_thick caps.rmin = cap.rmax diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index ee356b0b8..752a86c16 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -192,7 +192,7 @@ def __init__(self, *args, template=None, **kwargs): # except for the name of course if template is not None: # FIXME: consider using from_dictionary() - self.copy_user_info(template) + self.configure_like(template) # put back user infos which were explicitly passed as keyword argument for k in self.user_info.keys(): if k != "name" and k in kwargs: diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index a291da278..582aa340a 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -86,7 +86,7 @@ def create_test(sim, nb_thread=1): # source #2 source2 = sim.add_source("GenericSource", "source2") - # FIXME when source will be refactored, will possible to use copy_user_info + # FIXME when source will be refactored, will be possible to use configure_like source2.particle = "gamma" source2.energy.type = "mono" source2.energy.mono = 140.5 * keV diff --git a/opengate/tests/src/test059_tpsource_gantry_rot.py b/opengate/tests/src/test059_tpsource_gantry_rot.py index aeb83e62d..feba93172 100755 --- a/opengate/tests/src/test059_tpsource_gantry_rot.py +++ b/opengate/tests/src/test059_tpsource_gantry_rot.py @@ -92,16 +92,16 @@ ## ---- HBL Nozzle --- # FIXME : will change after volumes are refactored box_rot = sim.add_volume("Box", "box_rot") - box_rot.copy_user_info(box) + box_rot.configure_like(box) box_rot.rotation = Rotation.from_euler("y", -90, degrees=True).as_matrix() box_rot.translation = [1148.0, 0.0, 1000.0] nozzle_rot = sim.add_volume("Box", "nozzle_rot") - nozzle_rot.copy_user_info(nozzle) + nozzle_rot.configure_like(nozzle) nozzle_rot.mother = box_rot.name rashi_rot = sim.add_volume("Box", "rashi_rot") - rashi_rot.copy_user_info(rashi) + rashi_rot.configure_like(rashi) rashi_rot.mother = box_rot.name # ----------------------------------- @@ -115,7 +115,7 @@ # target 2 HBL phantom_rot = sim.add_volume("Box", "phantom_rot") - phantom_rot.copy_user_info(phantom) + phantom_rot.configure_like(phantom) phantom_rot.rotation = Rotation.from_euler("z", 90, degrees=True).as_matrix() phantom_rot.translation = [0.0, 0.0, 1000.0] @@ -129,7 +129,7 @@ dose.user_output.dose.active = True dose_rot = sim.add_actor("DoseActor", "doseInXYZ_rot") - dose_rot.copy_user_info(dose) + dose_rot.configure_like(dose) dose_rot.attached_to = phantom_rot.name dose_rot.output_filename = "testTPSganry_rot.mhd" From caeec04a4b62ea65f45fa536e0363183608f3691 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:47:33 +0200 Subject: [PATCH 141/183] Implement ActorBase.configure_like() --- opengate/actors/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opengate/actors/base.py b/opengate/actors/base.py index 1fadd086f..12e6a1217 100644 --- a/opengate/actors/base.py +++ b/opengate/actors/base.py @@ -157,6 +157,12 @@ def __setstate__(self, state): # # output_filename is a property # self.known_attributes.add("output_filename") + def configure_like(self, other_obj): + super().configure_like(other_obj) + # also pick up the configuration of the user output + for k, v in self.user_output.items(): + v.configure_like(other_obj.user_output[k]) + def to_dictionary(self): d = super().to_dictionary() d["user_output"] = dict( From b74d7729bb4f67b0d4eaf997d1014d51e7af6815 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:48:21 +0200 Subject: [PATCH 142/183] In SingleItkImageWithVariance: set variance to 0 where not possible to calculate it (denominator 0) --- opengate/actors/dataitems.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/actors/dataitems.py b/opengate/actors/dataitems.py index 5cc4a9600..f5eed4629 100644 --- a/opengate/actors/dataitems.py +++ b/opengate/actors/dataitems.py @@ -692,7 +692,7 @@ def get_variance_or_uncertainty(self, which_quantity): output_arr = np.divide( output_arr, value_array / number_of_samples, - out=np.ones_like(output_arr), + out=np.zeros_like(output_arr), where=value_array != 0, ) output_image = itk.image_view_from_array(output_arr) From 4d43b3be104e592b9035c166883ad11f2e6d8581 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:52:41 +0200 Subject: [PATCH 143/183] Update DoseActor (python) to use an actor output with dose and dose_squared combined. --- opengate/actors/doseactors.py | 258 ++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 122 deletions(-) diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index 85c9d6f80..0bd60fc06 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -442,6 +442,7 @@ class DoseActor(VoxelDepositActor, g4.GateDoseActor): def __init__(self, *args, **kwargs): VoxelDepositActor.__init__(self, *args, **kwargs) + # **** EDEP **** # This creates a user output with two components: 0=edep, 1=edep_squared # additionally, it also provides variance, std, and uncertainty via dynamic properties self._add_user_output( @@ -471,29 +472,54 @@ def __init__(self, *args, **kwargs): item="uncertainty", ) + # **** DOSE **** self._add_user_output( - ActorOutputSingleImage, - "dose", + ActorOutputSingleImageWithVariance, + "dose_with_uncertainty", + automatically_generate_interface=False, ) - - self._add_user_output( - ActorOutputSingleImage, + # create an interface to item 0 of user output "dose_with_uncertainty" + # and make it available via a property 'dose' in this actor + self._add_interface_to_user_output( + UserInterfaceToActorOutputImage, "dose_with_uncertainty", "dose", item=0 + ) + # create an interface to item 1 of user output "dose_with_uncertainty" + # and make it available via a property 'dose_squared' in this actor + self._add_interface_to_user_output( + UserInterfaceToActorOutputImage, + "dose_with_uncertainty", + "dose_squared", + item=1, + ) + self._add_interface_to_user_output( + UserInterfaceToActorOutputImage, + "dose_with_uncertainty", "dose_uncertainty", + item="uncertainty", ) + # set the defaults for the user output of this actor self._add_user_output(ActorOutputSingleMeanImage, "density") + self._add_user_output(ActorOutputSingleImage, "counts") + self.user_output.dose_with_uncertainty.set_active(False, item='all') + self.user_output.density.set_active(False) + self.user_output.counts.set_active(False) - self.user_output.dose.set_active(False) - self.user_output.dose_uncertainty.set_active(False) - + # item suffix is used when the filename is auto-generated or + # when the user sets one filename per actor self.user_output.edep_with_uncertainty.set_item_suffix("edep", item=0) self.user_output.edep_with_uncertainty.set_item_suffix("edep_squared", item=1) self.user_output.edep_with_uncertainty.set_item_suffix( "edep_uncertainty", item="uncertainty" ) - self.user_output.dose.set_item_suffix("dose") - self.user_output.dose_uncertainty.set_item_suffix("dose_uncertainty") + self.user_output.dose_with_uncertainty.set_item_suffix("dose", item=0) + self.user_output.dose_with_uncertainty.set_item_suffix("dose_squared", item=1) + self.user_output.dose_with_uncertainty.set_item_suffix( + "dose_uncertainty", item="uncertainty" + ) + # The following 2 are single item output and item=0 is default self.user_output.density.set_item_suffix("density") + self.user_output.counts.set_item_suffix("counts") self.__initcpp__() @@ -510,56 +536,24 @@ def __initcpp__(self): } ) - def compute_dose_from_edep_img(self, input_image, density_image=None): - """ - * create mass image: - - from ct HU units, if dose actor attached to ImageVolume. - - from material density, if standard volume - * compute dose as edep_image / mass_image - """ - vol = self.attached_to_volume - voxel_volume = self.spacing[0] * self.spacing[1] * self.spacing[2] - Gy = g4_units.Gy - gcm3 = g4_units.g_cm3 - - if vol.volume_type == "ImageVolume": - if self.score_in == "water": - # for dose to water, divide by density of water and not density of material - scaled_image = scale_itk_image(input_image, 1 / (1.0 * gcm3)) - else: - density_image = vol.create_density_image() - if images_have_same_domain(input_image, density_image) is False: - density_image = resample_itk_image_like( - density_image, input_image, 0, linear=True - ) - scaled_image = divide_itk_images( - img1_numerator=input_image, - img2_denominator=density_image, - filterVal=0, - replaceFilteredVal=0, - ) - # divide by voxel volume and convert unit - scaled_image = scale_itk_image(scaled_image, 1 / (Gy * voxel_volume)) + def create_density_image_from_image_volume(self, deposit_image): + if self.attached_to_volume.volume_type != "ImageVolume": + fatal(f"Cannot calculate the density map from the ImageVolume " + f"because this actor is attached to a {self.attached_to_volume.volume_type}. ") - else: - if self.score_in == "water": - # for dose to water, divide by density of water and not density of material - scaled_image = scale_itk_image(input_image, 1 / (1.0 * gcm3)) - else: - # the dose actor is attached to a volume, we need the density image - # to be computed from the cpp side - if density_image is None: - fatal(f"A density image computed via the G4 simulation is needed.") - scaled_image = divide_itk_images( - img1_numerator=input_image, - img2_denominator=density_image, - filterVal=0, - replaceFilteredVal=0, - ) - # divide by voxel volume and convert unit - scaled_image = scale_itk_image(scaled_image, 1 / (Gy * voxel_volume)) + density_image = self.attached_to_volume.create_density_image() + if images_have_same_domain(deposit_image, density_image) is False: + density_image = resample_itk_image_like( + density_image, deposit_image, 0, linear=True + ) + return density_image - return scaled_image + @property + def _density_via_mc(self): + return (self.calculate_density_from == 'simulation' or + (self.calculate_density_from == 'auto' + and self.attached_to_volume.volume_type != "ImageVolume") + ) def initialize(self, *args): """ @@ -572,35 +566,52 @@ def initialize(self, *args): VoxelDepositActor.initialize(self) - # dose uncertainty relies on edep_uncertainty. Set active flag accordingly - if self.user_output.dose_uncertainty.get_active() is True: - self.user_output.dose.set_active(True) - self.user_output.edep_with_uncertainty.set_active(True, item="uncertainty") # Make sure the squared component (item 1) is active if any of the quantities relying on it are active if ( - self.user_output.edep_with_uncertainty.get_active( - item=("uncertainty", "std", "variance") - ) - is True + self.user_output.edep_with_uncertainty.get_active( + item=("uncertainty", "std", "variance") + ) + is True ): - self.user_output.edep_with_uncertainty.set_active( - True, item=1 - ) # activate squared component + # activate the squared component, but avoid writing it to disk + # because the user has not activated it and thus most likely does not want it + if not self.user_output.edep_with_uncertainty.get_active(item=1): + self.user_output.edep_with_uncertainty.set_write_to_disk(False, item=1) + self.user_output.edep_with_uncertainty.set_active( + True, item=1 + ) # activate squared component - # activate density if we need the dose and the DoseActor is not attached to a volume + # Make sure the squared component (item 1) is active if any of the quantities relying on it are active if ( - self.user_output.dose.get_active() is True - and self.attached_to_volume.volume_type != "ImageVolume" + self.user_output.dose_with_uncertainty.get_active( + item=("uncertainty", "std", "variance") + ) + is True ): - if not self.user_output.density.get_active(): - self.user_output.density.set_active(True) - self.user_output.density.set_write_to_disk(False) + # activate the squared component, but avoid writing it to disk + # because the user has not activated it and thus most likely does not want it + if not self.user_output.dose_with_uncertainty.get_active(item=1): + self.user_output.dose_with_uncertainty.set_write_to_disk(False, item=1) + self.user_output.dose_with_uncertainty.set_active( + True, item=1 + ) # activate squared component + + if self.user_output.density.get_active() is True: + # scoring density via MC implies scoring counts + if self._density_via_mc: + if not self.user_output.counts.get_active(): + self.user_output.counts.set_active(True) + self.user_output.counts.set_write_to_disk(False) self.InitializeUserInput(self.user_info) # C++ side - self.SetSquareFlag(self.user_output.edep_with_uncertainty.get_active(item=1)) - self.SetDensityFlag( - self.user_output.density.get_active() - ) # item=0 is the default + # Set the flags on C++ side so the C++ knows which quantities need to be scored + self.SetEdepSquaredFlag(self.user_output.edep_with_uncertainty.get_active(item=1)) + self.SetDoseFlag(self.user_output.dose_with_uncertainty.get_active(item=0)) + self.SetDoseSquaredFlag(self.user_output.dose_with_uncertainty.get_active(item=1)) + # item=0 is the default + self.SetCountsFlag(self.user_output.counts.get_active()) + self.SetDensityFlag(self.user_output.density.get_active() and self._density_via_mc) + # C++ side has a boolean toWaterFlag and self.score_in == "water" yields True/False self.SetToWaterFlag(self.score_in == "water") # Set the physical volume name on the C++ side @@ -613,15 +624,22 @@ def BeginOfRunActionMasterThread(self, run_index): "edep_with_uncertainty", run_index, self.cpp_edep_image, - self.cpp_square_image, + self.cpp_edep_squared_image, ) - if self.user_output.density.get_active(): + if self.user_output.dose_with_uncertainty.get_active(item='any'): + self.prepare_output_for_run("dose_with_uncertainty", run_index) + self.push_to_cpp_image("dose_with_uncertainty", run_index, self.cpp_dose_image, self.cpp_dose_squared_image) + + # density might be active, but the user might want to get it from the ImageVolume + # therefore, we also check for _density_via_mc + if self.user_output.density.get_active() and self._density_via_mc: self.prepare_output_for_run("density", run_index) self.push_to_cpp_image("density", run_index, self.cpp_density_image) - if self.user_output.dose_uncertainty.get_active(): - self.prepare_output_for_run("dose_uncertainty", run_index) + if self.user_output.counts.get_active(): + self.prepare_output_for_run("counts", run_index) + self.push_to_cpp_image("counts", run_index, self.cpp_counts_image) g4.GateDoseActor.BeginOfRunActionMasterThread(self, run_index) @@ -630,56 +648,52 @@ def EndOfRunActionMasterThread(self, run_index): "edep_with_uncertainty", run_index, self.cpp_edep_image, - self.cpp_square_image, + self.cpp_edep_squared_image, ) self._update_output_coordinate_system("edep_with_uncertainty", run_index) self.user_output.edep_with_uncertainty.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) - # density image - if self.user_output.density.get_active(): - self.fetch_from_cpp_image("density", run_index, self.cpp_density_image) - self._update_output_coordinate_system("density", run_index) - self.user_output.density.store_meta_data( - run_index, number_of_samples=self.NbOfEvent - ) - - # dose - if self.user_output.dose.get_active(): - edep_image = self.user_output.edep_with_uncertainty.get_data( - run_index, item=0 - ) - density_image = None - if self.user_output.density.get_active(): - density_image = self.user_output.density.get_data(run_index) - dose_image = self.compute_dose_from_edep_img(edep_image, density_image) - dose_image.CopyInformation(edep_image) - self.store_output_data( - "dose", + if self.user_output.dose_with_uncertainty.get_active(item='any'): + self.fetch_from_cpp_image( + "dose_with_uncertainty", run_index, - dose_image, + self.cpp_dose_image, + self.cpp_dose_squared_image, ) - self.user_output.dose.store_meta_data( + self._update_output_coordinate_system("dose_with_uncertainty", run_index) + self.user_output.dose_with_uncertainty.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) - - if self.user_output.dose_uncertainty.get_active() is True: - # scale by density - edep_uncertainty_image = self.user_output.edep_with_uncertainty.get_data( - run_index, item="uncertainty" - ) - density_image = None - if self.user_output.density.get_active(): - density_image = self.user_output.density.get_data(run_index) - dose_uncertainty_image = self.compute_dose_from_edep_img( - edep_uncertainty_image, density_image - ) - dose_uncertainty_image.CopyInformation(edep_uncertainty_image) - self.user_output.dose_uncertainty.store_data( - run_index, dose_uncertainty_image + # divide by voxel volume and scale to unit Gy + if self.user_output.dose_with_uncertainty.get_active(item=0): + self.user_output.dose_with_uncertainty.data_per_run[run_index].data[0] /= \ + (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) + if self.user_output.dose_with_uncertainty.get_active(item=1): + # in the squared component 1, the denominator needs to be squared + self.user_output.dose_with_uncertainty.data_per_run[run_index].data[1] /= ( + (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) ** 2) + + if self.user_output.counts.get_active(): + self.fetch_from_cpp_image("counts", run_index, self.cpp_counts_image) + self._update_output_coordinate_system("counts", run_index) + self.user_output.counts.store_meta_data( + run_index, number_of_samples=self.NbOfEvent ) - self.user_output.dose_uncertainty.store_meta_data( + + # density image + if self.user_output.density.get_active(): + if self._density_via_mc: + self.fetch_from_cpp_image("density", run_index, self.cpp_density_image) + self._update_output_coordinate_system("density", run_index) + self.user_output.density.data_per_run[run_index] /= self.user_output.counts.data_per_run[run_index] + else: + edep_image = self.user_output.edep_with_uncertainty.get_data( + run_index, item=0 + ) + self.user_output.density.store_data(run_index, self.create_density_image_from_image_volume(edep_image)) + self.user_output.density.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) From 0591af04ccc5c7e85d1bea4efc3959503a080001 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:53:02 +0200 Subject: [PATCH 144/183] Update GateDoseActor (C++) to use an actor output with dose and dose_squared combined. --- .../opengate_lib/GateDoseActor.cpp | 217 +++++++++--------- .../opengate_lib/GateDoseActor.h | 66 ++++-- .../opengate_lib/pyGateDoseActor.cpp | 15 +- 3 files changed, 165 insertions(+), 133 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index bce36098d..0b1258cad 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -32,16 +32,7 @@ G4Mutex ComputeUncertaintyMutex = G4MUTEX_INITIALIZER; G4Mutex SetNbEventMutex = G4MUTEX_INITIALIZER; GateDoseActor::GateDoseActor(py::dict &user_info) - : GateVActor(user_info, true) { - // Action for this actor: during stepping - // fActions.insert("SteppingAction"); - // fActions.insert("BeginOfRunAction"); - // fActions.insert("EndOfRunAction"); - // fActions.insert("BeginOfEventAction"); - // fActions.insert("EndOfSimulationWorkerAction"); - // fActions.insert("EndSimulationAction"); - // fActions.insert("EndOfEventAction"); -} + : GateVActor(user_info, true) {} void GateDoseActor::InitializeUserInput(py::dict &user_info) { // IMPORTANT: call the base class method @@ -83,12 +74,21 @@ void GateDoseActor::InitializeCpp() { // (the size and allocation will be performed on the py side) cpp_edep_image = Image3DType::New(); - if (fSquareFlag) { - cpp_square_image = Image3DType::New(); + if (fDoseFlag) { + cpp_dose_image = Image3DType::New(); + } + if (fEdepSquaredFlag) { + cpp_edep_squared_image = Image3DType::New(); + } + if (fDoseSquaredFlag) { + cpp_dose_squared_image = Image3DType::New(); } if (fDensityFlag) { cpp_density_image = Image3DType::New(); } + if (fCountsFlag) { + cpp_counts_image = Image3DType::New(); + } } void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { @@ -105,40 +105,50 @@ void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { Image3DType::RegionType region = cpp_edep_image->GetLargestPossibleRegion(); size_edep = region.GetSize(); - if (fSquareFlag) { - AttachImageToVolume(cpp_square_image, fPhysicalVolumeName, + if (fEdepSquaredFlag) { + AttachImageToVolume(cpp_edep_squared_image, fPhysicalVolumeName, + fTranslation); + } + if (fDoseFlag) { + AttachImageToVolume(cpp_dose_image, fPhysicalVolumeName, + fTranslation); + } + if (fDoseSquaredFlag) { + AttachImageToVolume(cpp_dose_squared_image, fPhysicalVolumeName, fTranslation); } if (fDensityFlag) { AttachImageToVolume(cpp_density_image, fPhysicalVolumeName, fTranslation); } + if (fCountsFlag) { + AttachImageToVolume(cpp_counts_image, fPhysicalVolumeName, + fTranslation); + } +} + +void GateDoseActor::PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels) { + data.squared_worker_flatimg.resize(numberOfVoxels); + std::fill(data.squared_worker_flatimg.begin(), + data.squared_worker_flatimg.end(), 0.0); + data.lastid_worker_flatimg.resize(numberOfVoxels); + std::fill(data.lastid_worker_flatimg.begin(), data.lastid_worker_flatimg.end(), + 0); } void GateDoseActor::BeginOfRunAction(const G4Run *run) { - if (fSquareFlag) { - int N_voxels = size_edep[0] * size_edep[1] * size_edep[2]; - auto &l = fThreadLocalData.Get(); - l.edepSquared_worker_flatimg.resize(N_voxels); - std::fill(l.edepSquared_worker_flatimg.begin(), - l.edepSquared_worker_flatimg.end(), 0.0); - l.lastid_worker_flatimg.resize(N_voxels); - std::fill(l.lastid_worker_flatimg.begin(), l.lastid_worker_flatimg.end(), - 0); + int N_voxels = size_edep[0] * size_edep[1] * size_edep[2]; + if (fEdepSquaredFlag) { + PrepareLocalDataForRun(fThreadLocalDataEdep.Get(), N_voxels); + } + if (fDoseSquaredFlag) { + PrepareLocalDataForRun(fThreadLocalDataDose.Get(), N_voxels); } - - // if (fcpImageForThreadsFlag && (run->GetRunID() < 1)) { - // l.edep_worker_flatimg.resize(N_voxels); - // std::fill(l.edep_worker_flatimg.begin(), l.edep_worker_flatimg.end(), - // 0.0); - // } } void GateDoseActor::BeginOfEventAction(const G4Event *event) { G4AutoLock mutex(&SetNbEventMutex); NbOfEvent++; - // threadLocalT &data = fThreadLocalData.Get(); - // data.NbOfEvent_worker++; } void GateDoseActor::SteppingAction(G4Step *step) { @@ -180,6 +190,8 @@ void GateDoseActor::SteppingAction(G4Step *step) { // get edep in MeV (take weight into account) auto w = step->GetTrack()->GetWeight(); auto edep = step->GetTotalEnergyDeposit() / CLHEP::MeV * w; + double density; + double dose; if (fToWaterFlag) { auto *current_material = step->GetPreStepPoint()->GetMaterial(); @@ -195,7 +207,7 @@ void GateDoseActor::SteppingAction(G4Step *step) { auto energy = (energy1 + energy2) / 2; if (p == G4Gamma::Gamma()) p = G4Electron::Electron(); - auto &emc = fThreadLocalData.Get().emcalc; + auto &emc = fThreadLocalDataEdep.Get().emcalc; dedx_currstep = emc.ComputeTotalDEDX(energy, p, current_material, dedx_cut); dedx_water = emc.ComputeTotalDEDX(energy, p, water, dedx_cut); @@ -208,59 +220,38 @@ void GateDoseActor::SteppingAction(G4Step *step) { ImageAddValue(cpp_edep_image, index, edep); - if (fDensityFlag) { - // FIXME : not very efficient: should be computed once for all + if (fDensityFlag || fDoseFlag || fDoseSquaredFlag) { auto *current_material = step->GetPreStepPoint()->GetMaterial(); auto density = current_material->GetDensity(); - cpp_density_image->SetPixel(index, density); + if (fDensityFlag) { + ImageAddValue(cpp_density_image, index, density); + } + if (fDoseFlag || fDoseSquaredFlag) { + dose = edep / density; + if (fDoseFlag) { + ImageAddValue(cpp_dose_image, index, dose); + } + } } - if (fSquareFlag) { - auto &locald = fThreadLocalData.Get(); - G4AutoLock mutex(&SetPixelMutex); - - int index_flat = sub2ind(index); + if (fCountsFlag) { + ImageAddValue(cpp_counts_image, index, 1); + } + if (fEdepSquaredFlag || fDoseSquaredFlag) { auto event_id = G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID(); - auto previous_id = locald.lastid_worker_flatimg[index_flat]; - locald.lastid_worker_flatimg[index_flat] = event_id; - if (event_id == previous_id) { - // Same event: sum the deposited energy associated with this event ID - // and square once a new event ID is found (case below) - locald.edepSquared_worker_flatimg[index_flat] += edep; - } else { - // Different event : square deposited energy from the last event ID - // and start accumulating deposited energy for this new event ID - auto e = locald.edepSquared_worker_flatimg[index_flat]; - ImageAddValue(cpp_square_image, index, e * e); - // new temp value - locald.edepSquared_worker_flatimg[index_flat] = edep; + if (fEdepSquaredFlag) { +// G4AutoLock mutex(&SetPixelMutex); + ScoreSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image, edep, event_id, index); + } + if (fDoseSquaredFlag) { +// G4AutoLock mutex(&SetPixelMutex); + ScoreSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image, dose, event_id, index); } } } // else: outside of the image } -// void GateDoseActor::EndSimulationAction() { -// double planned_NbOfEvent_per_worker = double(NbOfEvent / (NbOfThreads)); -// if (fSTEofMeanFlag) { -// itk::ImageRegionIterator edep_iterator3D( -// cpp_edep_image, cpp_edep_image->GetLargestPossibleRegion()); -// for (edep_iterator3D.GoToBegin(); !edep_iterator3D.IsAtEnd(); -// ++edep_iterator3D) { -// -// Image3DType::IndexType index_f = edep_iterator3D.GetIndex(); -// Image3DType::PixelType pixelValue3D_perEvent = -// cpp_square_image->GetPixel(index_f); -// -// Image3DType::PixelType pixelValue_cpp = -// pixelValue3D_perEvent * planned_NbOfEvent_per_worker; -// cpp_square_image->SetPixel(index_f, pixelValue_cpp); -// // std::cout << "PixelValue end: " << pixelValue_cpp << std::endl; -// } -// } -//} - -// void GateDoseActor::EndOfEventAction(const G4Event *event) {} double GateDoseActor::ComputeMeanUncertainty() { G4AutoLock mutex(&ComputeUncertaintyMutex); @@ -269,11 +260,6 @@ double GateDoseActor::ComputeMeanUncertainty() { double mean_unc = 0.0; int n_voxel_unc = 0; double n = 2.0; - // if (fcpImageForThreadsFlag) { - // n = NbOfThreads; - // } else { - // n = NbOfEvent; - // } n = NbOfEvent; if (n < 2.0) { @@ -289,7 +275,7 @@ double GateDoseActor::ComputeMeanUncertainty() { if (val > max_edep * threshEdepPerc) { val /= n; n_voxel_unc++; - double val_squared_mean = cpp_square_image->GetPixel(index_f) / n; + double val_squared_mean = cpp_edep_squared_image->GetPixel(index_f) / n; double unc_i = (1.0 / (n - 1.0)) * (val_squared_mean - pow(val, 2)); if (unc_i < 0) { @@ -336,38 +322,48 @@ void GateDoseActor::ind2sub(int index_flat, Image3DType::IndexType &index3D) { void GateDoseActor::EndOfRunAction(const G4Run *run) { - if (fSquareFlag) { - // We need to flush the energy deposit from the last event ID of this run - // to cpp_square_image because it has only been accumulated in the - // SteppingAction It would be flushed to cpp_square_image in the - // SteppingAction of the next event ID, but we are at the end of the run. - threadLocalT &data = fThreadLocalData.Get(); - - G4AutoLock mutex(&SetWorkerEndRunMutex); - - itk::ImageRegionIterator iterator3D( - cpp_square_image, cpp_square_image->GetLargestPossibleRegion()); - for (iterator3D.GoToBegin(); !iterator3D.IsAtEnd(); ++iterator3D) { - Image3DType::IndexType index_f = iterator3D.GetIndex(); - Image3DType::PixelType pixelValue3D = - data.edepSquared_worker_flatimg[sub2ind(index_f)]; - ImageAddValue(cpp_square_image, index_f, - pixelValue3D * pixelValue3D); - } + if (fEdepSquaredFlag) { + GateDoseActor::FlushSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image); + } + if (fDoseSquaredFlag) { + GateDoseActor::FlushSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image); + } +} + +void GateDoseActor::ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, Image3DType::IndexType index) { + G4AutoLock mutex(&SetPixelMutex); + int index_flat = sub2ind(index); + auto previous_id = data.lastid_worker_flatimg[index_flat]; + data.lastid_worker_flatimg[index_flat] = event_id; + if (event_id == previous_id) { + // Same event: sum the deposited value associated with this event ID + // and square once a new event ID is found (case below) + data.squared_worker_flatimg[index_flat] += value; + } else { + // Different event : square deposited quantity from the last event ID + // and start accumulating deposited quantity for this new event ID + auto v = data.squared_worker_flatimg[index_flat]; + ImageAddValue(cpp_image, index, v * v); + // new temp value + data.squared_worker_flatimg[index_flat] = value; + } +} + +void GateDoseActor::FlushSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image) { +// G4AutoLock mutex(&SetWorkerEndRunMutex); + + itk::ImageRegionIterator iterator3D( + cpp_image, cpp_image->GetLargestPossibleRegion()); + for (iterator3D.GoToBegin(); !iterator3D.IsAtEnd(); ++iterator3D) { + Image3DType::IndexType index_f = iterator3D.GetIndex(); + Image3DType::PixelType pixelValue3D = + data.squared_worker_flatimg[sub2ind(index_f)]; + ImageAddValue(cpp_image, index_f, + pixelValue3D * pixelValue3D); } } int GateDoseActor::EndOfRunActionMasterThread(int run_id) { - // if (goalUncertainty != 0.0) { - // double unc = ComputeMeanUncertainty(); - // if (unc <= goalUncertainty) { - // return 1; - // } else { - // return 0; - // } - // } else { - // return 0; - // } return 0; } @@ -390,10 +386,5 @@ double GateDoseActor::GetMaxValueOfImage(Image3DType::Pointer imageP) { } } } - - // while (!pq.empty()) { - // std::cout << pq.top() << " "; - // pq.pop(); - // } return max; } diff --git a/core/opengate_core/opengate_lib/GateDoseActor.h b/core/opengate_core/opengate_lib/GateDoseActor.h index 82f399291..2a2a9e670 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.h +++ b/core/opengate_core/opengate_lib/GateDoseActor.h @@ -49,14 +49,26 @@ class GateDoseActor : public GateVActor { inline void SetToWaterFlag(const bool b) { fToWaterFlag = b; } - inline bool GetSquareFlag() const { return fSquareFlag; } + inline bool GetEdepSquaredFlag() const { return fEdepSquaredFlag; } - inline void SetSquareFlag(const bool b) { fSquareFlag = b; } + inline void SetEdepSquaredFlag(const bool b) { fEdepSquaredFlag = b; } inline void SetDensityFlag(const bool b) { fDensityFlag = b; } inline bool GetDensityFlag() const { return fDensityFlag; } + inline void SetDoseFlag(const bool b) { fDoseFlag = b; } + + inline bool GetDoseFlag() const { return fDoseFlag; } + + inline void SetDoseSquaredFlag(const bool b) { fDoseSquaredFlag = b; } + + inline bool GetDoseSquaredFlag() const { return fDoseSquaredFlag; } + + inline void SetCountsFlag(const bool b) { fCountsFlag = b; } + + inline bool GetCountsFlag() const { return fCountsFlag; } + inline std::string GetPhysicalVolumeName() const { return fPhysicalVolumeName; } @@ -75,16 +87,37 @@ class GateDoseActor : public GateVActor { // The image is accessible on py side (shared by all threads) Image3DType::Pointer cpp_edep_image; - // Image3DType::Pointer cpp_dose_image; - Image3DType::Pointer cpp_square_image; - Image3DType::SizeType size_edep{}; + Image3DType::Pointer cpp_edep_squared_image; + Image3DType::Pointer cpp_dose_image; + Image3DType::Pointer cpp_dose_squared_image; Image3DType::Pointer cpp_density_image; + Image3DType::Pointer cpp_counts_image; + Image3DType::SizeType size_edep{}; + + struct threadLocalT { + G4EmCalculator emcalc; + std::vector linear_worker_flatimg; + std::vector squared_worker_flatimg; + std::vector lastid_worker_flatimg; + // int NbOfEvent_worker = 0; + // Image3DType::IndexType index3D; + // int index_flat; + }; +// using ThreadLocalType = struct threadLocalT; + + void ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, Image3DType::IndexType index); + + void FlushSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image); + + void PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels); + + // // Option: indicate if we must compute uncertainty // bool fUncertaintyFlag; // Option: indicate if we must compute square - bool fSquareFlag{}; + bool fEdepSquaredFlag{}; // // Option: indicate if we must compute dose in Gray also // bool fDoseFlag; @@ -94,9 +127,16 @@ class GateDoseActor : public GateVActor { // Option: indicate we must convert to dose to water bool fToWaterFlag{}; - // Option: indicate the density is needed + // Option: Is dose to be scored? + bool fDoseFlag{}; + bool fDoseSquaredFlag{}; + + // Option: Is density to be scored? bool fDensityFlag{}; + // Option: Are counts to be scored + bool fCountsFlag{}; + // // Option: calculate dose in stepping action. If False, calc only edep and // // divide by mass at the end of the simulation, on py side // bool fOnFlyCalcFlag; @@ -123,16 +163,8 @@ class GateDoseActor : public GateVActor { std::string fHitType; protected: - struct threadLocalT { - G4EmCalculator emcalc; - std::vector edep_worker_flatimg; - std::vector edepSquared_worker_flatimg; - std::vector lastid_worker_flatimg; - // int NbOfEvent_worker = 0; - // Image3DType::IndexType index3D; - // int index_flat; - }; - G4Cache fThreadLocalData; + G4Cache fThreadLocalDataEdep; + G4Cache fThreadLocalDataDose; }; #endif // GateDoseActor_h diff --git a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp index 179d83f21..5d01fd046 100644 --- a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp @@ -37,18 +37,27 @@ void init_GateDoseActor(py::module &m) { &GateDoseActor::BeginOfRunActionMasterThread) .def("EndOfRunActionMasterThread", &GateDoseActor::EndOfRunActionMasterThread) - .def("GetSquareFlag", &GateDoseActor::GetSquareFlag) - .def("SetSquareFlag", &GateDoseActor::SetSquareFlag) + .def("GetEdepSquaredFlag", &GateDoseActor::GetEdepSquaredFlag) + .def("SetEdepSquaredFlag", &GateDoseActor::SetEdepSquaredFlag) + .def("GetDoseFlag", &GateDoseActor::GetDoseFlag) + .def("SetDoseFlag", &GateDoseActor::SetDoseFlag) + .def("GetDoseSquaredFlag", &GateDoseActor::GetDoseSquaredFlag) + .def("SetDoseSquaredFlag", &GateDoseActor::SetDoseSquaredFlag) .def("GetToWaterFlag", &GateDoseActor::GetToWaterFlag) .def("SetToWaterFlag", &GateDoseActor::SetToWaterFlag) .def("GetDensityFlag", &GateDoseActor::GetDensityFlag) .def("SetDensityFlag", &GateDoseActor::SetDensityFlag) + .def("GetCountsFlag", &GateDoseActor::GetCountsFlag) + .def("SetCountsFlag", &GateDoseActor::SetCountsFlag) .def("GetPhysicalVolumeName", &GateDoseActor::GetPhysicalVolumeName) .def("SetPhysicalVolumeName", &GateDoseActor::SetPhysicalVolumeName) .def_readwrite("NbOfEvent", &GateDoseActor::NbOfEvent) .def_readwrite("cpp_edep_image", &GateDoseActor::cpp_edep_image) + .def_readwrite("cpp_edep_squared_image", &GateDoseActor::cpp_edep_squared_image) + .def_readwrite("cpp_dose_image", &GateDoseActor::cpp_dose_image) + .def_readwrite("cpp_dose_squared_image", &GateDoseActor::cpp_dose_squared_image) .def_readwrite("cpp_density_image", &GateDoseActor::cpp_density_image) - .def_readwrite("cpp_square_image", &GateDoseActor::cpp_square_image) + .def_readwrite("cpp_counts_image", &GateDoseActor::cpp_counts_image) .def_readwrite("fPhysicalVolumeName", &GateDoseActor::fPhysicalVolumeName); } From 930ed22c302764306a9941a29a3a7ee1aaead7dc Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 10:54:17 +0200 Subject: [PATCH 145/183] Update kwarg ignore_value from 1 to 0 in calls to tests.utility.assert_images() --- opengate/tests/src/test008_dose_actor.py | 2 +- .../tests/src/test010_generic_source_angular_distribution.py | 2 +- opengate/tests/src/test030_dose_motion.py | 2 +- opengate/tests/src/test030_dose_motion_dynamic_param.py | 2 +- opengate/tests/src/test030_dose_motion_dynamic_param_custom.py | 2 +- opengate/tests/src/test041_dose_actor.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opengate/tests/src/test008_dose_actor.py b/opengate/tests/src/test008_dose_actor.py index 873f99445..b4f550612 100755 --- a/opengate/tests/src/test008_dose_actor.py +++ b/opengate/tests/src/test008_dose_actor.py @@ -117,7 +117,7 @@ dose.edep_uncertainty.get_output_path(), stat, tolerance=30, - ignore_value=1, + ignore_value=0, sum_tolerance=1, ) and is_ok diff --git a/opengate/tests/src/test010_generic_source_angular_distribution.py b/opengate/tests/src/test010_generic_source_angular_distribution.py index fbe538c91..5167c3a33 100755 --- a/opengate/tests/src/test010_generic_source_angular_distribution.py +++ b/opengate/tests/src/test010_generic_source_angular_distribution.py @@ -113,7 +113,7 @@ dose.edep_uncertainty.get_output_path(), stat, tolerance=30, - ignore_value=1, + ignore_value=0, sum_tolerance=1, ) and is_ok diff --git a/opengate/tests/src/test030_dose_motion.py b/opengate/tests/src/test030_dose_motion.py index 1e2f0eda2..c3d24dc2f 100755 --- a/opengate/tests/src/test030_dose_motion.py +++ b/opengate/tests/src/test030_dose_motion.py @@ -129,7 +129,7 @@ dose.edep_uncertainty.get_output_path(), stats, tolerance=15, - ignore_value=1, + ignore_value=0, ) ) and is_ok diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param.py b/opengate/tests/src/test030_dose_motion_dynamic_param.py index 0fa78e1d3..5997f77ba 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param.py @@ -125,7 +125,7 @@ dose.edep_uncertainty.get_output_path(), stats, tolerance=15, - ignore_value=1, + ignore_value=0, ) and is_ok ) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py index eb5f0f474..4635751aa 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py @@ -202,7 +202,7 @@ def apply_change(self, run_id): dose.edep_uncertainty.get_output_path(), stats, tolerance=15, - ignore_value=1, + ignore_value=0, ) and is_ok ) diff --git a/opengate/tests/src/test041_dose_actor.py b/opengate/tests/src/test041_dose_actor.py index 96342a019..d3b374c08 100755 --- a/opengate/tests/src/test041_dose_actor.py +++ b/opengate/tests/src/test041_dose_actor.py @@ -119,7 +119,7 @@ dose_actor.edep_uncertainty.get_output_path(), stats, tolerance=30, - ignore_value=1, + ignore_value=0, ) and is_ok ) From a1bbe1f8950d6e7a53a5bb04942c8e258f692441 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:26:18 +0200 Subject: [PATCH 146/183] In SingleItkImageWithVariance.get_variance_or_uncertainty(): set variance to zero if not squared data is available --- opengate/actors/dataitems.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opengate/actors/dataitems.py b/opengate/actors/dataitems.py index f5eed4629..119b79698 100644 --- a/opengate/actors/dataitems.py +++ b/opengate/actors/dataitems.py @@ -675,9 +675,9 @@ def get_variance_or_uncertainty(self, which_quantity): elif self.data[1] is None or self.data[1].data is None: warning( "This data item does not contain squared values so no variance can be calculated. " - "The variance will be set to 1 everywhere. " + "The variance will be set to 0 everywhere. " ) - output_arr = np.ones_like(value_array) + output_arr = np.zeros_like(value_array) else: squared_value_array = np.asarray(self.data[1].data) output_arr = calculate_variance( From ba114fda2060d20148d6eb0519dc6679236df549 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:27:23 +0200 Subject: [PATCH 147/183] Improve ignora_value logic in assert_images() --- opengate/tests/utility.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index c543ccbae..db7c278b7 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -313,7 +313,8 @@ def assert_images( filename2, stats=None, tolerance=0, - ignore_value=0, + ignore_value_data1=None, + ignore_value_data2=None, axis="z", fig_name=None, sum_tolerance=5, @@ -337,8 +338,23 @@ def assert_images( if scaleImageValuesFactor: data2 *= scaleImageValuesFactor - s1 = np.sum(data1) - s2 = np.sum(data2) + # do not consider pixels with a certain value + if ignore_value_data1 is None and ignore_value_data2 is None: + d1 = data1 + d2 = data2 + else: + if ignore_value_data1 is not None and ignore_value_data2 is not None: + mask = np.logical_or(data1 != ignore_value_data1, data2 != ignore_value_data2) + elif ignore_value_data1 is not None: + mask = data1 != ignore_value_data1 + else: + mask = data2 != ignore_value_data2 + d1 = data1[mask] + d2 = data2[mask] + + s1 = np.sum(d1) + s2 = np.sum(d2) + if s1 == 0 and s2 == 0: t = 0 else: @@ -350,10 +366,6 @@ def assert_images( print(f"Image1: {info1.size} {info1.spacing} {info1.origin} {ref_filename1}") print(f"Image2: {info2.size} {info2.spacing} {info2.origin} {filename2}") - # do not consider pixels with a value of zero (data2 is the reference) - d1 = data1[data2 != ignore_value] - d2 = data2[data2 != ignore_value] - # normalise by event if stats is not None: d1 = d1 / stats.counts.events From 9c2cf53148b6381a111ae4d14635b9aefc19398e Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:28:01 +0200 Subject: [PATCH 148/183] Update tests and tolerances in test008_dose_actor.py --- opengate/tests/src/test008_dose_actor.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/opengate/tests/src/test008_dose_actor.py b/opengate/tests/src/test008_dose_actor.py index b4f550612..a6e6dfe02 100755 --- a/opengate/tests/src/test008_dose_actor.py +++ b/opengate/tests/src/test008_dose_actor.py @@ -99,28 +99,16 @@ print("\nDifference for EDEP") is_ok = ( - utility.assert_images( - ref_path / "output-Edep.mhd", - dose.edep.get_output_path(), - stat, - tolerance=13, - ignore_value=0, - sum_tolerance=1, - ) - and is_ok + utility.assert_images(ref_path / "output-Edep.mhd", dose.edep.get_output_path(), stat, tolerance=13, + ignore_value_data2=0, sum_tolerance=1.5) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - ref_path / "output-Edep-Uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), - stat, - tolerance=30, - ignore_value=0, - sum_tolerance=1, - ) - and is_ok + utility.assert_images(ref_path / "output-Edep-Uncertainty.mhd", dose.edep_uncertainty.get_output_path(), + stat, tolerance=30, ignore_value_data2=0, sum_tolerance=3) + and is_ok ) utility.test_ok(is_ok) From b311a905d6f085d56ee5a2c195afd5bfb7f14c49 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:31:04 +0200 Subject: [PATCH 149/183] Update calls to assert_images: signature has changed --- ...010_generic_source_angular_distribution.py | 28 +++++--------- .../src/test010_generic_source_confine.py | 8 +--- opengate/tests/src/test012_mt_dose_actor.py | 10 ++--- opengate/tests/src/test015_iec_phantom_2.py | 11 +----- .../tests/src/test015_iec_phantom_3_mt.py | 6 +-- .../tests/src/test015_iec_phantom_5_wip.py | 11 +----- opengate/tests/src/test017_repeater.py | 11 ++---- opengate/tests/src/test020_profiling.py | 8 +--- opengate/tests/src/test021_voxel_source2.py | 12 ++---- opengate/tests/src/test023_filters.py | 18 ++------- .../tests/src/test023_filters_iec_phantom.py | 9 +---- .../tests/src/test023_filters_material.py | 9 +---- .../src/test028_ge_nm670_spect_2_helpers.py | 30 ++++----------- .../src/test029_volume_time_rotation_1.py | 13 ++----- .../test029_volume_time_rotation_1_process.py | 13 ++----- .../src/test029_volume_time_rotation_2.py | 13 ++----- opengate/tests/src/test030_dose_motion.py | 21 +++------- .../src/test030_dose_motion_dynamic_param.py | 22 +++-------- ...est030_dose_motion_dynamic_param_custom.py | 22 +++-------- .../src/test032_create_voxelized_volume.py | 9 +---- opengate/tests/src/test032_voxel_vs_volume.py | 9 +---- .../src/test032_voxelized_volume_source.py | 24 ++++-------- .../src/test033_rotation_spect_aa_helpers.py | 24 +++--------- opengate/tests/src/test034_gan_phsp_linac.py | 11 ++---- opengate/tests/src/test035_dose_rate.py | 11 ++---- .../src/test036_proj_param_2_with_index.py | 13 ++----- .../src/test038_gan_phsp_spect_gan_aa.py | 13 ++----- opengate/tests/src/test041_dose_actor.py | 34 +++++------------ .../test041_dose_actor_dose_to_water_mt.py | 10 +---- ...st041_dose_actor_mt_cp_images_n_threads.py | 10 +---- ...ose_actor_mt_standard_error_of_mean_WIP.py | 10 +---- opengate/tests/src/test042_gauss_gps.py | 38 ++++++------------- opengate/tests/src/test043_garf.py | 25 +++--------- opengate/tests/src/test043_garf_analog.py | 24 +++--------- opengate/tests/src/test043_garf_mt.py | 25 +++--------- opengate/tests/src/test044_pbs.py | 10 +---- opengate/tests/src/test044_pbs_rot_transl.py | 12 ++---- .../tests/src/test044_pbs_source_to_volume.py | 10 +---- opengate/tests/src/test044_pbs_unfocused.py | 11 ++---- .../tests/src/test047_gan_vox_source_cond.py | 12 ++---- .../src/test048_split_spect_projections.py | 12 ++---- .../tests/src/test050_let_actor_letd_mt.py | 12 ++---- .../test059_tpsource_flat_generation_flag.py | 25 ++++-------- .../tests/src/test059_tpsource_weights.py | 20 ++-------- .../test065_dose2water_from_edep2water_ct.py | 6 +-- .../tests/src/test065_dose_from_edep_ct.py | 6 +-- .../src/test065_dose_from_edep_volume.py | 6 +-- .../src/test067_cbct_fluence_actor_mt.py | 8 +--- opengate/tests/src/test073_helpers.py | 12 +----- 49 files changed, 186 insertions(+), 541 deletions(-) diff --git a/opengate/tests/src/test010_generic_source_angular_distribution.py b/opengate/tests/src/test010_generic_source_angular_distribution.py index 5167c3a33..5a6e16e5c 100755 --- a/opengate/tests/src/test010_generic_source_angular_distribution.py +++ b/opengate/tests/src/test010_generic_source_angular_distribution.py @@ -94,29 +94,19 @@ print("\nDifference for EDEP") is_ok = ( - utility.assert_images( - ref_path / "test010-generic_source_angular_distribution_edep_ref.mhd", - dose.edep.get_output_path(), - stat, - tolerance=13, - ignore_value=0, - sum_tolerance=1, - ) - and is_ok + utility.assert_images(ref_path / "test010-generic_source_angular_distribution_edep_ref.mhd", + dose.edep.get_output_path(), stat, tolerance=13, ignore_value_data2=0, + sum_tolerance=1) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - ref_path - / "test010-generic_source_angular_distribution_edep_uncertainty_ref.mhd", - dose.edep_uncertainty.get_output_path(), - stat, - tolerance=30, - ignore_value=0, - sum_tolerance=1, - ) - and is_ok + utility.assert_images(ref_path + / "test010-generic_source_angular_distribution_edep_uncertainty_ref.mhd", + dose.edep_uncertainty.get_output_path(), stat, tolerance=30, ignore_value_data2=0, + sum_tolerance=1) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source_confine.py b/opengate/tests/src/test010_generic_source_confine.py index 489dbc480..641974d91 100755 --- a/opengate/tests/src/test010_generic_source_confine.py +++ b/opengate/tests/src/test010_generic_source_confine.py @@ -122,11 +122,7 @@ # tests stats_ref = utility.read_stat_file(paths.output_ref / "test010_confine_stats.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.10) - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test010-2-edep.mhd", - dose_actor.edep.get_output_path(), - stats, - tolerance=59, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test010-2-edep.mhd", dose_actor.edep.get_output_path(), + stats, tolerance=59) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test012_mt_dose_actor.py b/opengate/tests/src/test012_mt_dose_actor.py index c904ab688..78a28ac8b 100755 --- a/opengate/tests/src/test012_mt_dose_actor.py +++ b/opengate/tests/src/test012_mt_dose_actor.py @@ -103,12 +103,8 @@ is_ok = utility.assert_stats(stat, stats_ref, 0.10) is_ok = ( - utility.assert_images( - paths.gate_output / "output-Edep.mhd", - dose_actor.edep.get_output_path(), - stat, - tolerance=45, - ) - and is_ok + utility.assert_images(paths.gate_output / "output-Edep.mhd", dose_actor.edep.get_output_path(), stat, + tolerance=45) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_2.py b/opengate/tests/src/test015_iec_phantom_2.py index 5f93ca810..8b1639d56 100755 --- a/opengate/tests/src/test015_iec_phantom_2.py +++ b/opengate/tests/src/test015_iec_phantom_2.py @@ -72,15 +72,8 @@ is_ok = utility.assert_stats(stats_actor, stats_ref, tolerance=0.03) # compare images - im_ok = utility.assert_images( - paths.output_ref / "test015_iec_2.mhd", - dose.edep.get_output_path(), - stats_actor, - axis="x", - tolerance=40, - ignore_value=0, - sum_tolerance=2, - ) + im_ok = utility.assert_images(paths.output_ref / "test015_iec_2.mhd", dose.edep.get_output_path(), stats_actor, + tolerance=40, ignore_value_data2=0, axis="x", sum_tolerance=2) is_ok = is_ok and im_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_3_mt.py b/opengate/tests/src/test015_iec_phantom_3_mt.py index 5f8109b0b..ad66251d7 100755 --- a/opengate/tests/src/test015_iec_phantom_3_mt.py +++ b/opengate/tests/src/test015_iec_phantom_3_mt.py @@ -78,11 +78,11 @@ paths.output_ref / "test015_iec_3.mhd", dose.edep.get_output_path(), stats, - axis="y", tolerance=80, - ignore_value=0, + ignore_value_data2=0, + axis="y", sum_tolerance=0.5, - sad_profile_tolerance=2, + sad_profile_tolerance=2 ) is_ok = is_ok and im_ok diff --git a/opengate/tests/src/test015_iec_phantom_5_wip.py b/opengate/tests/src/test015_iec_phantom_5_wip.py index af0853ede..55cdb3b02 100755 --- a/opengate/tests/src/test015_iec_phantom_5_wip.py +++ b/opengate/tests/src/test015_iec_phantom_5_wip.py @@ -79,15 +79,8 @@ # compare images f = paths.output / "test015_iec_2.mhd" - im_ok = utility.assert_images( - paths.output_ref / "test015_iec_2.mhd", - dose.output, - stats, - axis="x", - tolerance=40, - ignore_value=0, - sum_tolerance=2, - ) + im_ok = utility.assert_images(paths.output_ref / "test015_iec_2.mhd", dose.output, stats, tolerance=40, + ignore_value_data2=0, axis="x", sum_tolerance=2) is_ok = is_ok and im_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test017_repeater.py b/opengate/tests/src/test017_repeater.py index fd0afb173..59a8ec250 100755 --- a/opengate/tests/src/test017_repeater.py +++ b/opengate/tests/src/test017_repeater.py @@ -101,14 +101,9 @@ stats_ref = utility.read_stat_file(paths.output_ref / "test017-stats-ref.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.04) is_ok = ( - utility.assert_images( - paths.output_ref / "test017-edep-ref.mhd", - dose.edep.get_output_path(), - stats, - sum_tolerance=6, - tolerance=70, - ) - and is_ok + utility.assert_images(paths.output_ref / "test017-edep-ref.mhd", dose.edep.get_output_path(), stats, + tolerance=70, sum_tolerance=6) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test020_profiling.py b/opengate/tests/src/test020_profiling.py index 8574b60fa..af4b5e544 100755 --- a/opengate/tests/src/test020_profiling.py +++ b/opengate/tests/src/test020_profiling.py @@ -100,10 +100,6 @@ stats_ref = utility.read_stat_file(paths.gate / "output" / "stat_profiling.txt") stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, 0.1) - is_ok = is_ok and utility.assert_images( - paths.gate / "output" / "output_profiling-Edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=79, - ) + is_ok = is_ok and utility.assert_images(paths.gate / "output" / "output_profiling-Edep.mhd", + dose.edep.get_output_path(), stats, tolerance=79) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test021_voxel_source2.py b/opengate/tests/src/test021_voxel_source2.py index 00d94c167..f6bb050a9 100755 --- a/opengate/tests/src/test021_voxel_source2.py +++ b/opengate/tests/src/test021_voxel_source2.py @@ -147,15 +147,9 @@ is_ok = utility.assert_stats(stats, stats_ref, 0.1) and is_ok is_ok = ( - utility.assert_images( - paths.output_ref / "test021-edep_2.mhd", - dose.edep.get_output_path(), - stats, - tolerance=11, - ignore_value=0, - axis="y", - ) - and is_ok + utility.assert_images(paths.output_ref / "test021-edep_2.mhd", dose.edep.get_output_path(), stats, + tolerance=11, ignore_value_data2=0, axis="y") + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters.py b/opengate/tests/src/test023_filters.py index 62b8e8cd8..fd4e3cb84 100755 --- a/opengate/tests/src/test023_filters.py +++ b/opengate/tests/src/test023_filters.py @@ -101,21 +101,11 @@ is_ok = utility.assert_stats(stat, stats_ref, 0.8) print() - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test023-edep.mhd", - dose1.edep.get_output_path(), - stat, - tolerance=50, - sum_tolerance=4, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-edep.mhd", dose1.edep.get_output_path(), stat, + tolerance=50, sum_tolerance=4) print() - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test023-noe-edep.mhd", - dose2.edep.get_output_path(), - stat, - tolerance=40, - sum_tolerance=2, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-noe-edep.mhd", dose2.edep.get_output_path(), + stat, tolerance=40, sum_tolerance=2) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_iec_phantom.py b/opengate/tests/src/test023_filters_iec_phantom.py index 3e8044083..9a6dc20fc 100755 --- a/opengate/tests/src/test023_filters_iec_phantom.py +++ b/opengate/tests/src/test023_filters_iec_phantom.py @@ -81,12 +81,7 @@ f = paths.output_ref / "test023_stats_iec_phantom.txt" stats_ref = utility.read_stat_file(f) is_ok = utility.assert_stats(stat, stats_ref, 0.12) - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test023_iec_phantom.mhd", - dose.edep.get_output_path(), - stat, - sum_tolerance=28, - tolerance=102, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test023_iec_phantom.mhd", dose.edep.get_output_path(), + stat, tolerance=102, sum_tolerance=28) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_material.py b/opengate/tests/src/test023_filters_material.py index 7af904ed3..4863d4316 100755 --- a/opengate/tests/src/test023_filters_material.py +++ b/opengate/tests/src/test023_filters_material.py @@ -99,12 +99,7 @@ stats_ref = utility.read_stat_file(f2) is_ok = utility.assert_stats(stat2, stats_ref, 0.07) and is_ok - is_ok = is_ok and utility.assert_images( - paths.output_ref / "test023-edep.mhd", - dose.edep.get_output_path(), - stat, - sum_tolerance=3, - tolerance=50, - ) + is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-edep.mhd", dose.edep.get_output_path(), stat, + tolerance=50, sum_tolerance=3) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py index fdd05fee0..6a44ba156 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py @@ -354,17 +354,10 @@ def test_spect_proj(sim, paths, proj, version="3"): img.SetOrigin(origin) itk.imwrite(img, str(paths.output / fname_offset)) is_ok = ( - utility.assert_images( - paths.gate_output / f"projection{version}.mhd", - paths.output / fname_offset, - stats, - tolerance=16, - ignore_value=0, - axis="y", - sum_tolerance=1.6, - fig_name=paths.output / f"proj028_{version}_offset.png", - ) - and is_ok + utility.assert_images(paths.gate_output / f"projection{version}.mhd", paths.output / fname_offset, + stats, tolerance=16, ignore_value_data2=0, axis="y", + fig_name=paths.output / f"proj028_{version}_offset.png", sum_tolerance=1.6) + and is_ok ) # compare images with Gate @@ -374,17 +367,10 @@ def test_spect_proj(sim, paths, proj, version="3"): print("Compare images (new spacing/origin") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images( - paths.output_ref / "proj028_ref.mhd", - paths.output / fname, - stats, - tolerance=14, - ignore_value=0, - axis="y", - sum_tolerance=1.5, - fig_name=paths.output / f"proj028_{version}_no_offset.png", - ) - and is_ok + utility.assert_images(paths.output_ref / "proj028_ref.mhd", paths.output / fname, stats, + tolerance=14, ignore_value_data2=0, axis="y", + fig_name=paths.output / f"proj028_{version}_no_offset.png", sum_tolerance=1.5) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test029_volume_time_rotation_1.py b/opengate/tests/src/test029_volume_time_rotation_1.py index 7fd41e0ac..80bdce7f8 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1.py +++ b/opengate/tests/src/test029_volume_time_rotation_1.py @@ -47,16 +47,9 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images( - paths.output_ref / "proj029.mhd", - proj_actor.get_output_path(), # get the path by asking the actor; better than hard-code the path - stats, - tolerance=59, - ignore_value=0, - axis="x", - sum_tolerance=2, - ) - and is_ok + utility.assert_images(paths.output_ref / "proj029.mhd", proj_actor.get_output_path(), stats, tolerance=59, + ignore_value_data2=0, axis="x", sum_tolerance=2) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_1_process.py b/opengate/tests/src/test029_volume_time_rotation_1_process.py index bda95c12b..cc1012b94 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1_process.py +++ b/opengate/tests/src/test029_volume_time_rotation_1_process.py @@ -37,16 +37,9 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images( - paths.output_ref / "proj029.mhd", - proj_actor.get_output_path(), # get the path by asking the actor; better than hard-code the path - stats, - tolerance=59, - ignore_value=0, - axis="x", - sum_tolerance=2, - ) - and is_ok + utility.assert_images(paths.output_ref / "proj029.mhd", proj_actor.get_output_path(), stats, tolerance=59, + ignore_value_data2=0, axis="x", sum_tolerance=2) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_2.py b/opengate/tests/src/test029_volume_time_rotation_2.py index a3538ce77..38c48d1a5 100755 --- a/opengate/tests/src/test029_volume_time_rotation_2.py +++ b/opengate/tests/src/test029_volume_time_rotation_2.py @@ -47,16 +47,9 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images( - paths.output_ref / "proj029_scaled.mhd", - proj_actor.get_output_path(), # get the path by asking the actor; better than hard-code the path - stats, - tolerance=60, - ignore_value=0, - axis="x", - sum_tolerance=6, - ) - and is_ok + utility.assert_images(paths.output_ref / "proj029_scaled.mhd", proj_actor.get_output_path(), stats, + tolerance=60, ignore_value_data2=0, axis="x", sum_tolerance=6) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test030_dose_motion.py b/opengate/tests/src/test030_dose_motion.py index c3d24dc2f..71f47a51e 100755 --- a/opengate/tests/src/test030_dose_motion.py +++ b/opengate/tests/src/test030_dose_motion.py @@ -112,25 +112,16 @@ gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=30, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, + tolerance=30, ignore_value_data2=0) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), - stats, - tolerance=15, - ignore_value=0, - ) + utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), stats, tolerance=15, + ignore_value_data2=0) ) and is_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param.py b/opengate/tests/src/test030_dose_motion_dynamic_param.py index 5997f77ba..fb84bcaec 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param.py @@ -108,26 +108,16 @@ print() gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=30, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, + tolerance=30, ignore_value_data2=0) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), - stats, - tolerance=15, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), stats, tolerance=15, ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py index 4635751aa..87afb55eb 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py @@ -185,26 +185,16 @@ def apply_change(self, run_id): print() gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=30, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, + tolerance=30, ignore_value_data2=0) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), - stats, - tolerance=15, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), stats, tolerance=15, ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_create_voxelized_volume.py b/opengate/tests/src/test032_create_voxelized_volume.py index 47b74ec35..6dced7fad 100755 --- a/opengate/tests/src/test032_create_voxelized_volume.py +++ b/opengate/tests/src/test032_create_voxelized_volume.py @@ -64,13 +64,8 @@ # compare images gate.exception.warning("\nDifference with ref image") is_ok = ( - utility.assert_images( - paths.output_ref / "test032_iec.mhd", - path_to_image_1mm, - stats=None, - tolerance=0.01, - ) - and is_ok + utility.assert_images(paths.output_ref / "test032_iec.mhd", path_to_image_1mm, stats=None, tolerance=0.01) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_voxel_vs_volume.py b/opengate/tests/src/test032_voxel_vs_volume.py index 7e1f55af1..facd4fb2b 100755 --- a/opengate/tests/src/test032_voxel_vs_volume.py +++ b/opengate/tests/src/test032_voxel_vs_volume.py @@ -141,12 +141,7 @@ dose2 = sim.get_actor("dose2") # compare edep map - is_ok = utility.assert_images( - dose1.edep.get_output_path(), - dose2.edep.get_output_path(), - stats, - tolerance=87, - axis="x", - ) + is_ok = utility.assert_images(dose1.edep.get_output_path(), dose2.edep.get_output_path(), stats, tolerance=87, + axis="x") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_voxelized_volume_source.py b/opengate/tests/src/test032_voxelized_volume_source.py index 292b6c8d3..4f7c0518f 100755 --- a/opengate/tests/src/test032_voxelized_volume_source.py +++ b/opengate/tests/src/test032_voxelized_volume_source.py @@ -49,27 +49,19 @@ # compare images gate.exception.warning("\nDifference with ref image") is_ok = ( - utility.assert_images( - paths.output_ref / "iec_10mm.mhd", f1, stats=None, tolerance=0.001 - ) - and is_ok + utility.assert_images(paths.output_ref / "iec_10mm.mhd", f1, stats=None, tolerance=0.001) + and is_ok ) is_ok = ( - utility.assert_images( - paths.output_ref / "iec_source_10mm.mhd", f2, stats=None, tolerance=0.001 - ) - and is_ok + utility.assert_images(paths.output_ref / "iec_source_10mm.mhd", f2, stats=None, tolerance=0.001) + and is_ok ) is_ok = ( - utility.assert_images( - paths.output_ref / "iec_9mm.mhd", f3, stats=None, tolerance=0.001 - ) - and is_ok + utility.assert_images(paths.output_ref / "iec_9mm.mhd", f3, stats=None, tolerance=0.001) + and is_ok ) is_ok = ( - utility.assert_images( - paths.output_ref / "iec_source_9mm.mhd", f4, stats=None, tolerance=0.001 - ) - and is_ok + utility.assert_images(paths.output_ref / "iec_source_9mm.mhd", f4, stats=None, tolerance=0.001) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index 582aa340a..29a644a17 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -180,26 +180,14 @@ def evaluate_test(sim, sources, itol, ref_skipped): # compare edep map gate.exception.warning(f"Check images") is_ok = ( - utility.assert_images( - paths.output_ref / "test033_proj_1.mhd", - paths.output / "test033_proj_1.mhd", - stats, - tolerance=68, - axis="x", - sum_tolerance=itol, - ) - and is_ok + utility.assert_images(paths.output_ref / "test033_proj_1.mhd", paths.output / "test033_proj_1.mhd", stats, + tolerance=68, axis="x", sum_tolerance=itol) + and is_ok ) is_ok = ( - utility.assert_images( - paths.output_ref / "test033_proj_2.mhd", - paths.output / "test033_proj_2.mhd", - stats, - tolerance=68, - axis="x", - sum_tolerance=itol, - ) - and is_ok + utility.assert_images(paths.output_ref / "test033_proj_2.mhd", paths.output / "test033_proj_2.mhd", stats, + tolerance=68, axis="x", sum_tolerance=itol) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test034_gan_phsp_linac.py b/opengate/tests/src/test034_gan_phsp_linac.py index b63b3bf17..c17240b69 100755 --- a/opengate/tests/src/test034_gan_phsp_linac.py +++ b/opengate/tests/src/test034_gan_phsp_linac.py @@ -119,14 +119,9 @@ gate.exception.warning(f"Check dose") is_ok = ( - utility.assert_images( - paths.gate / "dose-Edep.mhd", - dose.edep.get_output_path(), - stats, - tolerance=58, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate / "dose-Edep.mhd", dose.edep.get_output_path(), stats, tolerance=58, + ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test035_dose_rate.py b/opengate/tests/src/test035_dose_rate.py index 26a281379..8be62b0cf 100755 --- a/opengate/tests/src/test035_dose_rate.py +++ b/opengate/tests/src/test035_dose_rate.py @@ -58,14 +58,9 @@ h = sim.get_actor("dose") print(h) is_ok = ( - utility.assert_images( - paths.output_ref / "edep.mhd", - h.edep.get_output_path(), - stats, - tolerance=15, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.output_ref / "edep.mhd", h.edep.get_output_path(), stats, tolerance=15, + ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test036_proj_param_2_with_index.py b/opengate/tests/src/test036_proj_param_2_with_index.py index 05e5c0581..8a1ebdab9 100755 --- a/opengate/tests/src/test036_proj_param_2_with_index.py +++ b/opengate/tests/src/test036_proj_param_2_with_index.py @@ -31,15 +31,8 @@ # test the output stats = sim.get_actor("Stats") - is_ok = utility.assert_images( - paths.output_ref / "proj1.mha", - proj.get_output_path(), - stats, - tolerance=38, - ignore_value=0, - axis="y", - sum_tolerance=1.5, - fig_name=paths.output / f"proj_index.png", - ) + is_ok = utility.assert_images(paths.output_ref / "proj1.mha", proj.get_output_path(), stats, tolerance=38, + ignore_value_data2=0, axis="y", fig_name=paths.output / f"proj_index.png", + sum_tolerance=1.5) utility.print_test(is_ok, f"Compare image proj:") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py index 9185fe78c..e641f9436 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py @@ -64,15 +64,10 @@ # image is_ok = ( - utility.assert_images( - paths.output_ref / "test038_gan_aa_proj.mhd", - paths.output / "test038_gan_aa_proj.mhd", - stats, - tolerance=70, - axis="x", - sum_tolerance=2.75, - ) - and is_ok + utility.assert_images(paths.output_ref / "test038_gan_aa_proj.mhd", + paths.output / "test038_gan_aa_proj.mhd", stats, tolerance=70, axis="x", + sum_tolerance=2.75) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test041_dose_actor.py b/opengate/tests/src/test041_dose_actor.py index d3b374c08..655ec84a1 100755 --- a/opengate/tests/src/test041_dose_actor.py +++ b/opengate/tests/src/test041_dose_actor.py @@ -102,38 +102,24 @@ gate.exception.warning("\nDifference for EDEP") is_ok = ( - utility.assert_images( - paths.gate_output / "output2-Edep.mhd", - dose_actor.edep.get_output_path(), - stats, - tolerance=10, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate_output / "output2-Edep.mhd", dose_actor.edep.get_output_path(), stats, + tolerance=10, ignore_value_data2=0) + and is_ok ) gate.exception.warning("\nDifference for uncertainty") is_ok = ( - utility.assert_images( - paths.gate_output / "output2-Edep-Uncertainty.mhd", - dose_actor.edep_uncertainty.get_output_path(), - stats, - tolerance=30, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate_output / "output2-Edep-Uncertainty.mhd", + dose_actor.edep_uncertainty.get_output_path(), stats, tolerance=30, + ignore_value_data2=0) + and is_ok ) gate.exception.warning("\nDifference for dose in Gray") is_ok = ( - utility.assert_images( - paths.gate_output / "output2-Dose.mhd", - dose_actor.dose.get_output_path(), - stats, - tolerance=10, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate_output / "output2-Dose.mhd", dose_actor.dose.get_output_path(), stats, + tolerance=10, ignore_value_data2=0) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py index 1f754b215..52a545618 100755 --- a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py +++ b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py @@ -150,14 +150,8 @@ # so we do not need to manually keep track of the paths here in the script # syntax: dose_actor.dose.get_output_path() - unused = utility.assert_images( - dose_actor_IDD_d.dose.get_output_path(), - dose_actor_IDD_d2w.dose.get_output_path(), - stats, - tolerance=100, - ignore_value=0, - axis="x", - ) + unused = utility.assert_images(dose_actor_IDD_d.dose.get_output_path(), dose_actor_IDD_d2w.dose.get_output_path(), + stats, tolerance=100, ignore_value_data2=0, axis="x") mSPR_40MeV = 1.268771331 # from PSTAR NIST tables, Feb 2023 mSPR_80MeV = 1.253197674 # from PSTAR NIST tables, Feb 2023 diff --git a/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py b/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py index 97a607bfe..be14e3015 100755 --- a/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py +++ b/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py @@ -107,14 +107,8 @@ def run_sim(N_events: int, N_threads: int, N_voxels: int, paths): def run_test(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stat): - unused = utility.assert_images( - doseFpath_IDD_singleImage, - doseFpath_IDD_NthreadImages, - stat, - tolerance=100, - ignore_value=0, - axis="x", - ) + unused = utility.assert_images(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stat, tolerance=100, + ignore_value_data2=0, axis="x") expected_ratio = 1.00 gate.exception.warning("Test ratio: edep in single image vs. Nthread image") is_ok = utility.assert_images_ratio( diff --git a/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py b/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py index bc0214697..664cda904 100755 --- a/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py +++ b/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py @@ -127,14 +127,8 @@ def run_sim(n_thr, c4_ref=None, paths=None): sim.get_actor(doseActorName_IDD_singleImage).get_output_path("edep_uncertainty") ) - unused = utility.assert_images( - doseFpath_IDD_singleImage, - doseFpath_IDD_NthreadImages, - stats, - tolerance=100, - ignore_value=0, - axis="x", - ) + unused = utility.assert_images(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stats, tolerance=100, + ignore_value_data2=0, axis="x") expected_ratio = 1.00 gate.exception.warning("Test ratio: dose / dose MT cp image for each trhead") is_ok = utility.assert_images_ratio( diff --git a/opengate/tests/src/test042_gauss_gps.py b/opengate/tests/src/test042_gauss_gps.py index 958cd4b92..35d15c152 100755 --- a/opengate/tests/src/test042_gauss_gps.py +++ b/opengate/tests/src/test042_gauss_gps.py @@ -124,42 +124,28 @@ print() gate.exception.warning("Difference for EDEP XZ") is_ok = ( - utility.assert_images( - paths.gate_output / "lateral_xz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInXZ").get_output_path("edep"), - stats, - tolerance=10, - ignore_value=0, - ) - and is_ok + utility.assert_images(paths.gate_output / "lateral_xz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInXZ").get_output_path("edep"), stats, tolerance=10, + ignore_value_data2=0) + and is_ok ) print() gate.exception.warning("Difference for EDEP XY") is_ok = ( - utility.assert_images( - paths.gate_output / "lateral_xy_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInXY").get_output_path("edep"), - stats, - tolerance=10, - ignore_value=0, - axis="y", - ) - and is_ok + utility.assert_images(paths.gate_output / "lateral_xy_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInXY").get_output_path("edep"), stats, tolerance=10, + ignore_value_data2=0, axis="y") + and is_ok ) print() gate.exception.warning("Difference for EDEP YZ") is_ok = ( - utility.assert_images( - paths.gate_output / "lateral_yz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInYZ").get_output_path("edep"), - stats, - tolerance=30, - ignore_value=0, - axis="y", - ) - and is_ok + utility.assert_images(paths.gate_output / "lateral_yz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInYZ").get_output_path("edep"), stats, tolerance=30, + ignore_value_data2=0, axis="y") + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test043_garf.py b/opengate/tests/src/test043_garf.py index fb092405e..e920fb817 100755 --- a/opengate/tests/src/test043_garf.py +++ b/opengate/tests/src/test043_garf.py @@ -127,30 +127,17 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog.mhd", - filename1, - stat, - tolerance=100, - ignore_value=0, - axis="x", - sum_tolerance=20, - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", filename1, stat, + tolerance=100, ignore_value_data2=0, axis="x", sum_tolerance=20) + and is_ok ) print() gate.exception.warning("Compare image to analog high statistics") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", - filename2, - stat, - tolerance=52, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", filename2, stat, + tolerance=52, ignore_value_data2=0, axis="x") + and is_ok ) print() diff --git a/opengate/tests/src/test043_garf_analog.py b/opengate/tests/src/test043_garf_analog.py index c007214fc..d1563e794 100755 --- a/opengate/tests/src/test043_garf_analog.py +++ b/opengate/tests/src/test043_garf_analog.py @@ -85,27 +85,15 @@ print() gate.exception.warning("Tests projection (old gate)") is_ok = ( - utility.assert_images( - test43.paths.gate_output / "projection_analog.mhd", - fn, - stats, - tolerance=75, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(test43.paths.gate_output / "projection_analog.mhd", fn, stats, tolerance=75, + ignore_value_data2=0, axis="x") + and is_ok ) print() gate.exception.warning("Tests projection (new)") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog.mhd", - proj.get_output_path(), - stats, - tolerance=80, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", proj.get_output_path(), + stats, tolerance=80, ignore_value_data2=0, axis="x") + and is_ok ) diff --git a/opengate/tests/src/test043_garf_mt.py b/opengate/tests/src/test043_garf_mt.py index 99f557f45..9c2b02eae 100755 --- a/opengate/tests/src/test043_garf_mt.py +++ b/opengate/tests/src/test043_garf_mt.py @@ -115,30 +115,17 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog.mhd", - filename1, - stats, - tolerance=100, - ignore_value=0, - axis="x", - sum_tolerance=20, - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", filename1, stats, + tolerance=100, ignore_value_data2=0, axis="x", sum_tolerance=20) + and is_ok ) print() gate.exception.warning("Compare image to analog high statistics") is_ok = ( - utility.assert_images( - test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", - filename2, - stats, - tolerance=52, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", filename2, stats, + tolerance=52, ignore_value_data2=0, axis="x") + and is_ok ) print() diff --git a/opengate/tests/src/test044_pbs.py b/opengate/tests/src/test044_pbs.py index ce32e4f85..f662a06d0 100755 --- a/opengate/tests/src/test044_pbs.py +++ b/opengate/tests/src/test044_pbs.py @@ -159,14 +159,8 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images( - ref_path / mhd_ref, - mhd_gate, - stats, - tolerance=50, - ignore_value=0, - ) - and is_ok + utility.assert_images(ref_path / mhd_ref, mhd_gate, stats, tolerance=50, ignore_value_data2=0) + and is_ok ) """EdepColorMap = utility.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = 'Plane_'+str(i)+'ColorMap.png' diff --git a/opengate/tests/src/test044_pbs_rot_transl.py b/opengate/tests/src/test044_pbs_rot_transl.py index 10eba703d..f516800eb 100755 --- a/opengate/tests/src/test044_pbs_rot_transl.py +++ b/opengate/tests/src/test044_pbs_rot_transl.py @@ -170,15 +170,9 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).edep.get_output_path() mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images( - ref_path / mhd_ref, - output_path / mhd_gate, - stat, - axis="x", - tolerance=50, - ignore_value=0, - ) - and is_ok + utility.assert_images(ref_path / mhd_ref, output_path / mhd_gate, stat, tolerance=50, + ignore_value_data2=0, axis="x") + and is_ok ) """ diff --git a/opengate/tests/src/test044_pbs_source_to_volume.py b/opengate/tests/src/test044_pbs_source_to_volume.py index 439f66724..e18a5c283 100755 --- a/opengate/tests/src/test044_pbs_source_to_volume.py +++ b/opengate/tests/src/test044_pbs_source_to_volume.py @@ -162,14 +162,8 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images( - ref_path / mhd_ref, - mhd_gate, - stat, - tolerance=50, - ignore_value=0, - ) - and is_ok + utility.assert_images(ref_path / mhd_ref, mhd_gate, stat, tolerance=50, ignore_value_data2=0) + and is_ok ) """EdepColorMap = utility.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = "Plane_" + str(i) + "ColorMap.png" diff --git a/opengate/tests/src/test044_pbs_unfocused.py b/opengate/tests/src/test044_pbs_unfocused.py index 2096030ee..ee58e3d0e 100755 --- a/opengate/tests/src/test044_pbs_unfocused.py +++ b/opengate/tests/src/test044_pbs_unfocused.py @@ -172,14 +172,9 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images( - ref_path / mhd_ref, - output_path / mhd_gate, - stats, - tolerance=50, - ignore_value=0, - ) - and is_ok + utility.assert_images(ref_path / mhd_ref, output_path / mhd_gate, stats, tolerance=50, + ignore_value_data2=0) + and is_ok ) """EdepColorMap = utlity.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = 'Plane_'+str(i)+'ColorMap.png' diff --git a/opengate/tests/src/test047_gan_vox_source_cond.py b/opengate/tests/src/test047_gan_vox_source_cond.py index de866f506..c6c49eb18 100755 --- a/opengate/tests/src/test047_gan_vox_source_cond.py +++ b/opengate/tests/src/test047_gan_vox_source_cond.py @@ -140,15 +140,9 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images( - paths.output_ref / "test047-edep.mhd", - dose.get_output_path("edep"), - stats, - tolerance=19, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(paths.output_ref / "test047-edep.mhd", dose.get_output_path("edep"), stats, + tolerance=19, ignore_value_data2=0, axis="x") + and is_ok ) print("Test with vv: ") diff --git a/opengate/tests/src/test048_split_spect_projections.py b/opengate/tests/src/test048_split_spect_projections.py index 0c0a132a9..c496523fe 100755 --- a/opengate/tests/src/test048_split_spect_projections.py +++ b/opengate/tests/src/test048_split_spect_projections.py @@ -40,15 +40,9 @@ is_ok = True for i in range(nb_ene): is_ok = ( - utility.assert_images( - paths.output_ref / f"t048_projection_{i}.mhd", - output_filenames[i], - None, - tolerance=1e-6, - ignore_value=0, - axis="x", - ) - and is_ok + utility.assert_images(paths.output_ref / f"t048_projection_{i}.mhd", output_filenames[i], None, + tolerance=1e-6, ignore_value_data2=0, axis="x") + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test050_let_actor_letd_mt.py b/opengate/tests/src/test050_let_actor_letd_mt.py index b3ad085b5..20bde9437 100755 --- a/opengate/tests/src/test050_let_actor_letd_mt.py +++ b/opengate/tests/src/test050_let_actor_letd_mt.py @@ -165,15 +165,9 @@ fNameIDD = doseIDD.edep.output_filename if do_debug: - is_ok = utility.assert_images( - ref_path / fNameIDD, - doseIDD.edep.get_output_path(), - stats, - tolerance=100, - ignore_value=0, - axis="x", - scaleImageValuesFactor=numPartSimRef / numPartSimTest, - ) + is_ok = utility.assert_images(ref_path / fNameIDD, doseIDD.edep.get_output_path(), stats, tolerance=100, + ignore_value_data2=0, axis="x", + scaleImageValuesFactor=numPartSimRef / numPartSimTest) tests_pass = [] is_ok = utility.assert_filtered_imagesprofile1D( diff --git a/opengate/tests/src/test059_tpsource_flat_generation_flag.py b/opengate/tests/src/test059_tpsource_flat_generation_flag.py index 2eabdaca4..2dd2ac789 100755 --- a/opengate/tests/src/test059_tpsource_flat_generation_flag.py +++ b/opengate/tests/src/test059_tpsource_flat_generation_flag.py @@ -239,27 +239,18 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): # check that the dose output is the same print("--- Dose image spot 0 ---") test = ( - utility.assert_images( - output_path / dose_actors[0].get_output_path("edep"), - output_path / dose_actors[2].get_output_path("edep"), - stats, - tolerance=70, - ignore_value=0, - ) - and test + utility.assert_images(output_path / dose_actors[0].get_output_path("edep"), + output_path / dose_actors[2].get_output_path("edep"), stats, tolerance=70, + ignore_value_data2=0) + and test ) print("--- Dose image spot 1 ---") test = ( - utility.assert_images( - output_path / dose_actors[1].get_output_path("edep"), - output_path / dose_actors[3].get_output_path("edep"), - stats, - tolerance=70, - ignore_value=0, - sum_tolerance=5.2, - ) - and test + utility.assert_images(output_path / dose_actors[1].get_output_path("edep"), + output_path / dose_actors[3].get_output_path("edep"), stats, tolerance=70, + ignore_value_data2=0, sum_tolerance=5.2) + and test ) # check that output with flat distribution has better statistics for the spot with less particles diff --git a/opengate/tests/src/test059_tpsource_weights.py b/opengate/tests/src/test059_tpsource_weights.py index 2e61f3034..8f1680341 100755 --- a/opengate/tests/src/test059_tpsource_weights.py +++ b/opengate/tests/src/test059_tpsource_weights.py @@ -161,26 +161,14 @@ # check first spot test = ( - utility.assert_images( - ref_path / mhd_1, - output_path / mhd_1, - stats, - tolerance=70, - ignore_value=0, - ) - and test + utility.assert_images(ref_path / mhd_1, output_path / mhd_1, stats, tolerance=70, ignore_value_data2=0) + and test ) # check second spot test = ( - utility.assert_images( - ref_path / mhd_1, - output_path / mhd_1, - stats, - tolerance=70, - ignore_value=0, - ) - and test + utility.assert_images(ref_path / mhd_1, output_path / mhd_1, stats, tolerance=70, ignore_value_data2=0) + and test ) print(" --------------------------------------- ") # fig1 = utility.create_2D_Edep_colorMap(output_path / mhd_1, show=True) diff --git a/opengate/tests/src/test065_dose2water_from_edep2water_ct.py b/opengate/tests/src/test065_dose2water_from_edep2water_ct.py index 36d5f228f..929dd3802 100755 --- a/opengate/tests/src/test065_dose2water_from_edep2water_ct.py +++ b/opengate/tests/src/test065_dose2water_from_edep2water_ct.py @@ -172,10 +172,6 @@ # img_mhd_out = itk.imread(d_post_path) # img_mhd_ref = itk.imread(d_step_path) - ok = utility.assert_images( - d_step_path, - d_post_path, - tolerance=10, - ) + ok = utility.assert_images(d_step_path, d_post_path, tolerance=10) utility.test_ok(ok) diff --git a/opengate/tests/src/test065_dose_from_edep_ct.py b/opengate/tests/src/test065_dose_from_edep_ct.py index b21fd8a0c..0e2481c8f 100755 --- a/opengate/tests/src/test065_dose_from_edep_ct.py +++ b/opengate/tests/src/test065_dose_from_edep_ct.py @@ -170,10 +170,6 @@ # img_mhd_out = itk.imread(d_post_path) # img_mhd_ref = itk.imread(d_step_path) - ok = utility.assert_images( - d_step_path, - d_post_path, - tolerance=10, - ) + ok = utility.assert_images(d_step_path, d_post_path, tolerance=10) utility.test_ok(ok) diff --git a/opengate/tests/src/test065_dose_from_edep_volume.py b/opengate/tests/src/test065_dose_from_edep_volume.py index 7fc2b34c8..6ae83655f 100755 --- a/opengate/tests/src/test065_dose_from_edep_volume.py +++ b/opengate/tests/src/test065_dose_from_edep_volume.py @@ -150,10 +150,6 @@ # img_mhd_out = itk.imread(d_post_path) # img_mhd_ref = itk.imread(d_step_path) - ok = utility.assert_images( - d_step_path, - d_post_path, - tolerance=10, - ) + ok = utility.assert_images(d_step_path, d_post_path, tolerance=10) utility.test_ok(ok) diff --git a/opengate/tests/src/test067_cbct_fluence_actor_mt.py b/opengate/tests/src/test067_cbct_fluence_actor_mt.py index a05224812..abe24c73b 100755 --- a/opengate/tests/src/test067_cbct_fluence_actor_mt.py +++ b/opengate/tests/src/test067_cbct_fluence_actor_mt.py @@ -92,12 +92,6 @@ out_path = detector_actor.get_output_path() # check images - is_ok = utility.assert_images( - paths.gate_output / "detector.mhd", - out_path, - stats, - tolerance=44, - axis="y", - ) + is_ok = utility.assert_images(paths.gate_output / "detector.mhd", out_path, stats, tolerance=44, axis="y") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test073_helpers.py b/opengate/tests/src/test073_helpers.py index 00c412b6f..0ea9191af 100644 --- a/opengate/tests/src/test073_helpers.py +++ b/opengate/tests/src/test073_helpers.py @@ -170,16 +170,8 @@ def compare_proj_images(crystal, sim, stats, image_filename, path, n=1): img.SetOrigin(origin) itk.imwrite(img, f2) - is_ok = utility.assert_images( - fr, - f2, - stats, - tolerance=69, - ignore_value=0, - axis="y", - sum_tolerance=6, - fig_name=path / f"test073_test_{n}.png", - ) + is_ok = utility.assert_images(fr, f2, stats, tolerance=69, ignore_value_data2=0, axis="y", + fig_name=path / f"test073_test_{n}.png", sum_tolerance=6) return is_ok From 866e0f94ac0609ea156fda50a71fd45936830c8e Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 11:32:20 +0200 Subject: [PATCH 150/183] Update calls to assert_images in inactive code: signature has changed --- opengate/tests/src/test044_pbs_weight.py | 2 +- opengate/tests/src/test059_tpsource_gantry_rot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opengate/tests/src/test044_pbs_weight.py b/opengate/tests/src/test044_pbs_weight.py index 184c4c18f..1c9359f87 100755 --- a/opengate/tests/src/test044_pbs_weight.py +++ b/opengate/tests/src/test044_pbs_weight.py @@ -184,7 +184,7 @@ # stat, # axis="x", # tolerance=50, - # ignore_value=0, + # ignore_value_data2=0, # ) fig1 = utility.create_2D_Edep_colorMap(output_path / mhd_1, show=False) fig2 = utility.create_2D_Edep_colorMap(output_path / mhd_2, show=False) diff --git a/opengate/tests/src/test059_tpsource_gantry_rot.py b/opengate/tests/src/test059_tpsource_gantry_rot.py index feba93172..a0ee45411 100755 --- a/opengate/tests/src/test059_tpsource_gantry_rot.py +++ b/opengate/tests/src/test059_tpsource_gantry_rot.py @@ -185,7 +185,7 @@ # dose_rot.output, # stat, # tolerance=50, - # ignore_value=0, + # ignore_value_data2=0, # ) ok = True From 7f2f052d0bbd21396e54cbd3e8edc4c8f97196c1 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 12:38:16 +0200 Subject: [PATCH 151/183] Optimize code in create_density_img() and let it return g/cm3 --- opengate/geometry/materials.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/opengate/geometry/materials.py b/opengate/geometry/materials.py index ce207a04a..9a8b5a226 100644 --- a/opengate/geometry/materials.py +++ b/opengate/geometry/materials.py @@ -303,24 +303,18 @@ def create_density_img(img_volume, material_database): Returns ------- rho : itk.Image - image of the same size and resolution of the ct. The voxel value is the density of the voxel. - Density is returned in G4 1/kg. + Image of the same size and resolution of the ct. The voxel value is the density of the voxel converted to g/cm3. """ - voxel_materials = img_volume.voxel_materials - ct_itk = img_volume.itk_image - act = itk.GetArrayFromImage(ct_itk) + act = itk.GetArrayFromImage(img_volume.itk_image) arho = np.zeros(act.shape, dtype=np.float32) - for material in voxel_materials: - *hu_interval, mat_name = material - hu0, hu1 = hu_interval - m = (act >= hu0) * (act < hu1) - density = material_database[mat_name].GetDensity() - arho[m] = density + for hu0, hu1, mat_name in img_volume.voxel_materials: + arho[(act >= hu0) * (act < hu1)] = material_database[mat_name].GetDensity() + arho *= (g4_units.cm3 / g4_units.g) rho = itk.GetImageFromArray(arho) - rho.CopyInformation(ct_itk) + rho.CopyInformation(img_volume.itk_image) return rho From 360e9b25da2c3405ef811d07e61bba2debf18626 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 12:38:52 +0200 Subject: [PATCH 152/183] Remove density scoring via MC from DoseActor because it is incorrect and not needed --- .../opengate_lib/GateDoseActor.cpp | 20 ++------ .../opengate_lib/GateDoseActor.h | 7 --- .../opengate_lib/pyGateDoseActor.cpp | 2 - opengate/actors/doseactors.py | 50 ++++++++----------- 4 files changed, 25 insertions(+), 54 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index 0b1258cad..17d52516b 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -83,9 +83,6 @@ void GateDoseActor::InitializeCpp() { if (fDoseSquaredFlag) { cpp_dose_squared_image = Image3DType::New(); } - if (fDensityFlag) { - cpp_density_image = Image3DType::New(); - } if (fCountsFlag) { cpp_counts_image = Image3DType::New(); } @@ -117,10 +114,6 @@ void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { AttachImageToVolume(cpp_dose_squared_image, fPhysicalVolumeName, fTranslation); } - if (fDensityFlag) { - AttachImageToVolume(cpp_density_image, fPhysicalVolumeName, - fTranslation); - } if (fCountsFlag) { AttachImageToVolume(cpp_counts_image, fPhysicalVolumeName, fTranslation); @@ -220,17 +213,12 @@ void GateDoseActor::SteppingAction(G4Step *step) { ImageAddValue(cpp_edep_image, index, edep); - if (fDensityFlag || fDoseFlag || fDoseSquaredFlag) { + if (fDoseFlag || fDoseSquaredFlag) { auto *current_material = step->GetPreStepPoint()->GetMaterial(); auto density = current_material->GetDensity(); - if (fDensityFlag) { - ImageAddValue(cpp_density_image, index, density); - } - if (fDoseFlag || fDoseSquaredFlag) { - dose = edep / density; - if (fDoseFlag) { - ImageAddValue(cpp_dose_image, index, dose); - } + dose = edep / density; + if (fDoseFlag) { + ImageAddValue(cpp_dose_image, index, dose); } } diff --git a/core/opengate_core/opengate_lib/GateDoseActor.h b/core/opengate_core/opengate_lib/GateDoseActor.h index 2a2a9e670..d0a0a728f 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.h +++ b/core/opengate_core/opengate_lib/GateDoseActor.h @@ -53,10 +53,6 @@ class GateDoseActor : public GateVActor { inline void SetEdepSquaredFlag(const bool b) { fEdepSquaredFlag = b; } - inline void SetDensityFlag(const bool b) { fDensityFlag = b; } - - inline bool GetDensityFlag() const { return fDensityFlag; } - inline void SetDoseFlag(const bool b) { fDoseFlag = b; } inline bool GetDoseFlag() const { return fDoseFlag; } @@ -131,9 +127,6 @@ class GateDoseActor : public GateVActor { bool fDoseFlag{}; bool fDoseSquaredFlag{}; - // Option: Is density to be scored? - bool fDensityFlag{}; - // Option: Are counts to be scored bool fCountsFlag{}; diff --git a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp index 5d01fd046..3e3fdc262 100644 --- a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp @@ -45,8 +45,6 @@ void init_GateDoseActor(py::module &m) { .def("SetDoseSquaredFlag", &GateDoseActor::SetDoseSquaredFlag) .def("GetToWaterFlag", &GateDoseActor::GetToWaterFlag) .def("SetToWaterFlag", &GateDoseActor::SetToWaterFlag) - .def("GetDensityFlag", &GateDoseActor::GetDensityFlag) - .def("SetDensityFlag", &GateDoseActor::SetDensityFlag) .def("GetCountsFlag", &GateDoseActor::GetCountsFlag) .def("SetCountsFlag", &GateDoseActor::SetCountsFlag) .def("GetPhysicalVolumeName", &GateDoseActor::GetPhysicalVolumeName) diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index 0bd60fc06..219b7d02e 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -400,6 +400,20 @@ class DoseActor(VoxelDepositActor, g4.GateDoseActor): ), }, ), + # "calculate_density_from": ( + # "auto", + # { + # "doc": "How should density be calculated?\n" + # "'simulation': via scoring along with the rest of the quantities.\n" + # "'image': from the CT image, if the actor is attached to an ImageVolume.\n" + # "'auto' (default): Let GATE pick the right one for you. ", + # "allowed_values": ( + # "auto", + # "simulation", + # "image" + # ), + # }, + # ), "ste_of_mean": ( False, { @@ -548,13 +562,6 @@ def create_density_image_from_image_volume(self, deposit_image): ) return density_image - @property - def _density_via_mc(self): - return (self.calculate_density_from == 'simulation' or - (self.calculate_density_from == 'auto' - and self.attached_to_volume.volume_type != "ImageVolume") - ) - def initialize(self, *args): """ At the start of the run, the image is centered according to the coordinate system of @@ -596,12 +603,9 @@ def initialize(self, *args): True, item=1 ) # activate squared component - if self.user_output.density.get_active() is True: - # scoring density via MC implies scoring counts - if self._density_via_mc: - if not self.user_output.counts.get_active(): - self.user_output.counts.set_active(True) - self.user_output.counts.set_write_to_disk(False) + if self.user_output.density.get_active() is True and self.attached_to_volume.volume_type != 'ImageVolume': + fatal("The dose actor can only produce a density map if it is attached to an ImageVolume. " + f"This actor is attached to a {self.attached_to_volume.volume_type} volume. ") self.InitializeUserInput(self.user_info) # C++ side # Set the flags on C++ side so the C++ knows which quantities need to be scored @@ -610,7 +614,6 @@ def initialize(self, *args): self.SetDoseSquaredFlag(self.user_output.dose_with_uncertainty.get_active(item=1)) # item=0 is the default self.SetCountsFlag(self.user_output.counts.get_active()) - self.SetDensityFlag(self.user_output.density.get_active() and self._density_via_mc) # C++ side has a boolean toWaterFlag and self.score_in == "water" yields True/False self.SetToWaterFlag(self.score_in == "water") @@ -631,12 +634,6 @@ def BeginOfRunActionMasterThread(self, run_index): self.prepare_output_for_run("dose_with_uncertainty", run_index) self.push_to_cpp_image("dose_with_uncertainty", run_index, self.cpp_dose_image, self.cpp_dose_squared_image) - # density might be active, but the user might want to get it from the ImageVolume - # therefore, we also check for _density_via_mc - if self.user_output.density.get_active() and self._density_via_mc: - self.prepare_output_for_run("density", run_index) - self.push_to_cpp_image("density", run_index, self.cpp_density_image) - if self.user_output.counts.get_active(): self.prepare_output_for_run("counts", run_index) self.push_to_cpp_image("counts", run_index, self.cpp_counts_image) @@ -684,15 +681,10 @@ def EndOfRunActionMasterThread(self, run_index): # density image if self.user_output.density.get_active(): - if self._density_via_mc: - self.fetch_from_cpp_image("density", run_index, self.cpp_density_image) - self._update_output_coordinate_system("density", run_index) - self.user_output.density.data_per_run[run_index] /= self.user_output.counts.data_per_run[run_index] - else: - edep_image = self.user_output.edep_with_uncertainty.get_data( - run_index, item=0 - ) - self.user_output.density.store_data(run_index, self.create_density_image_from_image_volume(edep_image)) + edep_image = self.user_output.edep_with_uncertainty.get_data( + run_index, item=0 + ) + self.user_output.density.store_data(run_index, self.create_density_image_from_image_volume(edep_image)) self.user_output.density.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) From 3a6606dd377b0ea99da800041fa338d79838b79e Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Tue, 8 Oct 2024 12:39:03 +0200 Subject: [PATCH 153/183] Update test008_dose_actor_2.py --- opengate/tests/src/test008_dose_actor_2.py | 75 +++++++++++----------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/opengate/tests/src/test008_dose_actor_2.py b/opengate/tests/src/test008_dose_actor_2.py index a0b210068..d76870be7 100755 --- a/opengate/tests/src/test008_dose_actor_2.py +++ b/opengate/tests/src/test008_dose_actor_2.py @@ -71,15 +71,17 @@ stats.output_filename = "stats.txt" # dose actor 1: depth edep - doseactor = sim.add_actor("DoseActor", "depth") - doseactor.attached_to = "patient" - doseactor.output_filename = "depth.mhd" - doseactor.spacing = [5 * mm, 5 * mm, 5 * mm] - doseactor.size = [50, 50, 50] - doseactor.output_coordinate_system = "attached_to_image" - doseactor.dose.active = True - doseactor.dose_uncertainty.active = True - doseactor.density.active = True + doseactor1 = sim.add_actor("DoseActor", "depth") + doseactor1.attached_to = "patient" + doseactor1.output_filename = "depth.mhd" + doseactor1.spacing = [5 * mm, 5 * mm, 5 * mm] + doseactor1.size = [50, 50, 50] + doseactor1.output_coordinate_system = "attached_to_image" + doseactor1.dose.active = True + doseactor1.edep_uncertainty.active = True + doseactor1.dose_uncertainty.active = True + doseactor1.dose_uncertainty.write_to_disk = True + doseactor1.density.active = True # run sim.run() @@ -88,43 +90,42 @@ print(stats) is_ok = True - print("\nDifference for EDEP") + print("\nDifference for Dose") is_ok = ( utility.assert_images( paths.output_ref / "depth_dose.mhd", - doseactor.dose.get_output_path(), + doseactor1.dose.get_output_path(), stats, tolerance=25, - ignore_value=0, + ignore_value_data2=0, sum_tolerance=1, ) and is_ok ) - print("\nDifference for uncertainty") - is_ok = ( - utility.assert_images( - paths.output_ref / "depth_dose_uncertainty.mhd", - doseactor.dose_uncertainty.get_output_path(), - stats, - tolerance=5, - ignore_value=1, - sum_tolerance=1, - ) - and is_ok - ) - - """print("\nDifference for density") - is_ok = ( - utility.assert_images( - paths.output_ref / "depth_density.mhd", - doseactor.density.get_output_path(), - stats, - tolerance=5, - ignore_value=1, - sum_tolerance=1, - ) - and is_ok - )""" + # print("\nDifference for dose uncertainty") + # is_ok = ( + # utility.assert_images( + # paths.output_ref / "depth_dose_uncertainty.mhd", + # doseactor.dose_uncertainty.get_output_path(), + # stats, + # tolerance=5, + # ignore_value_data2=0, + # sum_tolerance=1, + # ) + # and is_ok + # ) + + # print("\nDifference for density: calculated via simulation and from the CT image") + # is_ok = ( + # utility.assert_images( + # paths.output_ref / "depth_density.mhd", + # doseactor1.density.get_output_path(), + # stats, + # tolerance=5, + # sum_tolerance=1, + # ) + # and is_ok + # ) utility.test_ok(is_ok) From 6ad0feef320ade004380dcf6737fb52a8c895634 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:55:30 +0000 Subject: [PATCH 154/183] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateDoseActor.cpp | 49 +++++++------ .../opengate_lib/GateDoseActor.h | 8 +-- .../opengate_lib/pyGateDoseActor.cpp | 6 +- opengate/actors/actoroutput.py | 4 +- opengate/actors/doseactors.py | 70 ++++++++++++------- opengate/geometry/materials.py | 2 +- opengate/tests/src/test008_dose_actor.py | 24 +++++-- ...010_generic_source_angular_distribution.py | 28 +++++--- .../src/test010_generic_source_confine.py | 8 ++- opengate/tests/src/test012_mt_dose_actor.py | 10 ++- opengate/tests/src/test015_iec_phantom_2.py | 11 ++- .../tests/src/test015_iec_phantom_3_mt.py | 2 +- .../tests/src/test015_iec_phantom_5_wip.py | 11 ++- opengate/tests/src/test017_repeater.py | 11 ++- opengate/tests/src/test020_profiling.py | 8 ++- opengate/tests/src/test021_voxel_source2.py | 12 +++- opengate/tests/src/test023_filters.py | 18 +++-- .../tests/src/test023_filters_iec_phantom.py | 9 ++- .../tests/src/test023_filters_material.py | 9 ++- .../src/test029_volume_time_rotation_1.py | 13 +++- .../test029_volume_time_rotation_1_process.py | 13 +++- .../src/test029_volume_time_rotation_2.py | 13 +++- opengate/tests/src/test030_dose_motion.py | 21 ++++-- .../src/test030_dose_motion_dynamic_param.py | 22 ++++-- ...est030_dose_motion_dynamic_param_custom.py | 22 ++++-- .../src/test032_create_voxelized_volume.py | 9 ++- opengate/tests/src/test032_voxel_vs_volume.py | 9 ++- .../src/test032_voxelized_volume_source.py | 24 ++++--- .../src/test033_rotation_spect_aa_helpers.py | 24 +++++-- opengate/tests/src/test034_gan_phsp_linac.py | 11 ++- opengate/tests/src/test035_dose_rate.py | 11 ++- .../src/test036_proj_param_2_with_index.py | 13 +++- .../src/test038_gan_phsp_spect_gan_aa.py | 13 ++-- opengate/tests/src/test041_dose_actor.py | 34 ++++++--- .../test041_dose_actor_dose_to_water_mt.py | 10 ++- ...st041_dose_actor_mt_cp_images_n_threads.py | 10 ++- ...ose_actor_mt_standard_error_of_mean_WIP.py | 10 ++- opengate/tests/src/test042_gauss_gps.py | 38 ++++++---- opengate/tests/src/test043_garf.py | 25 +++++-- opengate/tests/src/test043_garf_analog.py | 24 +++++-- opengate/tests/src/test043_garf_mt.py | 25 +++++-- opengate/tests/src/test044_pbs.py | 6 +- opengate/tests/src/test044_pbs_rot_transl.py | 12 +++- .../tests/src/test044_pbs_source_to_volume.py | 6 +- opengate/tests/src/test044_pbs_unfocused.py | 11 ++- .../tests/src/test047_gan_vox_source_cond.py | 12 +++- .../src/test048_split_spect_projections.py | 12 +++- .../tests/src/test050_let_actor_letd_mt.py | 12 +++- .../test059_tpsource_flat_generation_flag.py | 25 ++++--- .../tests/src/test059_tpsource_weights.py | 20 ++++-- .../src/test067_cbct_fluence_actor_mt.py | 4 +- opengate/tests/src/test073_helpers.py | 12 +++- opengate/tests/utility.py | 4 +- 53 files changed, 595 insertions(+), 235 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index 17d52516b..1d991205a 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -103,16 +103,16 @@ void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { size_edep = region.GetSize(); if (fEdepSquaredFlag) { - AttachImageToVolume(cpp_edep_squared_image, fPhysicalVolumeName, - fTranslation); + AttachImageToVolume(cpp_edep_squared_image, + fPhysicalVolumeName, fTranslation); } if (fDoseFlag) { AttachImageToVolume(cpp_dose_image, fPhysicalVolumeName, fTranslation); } if (fDoseSquaredFlag) { - AttachImageToVolume(cpp_dose_squared_image, fPhysicalVolumeName, - fTranslation); + AttachImageToVolume(cpp_dose_squared_image, + fPhysicalVolumeName, fTranslation); } if (fCountsFlag) { AttachImageToVolume(cpp_counts_image, fPhysicalVolumeName, @@ -120,13 +120,14 @@ void GateDoseActor::BeginOfRunActionMasterThread(int run_id) { } } -void GateDoseActor::PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels) { +void GateDoseActor::PrepareLocalDataForRun(threadLocalT &data, + int numberOfVoxels) { data.squared_worker_flatimg.resize(numberOfVoxels); std::fill(data.squared_worker_flatimg.begin(), data.squared_worker_flatimg.end(), 0.0); data.lastid_worker_flatimg.resize(numberOfVoxels); - std::fill(data.lastid_worker_flatimg.begin(), data.lastid_worker_flatimg.end(), - 0); + std::fill(data.lastid_worker_flatimg.begin(), + data.lastid_worker_flatimg.end(), 0); } void GateDoseActor::BeginOfRunAction(const G4Run *run) { @@ -229,18 +230,19 @@ void GateDoseActor::SteppingAction(G4Step *step) { auto event_id = G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID(); if (fEdepSquaredFlag) { -// G4AutoLock mutex(&SetPixelMutex); - ScoreSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image, edep, event_id, index); + // G4AutoLock mutex(&SetPixelMutex); + ScoreSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image, + edep, event_id, index); } if (fDoseSquaredFlag) { -// G4AutoLock mutex(&SetPixelMutex); - ScoreSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image, dose, event_id, index); + // G4AutoLock mutex(&SetPixelMutex); + ScoreSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image, + dose, event_id, index); } } } // else: outside of the image } - double GateDoseActor::ComputeMeanUncertainty() { G4AutoLock mutex(&ComputeUncertaintyMutex); itk::ImageRegionIterator edep_iterator3D( @@ -311,14 +313,19 @@ void GateDoseActor::ind2sub(int index_flat, Image3DType::IndexType &index3D) { void GateDoseActor::EndOfRunAction(const G4Run *run) { if (fEdepSquaredFlag) { - GateDoseActor::FlushSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image); + GateDoseActor::FlushSquaredValue(fThreadLocalDataEdep.Get(), + cpp_edep_squared_image); } if (fDoseSquaredFlag) { - GateDoseActor::FlushSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image); + GateDoseActor::FlushSquaredValue(fThreadLocalDataDose.Get(), + cpp_dose_squared_image); } } -void GateDoseActor::ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, Image3DType::IndexType index) { +void GateDoseActor::ScoreSquaredValue(threadLocalT &data, + Image3DType::Pointer cpp_image, + double value, int event_id, + Image3DType::IndexType index) { G4AutoLock mutex(&SetPixelMutex); int index_flat = sub2ind(index); auto previous_id = data.lastid_worker_flatimg[index_flat]; @@ -337,8 +344,9 @@ void GateDoseActor::ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer c } } -void GateDoseActor::FlushSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image) { -// G4AutoLock mutex(&SetWorkerEndRunMutex); +void GateDoseActor::FlushSquaredValue(threadLocalT &data, + Image3DType::Pointer cpp_image) { + // G4AutoLock mutex(&SetWorkerEndRunMutex); itk::ImageRegionIterator iterator3D( cpp_image, cpp_image->GetLargestPossibleRegion()); @@ -346,14 +354,11 @@ void GateDoseActor::FlushSquaredValue(threadLocalT &data, Image3DType::Pointer c Image3DType::IndexType index_f = iterator3D.GetIndex(); Image3DType::PixelType pixelValue3D = data.squared_worker_flatimg[sub2ind(index_f)]; - ImageAddValue(cpp_image, index_f, - pixelValue3D * pixelValue3D); + ImageAddValue(cpp_image, index_f, pixelValue3D * pixelValue3D); } } -int GateDoseActor::EndOfRunActionMasterThread(int run_id) { - return 0; -} +int GateDoseActor::EndOfRunActionMasterThread(int run_id) { return 0; } double GateDoseActor::GetMaxValueOfImage(Image3DType::Pointer imageP) { itk::ImageRegionIterator iterator3D( diff --git a/core/opengate_core/opengate_lib/GateDoseActor.h b/core/opengate_core/opengate_lib/GateDoseActor.h index d0a0a728f..098ee3816 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.h +++ b/core/opengate_core/opengate_lib/GateDoseActor.h @@ -99,16 +99,16 @@ class GateDoseActor : public GateVActor { // Image3DType::IndexType index3D; // int index_flat; }; -// using ThreadLocalType = struct threadLocalT; + // using ThreadLocalType = struct threadLocalT; - void ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, Image3DType::IndexType index); + void ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, + double value, int event_id, + Image3DType::IndexType index); void FlushSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image); void PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels); - - // // Option: indicate if we must compute uncertainty // bool fUncertaintyFlag; diff --git a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp index 3e3fdc262..7ce44b353 100644 --- a/core/opengate_core/opengate_lib/pyGateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateDoseActor.cpp @@ -51,9 +51,11 @@ void init_GateDoseActor(py::module &m) { .def("SetPhysicalVolumeName", &GateDoseActor::SetPhysicalVolumeName) .def_readwrite("NbOfEvent", &GateDoseActor::NbOfEvent) .def_readwrite("cpp_edep_image", &GateDoseActor::cpp_edep_image) - .def_readwrite("cpp_edep_squared_image", &GateDoseActor::cpp_edep_squared_image) + .def_readwrite("cpp_edep_squared_image", + &GateDoseActor::cpp_edep_squared_image) .def_readwrite("cpp_dose_image", &GateDoseActor::cpp_dose_image) - .def_readwrite("cpp_dose_squared_image", &GateDoseActor::cpp_dose_squared_image) + .def_readwrite("cpp_dose_squared_image", + &GateDoseActor::cpp_dose_squared_image) .def_readwrite("cpp_density_image", &GateDoseActor::cpp_density_image) .def_readwrite("cpp_counts_image", &GateDoseActor::cpp_counts_image) .def_readwrite("fPhysicalVolumeName", diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index 68805af60..ceba7d140 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -504,8 +504,8 @@ def set_active(self, value, item=0): self.data_item_config[i]["active"] = bool(value) def get_active(self, item=0): - if item == 'any': - item = 'all' + if item == "any": + item = "all" items = self._collect_item_identifiers(item) return any([self.data_item_config[k]["active"] is True for k in items]) diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index 219b7d02e..6ffae3ea3 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -515,7 +515,7 @@ def __init__(self, *args, **kwargs): # set the defaults for the user output of this actor self._add_user_output(ActorOutputSingleMeanImage, "density") self._add_user_output(ActorOutputSingleImage, "counts") - self.user_output.dose_with_uncertainty.set_active(False, item='all') + self.user_output.dose_with_uncertainty.set_active(False, item="all") self.user_output.density.set_active(False) self.user_output.counts.set_active(False) @@ -552,8 +552,10 @@ def __initcpp__(self): def create_density_image_from_image_volume(self, deposit_image): if self.attached_to_volume.volume_type != "ImageVolume": - fatal(f"Cannot calculate the density map from the ImageVolume " - f"because this actor is attached to a {self.attached_to_volume.volume_type}. ") + fatal( + f"Cannot calculate the density map from the ImageVolume " + f"because this actor is attached to a {self.attached_to_volume.volume_type}. " + ) density_image = self.attached_to_volume.create_density_image() if images_have_same_domain(deposit_image, density_image) is False: @@ -575,10 +577,10 @@ def initialize(self, *args): # Make sure the squared component (item 1) is active if any of the quantities relying on it are active if ( - self.user_output.edep_with_uncertainty.get_active( - item=("uncertainty", "std", "variance") - ) - is True + self.user_output.edep_with_uncertainty.get_active( + item=("uncertainty", "std", "variance") + ) + is True ): # activate the squared component, but avoid writing it to disk # because the user has not activated it and thus most likely does not want it @@ -590,10 +592,10 @@ def initialize(self, *args): # Make sure the squared component (item 1) is active if any of the quantities relying on it are active if ( - self.user_output.dose_with_uncertainty.get_active( - item=("uncertainty", "std", "variance") - ) - is True + self.user_output.dose_with_uncertainty.get_active( + item=("uncertainty", "std", "variance") + ) + is True ): # activate the squared component, but avoid writing it to disk # because the user has not activated it and thus most likely does not want it @@ -603,15 +605,24 @@ def initialize(self, *args): True, item=1 ) # activate squared component - if self.user_output.density.get_active() is True and self.attached_to_volume.volume_type != 'ImageVolume': - fatal("The dose actor can only produce a density map if it is attached to an ImageVolume. " - f"This actor is attached to a {self.attached_to_volume.volume_type} volume. ") + if ( + self.user_output.density.get_active() is True + and self.attached_to_volume.volume_type != "ImageVolume" + ): + fatal( + "The dose actor can only produce a density map if it is attached to an ImageVolume. " + f"This actor is attached to a {self.attached_to_volume.volume_type} volume. " + ) self.InitializeUserInput(self.user_info) # C++ side # Set the flags on C++ side so the C++ knows which quantities need to be scored - self.SetEdepSquaredFlag(self.user_output.edep_with_uncertainty.get_active(item=1)) + self.SetEdepSquaredFlag( + self.user_output.edep_with_uncertainty.get_active(item=1) + ) self.SetDoseFlag(self.user_output.dose_with_uncertainty.get_active(item=0)) - self.SetDoseSquaredFlag(self.user_output.dose_with_uncertainty.get_active(item=1)) + self.SetDoseSquaredFlag( + self.user_output.dose_with_uncertainty.get_active(item=1) + ) # item=0 is the default self.SetCountsFlag(self.user_output.counts.get_active()) # C++ side has a boolean toWaterFlag and self.score_in == "water" yields True/False @@ -630,9 +641,14 @@ def BeginOfRunActionMasterThread(self, run_index): self.cpp_edep_squared_image, ) - if self.user_output.dose_with_uncertainty.get_active(item='any'): + if self.user_output.dose_with_uncertainty.get_active(item="any"): self.prepare_output_for_run("dose_with_uncertainty", run_index) - self.push_to_cpp_image("dose_with_uncertainty", run_index, self.cpp_dose_image, self.cpp_dose_squared_image) + self.push_to_cpp_image( + "dose_with_uncertainty", + run_index, + self.cpp_dose_image, + self.cpp_dose_squared_image, + ) if self.user_output.counts.get_active(): self.prepare_output_for_run("counts", run_index) @@ -652,7 +668,7 @@ def EndOfRunActionMasterThread(self, run_index): run_index, number_of_samples=self.NbOfEvent ) - if self.user_output.dose_with_uncertainty.get_active(item='any'): + if self.user_output.dose_with_uncertainty.get_active(item="any"): self.fetch_from_cpp_image( "dose_with_uncertainty", run_index, @@ -665,12 +681,16 @@ def EndOfRunActionMasterThread(self, run_index): ) # divide by voxel volume and scale to unit Gy if self.user_output.dose_with_uncertainty.get_active(item=0): - self.user_output.dose_with_uncertainty.data_per_run[run_index].data[0] /= \ - (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) + self.user_output.dose_with_uncertainty.data_per_run[run_index].data[ + 0 + ] /= (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) if self.user_output.dose_with_uncertainty.get_active(item=1): # in the squared component 1, the denominator needs to be squared - self.user_output.dose_with_uncertainty.data_per_run[run_index].data[1] /= ( - (g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2]) ** 2) + self.user_output.dose_with_uncertainty.data_per_run[run_index].data[ + 1 + ] /= ( + g4_units.Gy * self.spacing[0] * self.spacing[1] * self.spacing[2] + ) ** 2 if self.user_output.counts.get_active(): self.fetch_from_cpp_image("counts", run_index, self.cpp_counts_image) @@ -684,7 +704,9 @@ def EndOfRunActionMasterThread(self, run_index): edep_image = self.user_output.edep_with_uncertainty.get_data( run_index, item=0 ) - self.user_output.density.store_data(run_index, self.create_density_image_from_image_volume(edep_image)) + self.user_output.density.store_data( + run_index, self.create_density_image_from_image_volume(edep_image) + ) self.user_output.density.store_meta_data( run_index, number_of_samples=self.NbOfEvent ) diff --git a/opengate/geometry/materials.py b/opengate/geometry/materials.py index 9a8b5a226..cd4e3871f 100644 --- a/opengate/geometry/materials.py +++ b/opengate/geometry/materials.py @@ -312,7 +312,7 @@ def create_density_img(img_volume, material_database): for hu0, hu1, mat_name in img_volume.voxel_materials: arho[(act >= hu0) * (act < hu1)] = material_database[mat_name].GetDensity() - arho *= (g4_units.cm3 / g4_units.g) + arho *= g4_units.cm3 / g4_units.g rho = itk.GetImageFromArray(arho) rho.CopyInformation(img_volume.itk_image) diff --git a/opengate/tests/src/test008_dose_actor.py b/opengate/tests/src/test008_dose_actor.py index a6e6dfe02..cc8f6d161 100755 --- a/opengate/tests/src/test008_dose_actor.py +++ b/opengate/tests/src/test008_dose_actor.py @@ -99,16 +99,28 @@ print("\nDifference for EDEP") is_ok = ( - utility.assert_images(ref_path / "output-Edep.mhd", dose.edep.get_output_path(), stat, tolerance=13, - ignore_value_data2=0, sum_tolerance=1.5) - and is_ok + utility.assert_images( + ref_path / "output-Edep.mhd", + dose.edep.get_output_path(), + stat, + tolerance=13, + ignore_value_data2=0, + sum_tolerance=1.5, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(ref_path / "output-Edep-Uncertainty.mhd", dose.edep_uncertainty.get_output_path(), - stat, tolerance=30, ignore_value_data2=0, sum_tolerance=3) - and is_ok + utility.assert_images( + ref_path / "output-Edep-Uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), + stat, + tolerance=30, + ignore_value_data2=0, + sum_tolerance=3, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source_angular_distribution.py b/opengate/tests/src/test010_generic_source_angular_distribution.py index 5a6e16e5c..01ba93f9a 100755 --- a/opengate/tests/src/test010_generic_source_angular_distribution.py +++ b/opengate/tests/src/test010_generic_source_angular_distribution.py @@ -94,19 +94,29 @@ print("\nDifference for EDEP") is_ok = ( - utility.assert_images(ref_path / "test010-generic_source_angular_distribution_edep_ref.mhd", - dose.edep.get_output_path(), stat, tolerance=13, ignore_value_data2=0, - sum_tolerance=1) - and is_ok + utility.assert_images( + ref_path / "test010-generic_source_angular_distribution_edep_ref.mhd", + dose.edep.get_output_path(), + stat, + tolerance=13, + ignore_value_data2=0, + sum_tolerance=1, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(ref_path - / "test010-generic_source_angular_distribution_edep_uncertainty_ref.mhd", - dose.edep_uncertainty.get_output_path(), stat, tolerance=30, ignore_value_data2=0, - sum_tolerance=1) - and is_ok + utility.assert_images( + ref_path + / "test010-generic_source_angular_distribution_edep_uncertainty_ref.mhd", + dose.edep_uncertainty.get_output_path(), + stat, + tolerance=30, + ignore_value_data2=0, + sum_tolerance=1, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source_confine.py b/opengate/tests/src/test010_generic_source_confine.py index 641974d91..489dbc480 100755 --- a/opengate/tests/src/test010_generic_source_confine.py +++ b/opengate/tests/src/test010_generic_source_confine.py @@ -122,7 +122,11 @@ # tests stats_ref = utility.read_stat_file(paths.output_ref / "test010_confine_stats.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.10) - is_ok = is_ok and utility.assert_images(paths.output_ref / "test010-2-edep.mhd", dose_actor.edep.get_output_path(), - stats, tolerance=59) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test010-2-edep.mhd", + dose_actor.edep.get_output_path(), + stats, + tolerance=59, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test012_mt_dose_actor.py b/opengate/tests/src/test012_mt_dose_actor.py index 78a28ac8b..c904ab688 100755 --- a/opengate/tests/src/test012_mt_dose_actor.py +++ b/opengate/tests/src/test012_mt_dose_actor.py @@ -103,8 +103,12 @@ is_ok = utility.assert_stats(stat, stats_ref, 0.10) is_ok = ( - utility.assert_images(paths.gate_output / "output-Edep.mhd", dose_actor.edep.get_output_path(), stat, - tolerance=45) - and is_ok + utility.assert_images( + paths.gate_output / "output-Edep.mhd", + dose_actor.edep.get_output_path(), + stat, + tolerance=45, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_2.py b/opengate/tests/src/test015_iec_phantom_2.py index 8b1639d56..e33cfc158 100755 --- a/opengate/tests/src/test015_iec_phantom_2.py +++ b/opengate/tests/src/test015_iec_phantom_2.py @@ -72,8 +72,15 @@ is_ok = utility.assert_stats(stats_actor, stats_ref, tolerance=0.03) # compare images - im_ok = utility.assert_images(paths.output_ref / "test015_iec_2.mhd", dose.edep.get_output_path(), stats_actor, - tolerance=40, ignore_value_data2=0, axis="x", sum_tolerance=2) + im_ok = utility.assert_images( + paths.output_ref / "test015_iec_2.mhd", + dose.edep.get_output_path(), + stats_actor, + tolerance=40, + ignore_value_data2=0, + axis="x", + sum_tolerance=2, + ) is_ok = is_ok and im_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_3_mt.py b/opengate/tests/src/test015_iec_phantom_3_mt.py index ad66251d7..a1b1b4965 100755 --- a/opengate/tests/src/test015_iec_phantom_3_mt.py +++ b/opengate/tests/src/test015_iec_phantom_3_mt.py @@ -82,7 +82,7 @@ ignore_value_data2=0, axis="y", sum_tolerance=0.5, - sad_profile_tolerance=2 + sad_profile_tolerance=2, ) is_ok = is_ok and im_ok diff --git a/opengate/tests/src/test015_iec_phantom_5_wip.py b/opengate/tests/src/test015_iec_phantom_5_wip.py index 55cdb3b02..e24bc89d1 100755 --- a/opengate/tests/src/test015_iec_phantom_5_wip.py +++ b/opengate/tests/src/test015_iec_phantom_5_wip.py @@ -79,8 +79,15 @@ # compare images f = paths.output / "test015_iec_2.mhd" - im_ok = utility.assert_images(paths.output_ref / "test015_iec_2.mhd", dose.output, stats, tolerance=40, - ignore_value_data2=0, axis="x", sum_tolerance=2) + im_ok = utility.assert_images( + paths.output_ref / "test015_iec_2.mhd", + dose.output, + stats, + tolerance=40, + ignore_value_data2=0, + axis="x", + sum_tolerance=2, + ) is_ok = is_ok and im_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test017_repeater.py b/opengate/tests/src/test017_repeater.py index 59a8ec250..01c30aa49 100755 --- a/opengate/tests/src/test017_repeater.py +++ b/opengate/tests/src/test017_repeater.py @@ -101,9 +101,14 @@ stats_ref = utility.read_stat_file(paths.output_ref / "test017-stats-ref.txt") is_ok = utility.assert_stats(stats, stats_ref, 0.04) is_ok = ( - utility.assert_images(paths.output_ref / "test017-edep-ref.mhd", dose.edep.get_output_path(), stats, - tolerance=70, sum_tolerance=6) - and is_ok + utility.assert_images( + paths.output_ref / "test017-edep-ref.mhd", + dose.edep.get_output_path(), + stats, + tolerance=70, + sum_tolerance=6, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test020_profiling.py b/opengate/tests/src/test020_profiling.py index af4b5e544..8574b60fa 100755 --- a/opengate/tests/src/test020_profiling.py +++ b/opengate/tests/src/test020_profiling.py @@ -100,6 +100,10 @@ stats_ref = utility.read_stat_file(paths.gate / "output" / "stat_profiling.txt") stats_ref.counts.runs = sim.number_of_threads is_ok = utility.assert_stats(stats, stats_ref, 0.1) - is_ok = is_ok and utility.assert_images(paths.gate / "output" / "output_profiling-Edep.mhd", - dose.edep.get_output_path(), stats, tolerance=79) + is_ok = is_ok and utility.assert_images( + paths.gate / "output" / "output_profiling-Edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=79, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test021_voxel_source2.py b/opengate/tests/src/test021_voxel_source2.py index f6bb050a9..aabc3131c 100755 --- a/opengate/tests/src/test021_voxel_source2.py +++ b/opengate/tests/src/test021_voxel_source2.py @@ -147,9 +147,15 @@ is_ok = utility.assert_stats(stats, stats_ref, 0.1) and is_ok is_ok = ( - utility.assert_images(paths.output_ref / "test021-edep_2.mhd", dose.edep.get_output_path(), stats, - tolerance=11, ignore_value_data2=0, axis="y") - and is_ok + utility.assert_images( + paths.output_ref / "test021-edep_2.mhd", + dose.edep.get_output_path(), + stats, + tolerance=11, + ignore_value_data2=0, + axis="y", + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters.py b/opengate/tests/src/test023_filters.py index fd4e3cb84..62b8e8cd8 100755 --- a/opengate/tests/src/test023_filters.py +++ b/opengate/tests/src/test023_filters.py @@ -101,11 +101,21 @@ is_ok = utility.assert_stats(stat, stats_ref, 0.8) print() - is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-edep.mhd", dose1.edep.get_output_path(), stat, - tolerance=50, sum_tolerance=4) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test023-edep.mhd", + dose1.edep.get_output_path(), + stat, + tolerance=50, + sum_tolerance=4, + ) print() - is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-noe-edep.mhd", dose2.edep.get_output_path(), - stat, tolerance=40, sum_tolerance=2) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test023-noe-edep.mhd", + dose2.edep.get_output_path(), + stat, + tolerance=40, + sum_tolerance=2, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_iec_phantom.py b/opengate/tests/src/test023_filters_iec_phantom.py index 9a6dc20fc..9d4298d46 100755 --- a/opengate/tests/src/test023_filters_iec_phantom.py +++ b/opengate/tests/src/test023_filters_iec_phantom.py @@ -81,7 +81,12 @@ f = paths.output_ref / "test023_stats_iec_phantom.txt" stats_ref = utility.read_stat_file(f) is_ok = utility.assert_stats(stat, stats_ref, 0.12) - is_ok = is_ok and utility.assert_images(paths.output_ref / "test023_iec_phantom.mhd", dose.edep.get_output_path(), - stat, tolerance=102, sum_tolerance=28) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test023_iec_phantom.mhd", + dose.edep.get_output_path(), + stat, + tolerance=102, + sum_tolerance=28, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_material.py b/opengate/tests/src/test023_filters_material.py index 4863d4316..fbd427acd 100755 --- a/opengate/tests/src/test023_filters_material.py +++ b/opengate/tests/src/test023_filters_material.py @@ -99,7 +99,12 @@ stats_ref = utility.read_stat_file(f2) is_ok = utility.assert_stats(stat2, stats_ref, 0.07) and is_ok - is_ok = is_ok and utility.assert_images(paths.output_ref / "test023-edep.mhd", dose.edep.get_output_path(), stat, - tolerance=50, sum_tolerance=3) + is_ok = is_ok and utility.assert_images( + paths.output_ref / "test023-edep.mhd", + dose.edep.get_output_path(), + stat, + tolerance=50, + sum_tolerance=3, + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_1.py b/opengate/tests/src/test029_volume_time_rotation_1.py index 80bdce7f8..c4bad7e28 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1.py +++ b/opengate/tests/src/test029_volume_time_rotation_1.py @@ -47,9 +47,16 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images(paths.output_ref / "proj029.mhd", proj_actor.get_output_path(), stats, tolerance=59, - ignore_value_data2=0, axis="x", sum_tolerance=2) - and is_ok + utility.assert_images( + paths.output_ref / "proj029.mhd", + proj_actor.get_output_path(), + stats, + tolerance=59, + ignore_value_data2=0, + axis="x", + sum_tolerance=2, + ) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_1_process.py b/opengate/tests/src/test029_volume_time_rotation_1_process.py index cc1012b94..39d9b04f6 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1_process.py +++ b/opengate/tests/src/test029_volume_time_rotation_1_process.py @@ -37,9 +37,16 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images(paths.output_ref / "proj029.mhd", proj_actor.get_output_path(), stats, tolerance=59, - ignore_value_data2=0, axis="x", sum_tolerance=2) - and is_ok + utility.assert_images( + paths.output_ref / "proj029.mhd", + proj_actor.get_output_path(), + stats, + tolerance=59, + ignore_value_data2=0, + axis="x", + sum_tolerance=2, + ) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test029_volume_time_rotation_2.py b/opengate/tests/src/test029_volume_time_rotation_2.py index 38c48d1a5..03d9b93e8 100755 --- a/opengate/tests/src/test029_volume_time_rotation_2.py +++ b/opengate/tests/src/test029_volume_time_rotation_2.py @@ -47,9 +47,16 @@ gate.exception.warning("Compare images") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images(paths.output_ref / "proj029_scaled.mhd", proj_actor.get_output_path(), stats, - tolerance=60, ignore_value_data2=0, axis="x", sum_tolerance=6) - and is_ok + utility.assert_images( + paths.output_ref / "proj029_scaled.mhd", + proj_actor.get_output_path(), + stats, + tolerance=60, + ignore_value_data2=0, + axis="x", + sum_tolerance=6, + ) + and is_ok ) print(is_ok) diff --git a/opengate/tests/src/test030_dose_motion.py b/opengate/tests/src/test030_dose_motion.py index 71f47a51e..4316e5dcb 100755 --- a/opengate/tests/src/test030_dose_motion.py +++ b/opengate/tests/src/test030_dose_motion.py @@ -112,16 +112,25 @@ gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, - tolerance=30, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=30, + ignore_value_data2=0, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), stats, tolerance=15, - ignore_value_data2=0) + utility.assert_images( + paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), + stats, + tolerance=15, + ignore_value_data2=0, + ) ) and is_ok utility.test_ok(is_ok) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param.py b/opengate/tests/src/test030_dose_motion_dynamic_param.py index fb84bcaec..f3fcda9a7 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param.py @@ -108,16 +108,26 @@ print() gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, - tolerance=30, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=30, + ignore_value_data2=0, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), stats, tolerance=15, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), + stats, + tolerance=15, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py index 87afb55eb..acce64388 100755 --- a/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py +++ b/opengate/tests/src/test030_dose_motion_dynamic_param_custom.py @@ -185,16 +185,26 @@ def apply_change(self, run_id): print() gate.exception.warning("Difference for EDEP") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep.mhd", dose.edep.get_output_path(), stats, - tolerance=30, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=30, + ignore_value_data2=0, + ) + and is_ok ) print("\nDifference for uncertainty") is_ok = ( - utility.assert_images(paths.output_ref / "test030-edep_uncertainty.mhd", - dose.edep_uncertainty.get_output_path(), stats, tolerance=15, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "test030-edep_uncertainty.mhd", + dose.edep_uncertainty.get_output_path(), + stats, + tolerance=15, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_create_voxelized_volume.py b/opengate/tests/src/test032_create_voxelized_volume.py index 6dced7fad..47b74ec35 100755 --- a/opengate/tests/src/test032_create_voxelized_volume.py +++ b/opengate/tests/src/test032_create_voxelized_volume.py @@ -64,8 +64,13 @@ # compare images gate.exception.warning("\nDifference with ref image") is_ok = ( - utility.assert_images(paths.output_ref / "test032_iec.mhd", path_to_image_1mm, stats=None, tolerance=0.01) - and is_ok + utility.assert_images( + paths.output_ref / "test032_iec.mhd", + path_to_image_1mm, + stats=None, + tolerance=0.01, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_voxel_vs_volume.py b/opengate/tests/src/test032_voxel_vs_volume.py index facd4fb2b..7e1f55af1 100755 --- a/opengate/tests/src/test032_voxel_vs_volume.py +++ b/opengate/tests/src/test032_voxel_vs_volume.py @@ -141,7 +141,12 @@ dose2 = sim.get_actor("dose2") # compare edep map - is_ok = utility.assert_images(dose1.edep.get_output_path(), dose2.edep.get_output_path(), stats, tolerance=87, - axis="x") + is_ok = utility.assert_images( + dose1.edep.get_output_path(), + dose2.edep.get_output_path(), + stats, + tolerance=87, + axis="x", + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test032_voxelized_volume_source.py b/opengate/tests/src/test032_voxelized_volume_source.py index 4f7c0518f..292b6c8d3 100755 --- a/opengate/tests/src/test032_voxelized_volume_source.py +++ b/opengate/tests/src/test032_voxelized_volume_source.py @@ -49,19 +49,27 @@ # compare images gate.exception.warning("\nDifference with ref image") is_ok = ( - utility.assert_images(paths.output_ref / "iec_10mm.mhd", f1, stats=None, tolerance=0.001) - and is_ok + utility.assert_images( + paths.output_ref / "iec_10mm.mhd", f1, stats=None, tolerance=0.001 + ) + and is_ok ) is_ok = ( - utility.assert_images(paths.output_ref / "iec_source_10mm.mhd", f2, stats=None, tolerance=0.001) - and is_ok + utility.assert_images( + paths.output_ref / "iec_source_10mm.mhd", f2, stats=None, tolerance=0.001 + ) + and is_ok ) is_ok = ( - utility.assert_images(paths.output_ref / "iec_9mm.mhd", f3, stats=None, tolerance=0.001) - and is_ok + utility.assert_images( + paths.output_ref / "iec_9mm.mhd", f3, stats=None, tolerance=0.001 + ) + and is_ok ) is_ok = ( - utility.assert_images(paths.output_ref / "iec_source_9mm.mhd", f4, stats=None, tolerance=0.001) - and is_ok + utility.assert_images( + paths.output_ref / "iec_source_9mm.mhd", f4, stats=None, tolerance=0.001 + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index 29a644a17..582aa340a 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -180,14 +180,26 @@ def evaluate_test(sim, sources, itol, ref_skipped): # compare edep map gate.exception.warning(f"Check images") is_ok = ( - utility.assert_images(paths.output_ref / "test033_proj_1.mhd", paths.output / "test033_proj_1.mhd", stats, - tolerance=68, axis="x", sum_tolerance=itol) - and is_ok + utility.assert_images( + paths.output_ref / "test033_proj_1.mhd", + paths.output / "test033_proj_1.mhd", + stats, + tolerance=68, + axis="x", + sum_tolerance=itol, + ) + and is_ok ) is_ok = ( - utility.assert_images(paths.output_ref / "test033_proj_2.mhd", paths.output / "test033_proj_2.mhd", stats, - tolerance=68, axis="x", sum_tolerance=itol) - and is_ok + utility.assert_images( + paths.output_ref / "test033_proj_2.mhd", + paths.output / "test033_proj_2.mhd", + stats, + tolerance=68, + axis="x", + sum_tolerance=itol, + ) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test034_gan_phsp_linac.py b/opengate/tests/src/test034_gan_phsp_linac.py index c17240b69..fde867d1a 100755 --- a/opengate/tests/src/test034_gan_phsp_linac.py +++ b/opengate/tests/src/test034_gan_phsp_linac.py @@ -119,9 +119,14 @@ gate.exception.warning(f"Check dose") is_ok = ( - utility.assert_images(paths.gate / "dose-Edep.mhd", dose.edep.get_output_path(), stats, tolerance=58, - ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate / "dose-Edep.mhd", + dose.edep.get_output_path(), + stats, + tolerance=58, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test035_dose_rate.py b/opengate/tests/src/test035_dose_rate.py index 8be62b0cf..dc3aea578 100755 --- a/opengate/tests/src/test035_dose_rate.py +++ b/opengate/tests/src/test035_dose_rate.py @@ -58,9 +58,14 @@ h = sim.get_actor("dose") print(h) is_ok = ( - utility.assert_images(paths.output_ref / "edep.mhd", h.edep.get_output_path(), stats, tolerance=15, - ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.output_ref / "edep.mhd", + h.edep.get_output_path(), + stats, + tolerance=15, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test036_proj_param_2_with_index.py b/opengate/tests/src/test036_proj_param_2_with_index.py index 8a1ebdab9..64e5b8dab 100755 --- a/opengate/tests/src/test036_proj_param_2_with_index.py +++ b/opengate/tests/src/test036_proj_param_2_with_index.py @@ -31,8 +31,15 @@ # test the output stats = sim.get_actor("Stats") - is_ok = utility.assert_images(paths.output_ref / "proj1.mha", proj.get_output_path(), stats, tolerance=38, - ignore_value_data2=0, axis="y", fig_name=paths.output / f"proj_index.png", - sum_tolerance=1.5) + is_ok = utility.assert_images( + paths.output_ref / "proj1.mha", + proj.get_output_path(), + stats, + tolerance=38, + ignore_value_data2=0, + axis="y", + fig_name=paths.output / f"proj_index.png", + sum_tolerance=1.5, + ) utility.print_test(is_ok, f"Compare image proj:") utility.test_ok(is_ok) diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py index e641f9436..9185fe78c 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py @@ -64,10 +64,15 @@ # image is_ok = ( - utility.assert_images(paths.output_ref / "test038_gan_aa_proj.mhd", - paths.output / "test038_gan_aa_proj.mhd", stats, tolerance=70, axis="x", - sum_tolerance=2.75) - and is_ok + utility.assert_images( + paths.output_ref / "test038_gan_aa_proj.mhd", + paths.output / "test038_gan_aa_proj.mhd", + stats, + tolerance=70, + axis="x", + sum_tolerance=2.75, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test041_dose_actor.py b/opengate/tests/src/test041_dose_actor.py index 655ec84a1..8c0c048f2 100755 --- a/opengate/tests/src/test041_dose_actor.py +++ b/opengate/tests/src/test041_dose_actor.py @@ -102,24 +102,38 @@ gate.exception.warning("\nDifference for EDEP") is_ok = ( - utility.assert_images(paths.gate_output / "output2-Edep.mhd", dose_actor.edep.get_output_path(), stats, - tolerance=10, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate_output / "output2-Edep.mhd", + dose_actor.edep.get_output_path(), + stats, + tolerance=10, + ignore_value_data2=0, + ) + and is_ok ) gate.exception.warning("\nDifference for uncertainty") is_ok = ( - utility.assert_images(paths.gate_output / "output2-Edep-Uncertainty.mhd", - dose_actor.edep_uncertainty.get_output_path(), stats, tolerance=30, - ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate_output / "output2-Edep-Uncertainty.mhd", + dose_actor.edep_uncertainty.get_output_path(), + stats, + tolerance=30, + ignore_value_data2=0, + ) + and is_ok ) gate.exception.warning("\nDifference for dose in Gray") is_ok = ( - utility.assert_images(paths.gate_output / "output2-Dose.mhd", dose_actor.dose.get_output_path(), stats, - tolerance=10, ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate_output / "output2-Dose.mhd", + dose_actor.dose.get_output_path(), + stats, + tolerance=10, + ignore_value_data2=0, + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py index 52a545618..0587b3fd6 100755 --- a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py +++ b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py @@ -150,8 +150,14 @@ # so we do not need to manually keep track of the paths here in the script # syntax: dose_actor.dose.get_output_path() - unused = utility.assert_images(dose_actor_IDD_d.dose.get_output_path(), dose_actor_IDD_d2w.dose.get_output_path(), - stats, tolerance=100, ignore_value_data2=0, axis="x") + unused = utility.assert_images( + dose_actor_IDD_d.dose.get_output_path(), + dose_actor_IDD_d2w.dose.get_output_path(), + stats, + tolerance=100, + ignore_value_data2=0, + axis="x", + ) mSPR_40MeV = 1.268771331 # from PSTAR NIST tables, Feb 2023 mSPR_80MeV = 1.253197674 # from PSTAR NIST tables, Feb 2023 diff --git a/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py b/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py index be14e3015..d4a9ea6d4 100755 --- a/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py +++ b/opengate/tests/src/test041_dose_actor_mt_cp_images_n_threads.py @@ -107,8 +107,14 @@ def run_sim(N_events: int, N_threads: int, N_voxels: int, paths): def run_test(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stat): - unused = utility.assert_images(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stat, tolerance=100, - ignore_value_data2=0, axis="x") + unused = utility.assert_images( + doseFpath_IDD_singleImage, + doseFpath_IDD_NthreadImages, + stat, + tolerance=100, + ignore_value_data2=0, + axis="x", + ) expected_ratio = 1.00 gate.exception.warning("Test ratio: edep in single image vs. Nthread image") is_ok = utility.assert_images_ratio( diff --git a/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py b/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py index 664cda904..a7be94324 100755 --- a/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py +++ b/opengate/tests/src/test041_dose_actor_mt_standard_error_of_mean_WIP.py @@ -127,8 +127,14 @@ def run_sim(n_thr, c4_ref=None, paths=None): sim.get_actor(doseActorName_IDD_singleImage).get_output_path("edep_uncertainty") ) - unused = utility.assert_images(doseFpath_IDD_singleImage, doseFpath_IDD_NthreadImages, stats, tolerance=100, - ignore_value_data2=0, axis="x") + unused = utility.assert_images( + doseFpath_IDD_singleImage, + doseFpath_IDD_NthreadImages, + stats, + tolerance=100, + ignore_value_data2=0, + axis="x", + ) expected_ratio = 1.00 gate.exception.warning("Test ratio: dose / dose MT cp image for each trhead") is_ok = utility.assert_images_ratio( diff --git a/opengate/tests/src/test042_gauss_gps.py b/opengate/tests/src/test042_gauss_gps.py index 35d15c152..eb975732a 100755 --- a/opengate/tests/src/test042_gauss_gps.py +++ b/opengate/tests/src/test042_gauss_gps.py @@ -124,28 +124,42 @@ print() gate.exception.warning("Difference for EDEP XZ") is_ok = ( - utility.assert_images(paths.gate_output / "lateral_xz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInXZ").get_output_path("edep"), stats, tolerance=10, - ignore_value_data2=0) - and is_ok + utility.assert_images( + paths.gate_output / "lateral_xz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInXZ").get_output_path("edep"), + stats, + tolerance=10, + ignore_value_data2=0, + ) + and is_ok ) print() gate.exception.warning("Difference for EDEP XY") is_ok = ( - utility.assert_images(paths.gate_output / "lateral_xy_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInXY").get_output_path("edep"), stats, tolerance=10, - ignore_value_data2=0, axis="y") - and is_ok + utility.assert_images( + paths.gate_output / "lateral_xy_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInXY").get_output_path("edep"), + stats, + tolerance=10, + ignore_value_data2=0, + axis="y", + ) + and is_ok ) print() gate.exception.warning("Difference for EDEP YZ") is_ok = ( - utility.assert_images(paths.gate_output / "lateral_yz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", - paths.output / sim.get_actor("doseInYZ").get_output_path("edep"), stats, tolerance=30, - ignore_value_data2=0, axis="y") - and is_ok + utility.assert_images( + paths.gate_output / "lateral_yz_Protons_40MeV_sourceShapeGaussian-Edep.mhd", + paths.output / sim.get_actor("doseInYZ").get_output_path("edep"), + stats, + tolerance=30, + ignore_value_data2=0, + axis="y", + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test043_garf.py b/opengate/tests/src/test043_garf.py index e920fb817..48bc5a233 100755 --- a/opengate/tests/src/test043_garf.py +++ b/opengate/tests/src/test043_garf.py @@ -127,17 +127,30 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", filename1, stat, - tolerance=100, ignore_value_data2=0, axis="x", sum_tolerance=20) - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog.mhd", + filename1, + stat, + tolerance=100, + ignore_value_data2=0, + axis="x", + sum_tolerance=20, + ) + and is_ok ) print() gate.exception.warning("Compare image to analog high statistics") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", filename2, stat, - tolerance=52, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", + filename2, + stat, + tolerance=52, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) print() diff --git a/opengate/tests/src/test043_garf_analog.py b/opengate/tests/src/test043_garf_analog.py index d1563e794..a068a705f 100755 --- a/opengate/tests/src/test043_garf_analog.py +++ b/opengate/tests/src/test043_garf_analog.py @@ -85,15 +85,27 @@ print() gate.exception.warning("Tests projection (old gate)") is_ok = ( - utility.assert_images(test43.paths.gate_output / "projection_analog.mhd", fn, stats, tolerance=75, - ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + test43.paths.gate_output / "projection_analog.mhd", + fn, + stats, + tolerance=75, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) print() gate.exception.warning("Tests projection (new)") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", proj.get_output_path(), - stats, tolerance=80, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog.mhd", + proj.get_output_path(), + stats, + tolerance=80, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) diff --git a/opengate/tests/src/test043_garf_mt.py b/opengate/tests/src/test043_garf_mt.py index 9c2b02eae..f1f4f9f00 100755 --- a/opengate/tests/src/test043_garf_mt.py +++ b/opengate/tests/src/test043_garf_mt.py @@ -115,17 +115,30 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog.mhd", filename1, stats, - tolerance=100, ignore_value_data2=0, axis="x", sum_tolerance=20) - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog.mhd", + filename1, + stats, + tolerance=100, + ignore_value_data2=0, + axis="x", + sum_tolerance=20, + ) + and is_ok ) print() gate.exception.warning("Compare image to analog high statistics") is_ok = ( - utility.assert_images(test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", filename2, stats, - tolerance=52, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + test43.paths.output_ref / "test043_projection_analog_high_stat.mhd", + filename2, + stats, + tolerance=52, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) print() diff --git a/opengate/tests/src/test044_pbs.py b/opengate/tests/src/test044_pbs.py index f662a06d0..785411e1b 100755 --- a/opengate/tests/src/test044_pbs.py +++ b/opengate/tests/src/test044_pbs.py @@ -159,8 +159,10 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images(ref_path / mhd_ref, mhd_gate, stats, tolerance=50, ignore_value_data2=0) - and is_ok + utility.assert_images( + ref_path / mhd_ref, mhd_gate, stats, tolerance=50, ignore_value_data2=0 + ) + and is_ok ) """EdepColorMap = utility.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = 'Plane_'+str(i)+'ColorMap.png' diff --git a/opengate/tests/src/test044_pbs_rot_transl.py b/opengate/tests/src/test044_pbs_rot_transl.py index f516800eb..2bb616d3d 100755 --- a/opengate/tests/src/test044_pbs_rot_transl.py +++ b/opengate/tests/src/test044_pbs_rot_transl.py @@ -170,9 +170,15 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).edep.get_output_path() mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images(ref_path / mhd_ref, output_path / mhd_gate, stat, tolerance=50, - ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + ref_path / mhd_ref, + output_path / mhd_gate, + stat, + tolerance=50, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) """ diff --git a/opengate/tests/src/test044_pbs_source_to_volume.py b/opengate/tests/src/test044_pbs_source_to_volume.py index e18a5c283..ed953d1d3 100755 --- a/opengate/tests/src/test044_pbs_source_to_volume.py +++ b/opengate/tests/src/test044_pbs_source_to_volume.py @@ -162,8 +162,10 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images(ref_path / mhd_ref, mhd_gate, stat, tolerance=50, ignore_value_data2=0) - and is_ok + utility.assert_images( + ref_path / mhd_ref, mhd_gate, stat, tolerance=50, ignore_value_data2=0 + ) + and is_ok ) """EdepColorMap = utility.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = "Plane_" + str(i) + "ColorMap.png" diff --git a/opengate/tests/src/test044_pbs_unfocused.py b/opengate/tests/src/test044_pbs_unfocused.py index ee58e3d0e..33077ca6c 100755 --- a/opengate/tests/src/test044_pbs_unfocused.py +++ b/opengate/tests/src/test044_pbs_unfocused.py @@ -172,9 +172,14 @@ mhd_gate = sim.get_actor("doseInYZ" + str(i)).get_output_path("edep") mhd_ref = "plane" + str(i) + "a_" + folder + "-Edep.mhd" is_ok = ( - utility.assert_images(ref_path / mhd_ref, output_path / mhd_gate, stats, tolerance=50, - ignore_value_data2=0) - and is_ok + utility.assert_images( + ref_path / mhd_ref, + output_path / mhd_gate, + stats, + tolerance=50, + ignore_value_data2=0, + ) + and is_ok ) """EdepColorMap = utlity.create_2D_Edep_colorMap(output_path / mhd_gate) img_name = 'Plane_'+str(i)+'ColorMap.png' diff --git a/opengate/tests/src/test047_gan_vox_source_cond.py b/opengate/tests/src/test047_gan_vox_source_cond.py index c6c49eb18..9a71075e3 100755 --- a/opengate/tests/src/test047_gan_vox_source_cond.py +++ b/opengate/tests/src/test047_gan_vox_source_cond.py @@ -140,9 +140,15 @@ print() gate.exception.warning("Compare image to analog") is_ok = ( - utility.assert_images(paths.output_ref / "test047-edep.mhd", dose.get_output_path("edep"), stats, - tolerance=19, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + paths.output_ref / "test047-edep.mhd", + dose.get_output_path("edep"), + stats, + tolerance=19, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) print("Test with vv: ") diff --git a/opengate/tests/src/test048_split_spect_projections.py b/opengate/tests/src/test048_split_spect_projections.py index c496523fe..60e23132f 100755 --- a/opengate/tests/src/test048_split_spect_projections.py +++ b/opengate/tests/src/test048_split_spect_projections.py @@ -40,9 +40,15 @@ is_ok = True for i in range(nb_ene): is_ok = ( - utility.assert_images(paths.output_ref / f"t048_projection_{i}.mhd", output_filenames[i], None, - tolerance=1e-6, ignore_value_data2=0, axis="x") - and is_ok + utility.assert_images( + paths.output_ref / f"t048_projection_{i}.mhd", + output_filenames[i], + None, + tolerance=1e-6, + ignore_value_data2=0, + axis="x", + ) + and is_ok ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test050_let_actor_letd_mt.py b/opengate/tests/src/test050_let_actor_letd_mt.py index 20bde9437..9302e1265 100755 --- a/opengate/tests/src/test050_let_actor_letd_mt.py +++ b/opengate/tests/src/test050_let_actor_letd_mt.py @@ -165,9 +165,15 @@ fNameIDD = doseIDD.edep.output_filename if do_debug: - is_ok = utility.assert_images(ref_path / fNameIDD, doseIDD.edep.get_output_path(), stats, tolerance=100, - ignore_value_data2=0, axis="x", - scaleImageValuesFactor=numPartSimRef / numPartSimTest) + is_ok = utility.assert_images( + ref_path / fNameIDD, + doseIDD.edep.get_output_path(), + stats, + tolerance=100, + ignore_value_data2=0, + axis="x", + scaleImageValuesFactor=numPartSimRef / numPartSimTest, + ) tests_pass = [] is_ok = utility.assert_filtered_imagesprofile1D( diff --git a/opengate/tests/src/test059_tpsource_flat_generation_flag.py b/opengate/tests/src/test059_tpsource_flat_generation_flag.py index 2dd2ac789..8cbd3b479 100755 --- a/opengate/tests/src/test059_tpsource_flat_generation_flag.py +++ b/opengate/tests/src/test059_tpsource_flat_generation_flag.py @@ -239,18 +239,27 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): # check that the dose output is the same print("--- Dose image spot 0 ---") test = ( - utility.assert_images(output_path / dose_actors[0].get_output_path("edep"), - output_path / dose_actors[2].get_output_path("edep"), stats, tolerance=70, - ignore_value_data2=0) - and test + utility.assert_images( + output_path / dose_actors[0].get_output_path("edep"), + output_path / dose_actors[2].get_output_path("edep"), + stats, + tolerance=70, + ignore_value_data2=0, + ) + and test ) print("--- Dose image spot 1 ---") test = ( - utility.assert_images(output_path / dose_actors[1].get_output_path("edep"), - output_path / dose_actors[3].get_output_path("edep"), stats, tolerance=70, - ignore_value_data2=0, sum_tolerance=5.2) - and test + utility.assert_images( + output_path / dose_actors[1].get_output_path("edep"), + output_path / dose_actors[3].get_output_path("edep"), + stats, + tolerance=70, + ignore_value_data2=0, + sum_tolerance=5.2, + ) + and test ) # check that output with flat distribution has better statistics for the spot with less particles diff --git a/opengate/tests/src/test059_tpsource_weights.py b/opengate/tests/src/test059_tpsource_weights.py index 8f1680341..4a22bd685 100755 --- a/opengate/tests/src/test059_tpsource_weights.py +++ b/opengate/tests/src/test059_tpsource_weights.py @@ -161,14 +161,26 @@ # check first spot test = ( - utility.assert_images(ref_path / mhd_1, output_path / mhd_1, stats, tolerance=70, ignore_value_data2=0) - and test + utility.assert_images( + ref_path / mhd_1, + output_path / mhd_1, + stats, + tolerance=70, + ignore_value_data2=0, + ) + and test ) # check second spot test = ( - utility.assert_images(ref_path / mhd_1, output_path / mhd_1, stats, tolerance=70, ignore_value_data2=0) - and test + utility.assert_images( + ref_path / mhd_1, + output_path / mhd_1, + stats, + tolerance=70, + ignore_value_data2=0, + ) + and test ) print(" --------------------------------------- ") # fig1 = utility.create_2D_Edep_colorMap(output_path / mhd_1, show=True) diff --git a/opengate/tests/src/test067_cbct_fluence_actor_mt.py b/opengate/tests/src/test067_cbct_fluence_actor_mt.py index abe24c73b..5b7b470a7 100755 --- a/opengate/tests/src/test067_cbct_fluence_actor_mt.py +++ b/opengate/tests/src/test067_cbct_fluence_actor_mt.py @@ -92,6 +92,8 @@ out_path = detector_actor.get_output_path() # check images - is_ok = utility.assert_images(paths.gate_output / "detector.mhd", out_path, stats, tolerance=44, axis="y") + is_ok = utility.assert_images( + paths.gate_output / "detector.mhd", out_path, stats, tolerance=44, axis="y" + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test073_helpers.py b/opengate/tests/src/test073_helpers.py index 0ea9191af..48ec93e8b 100644 --- a/opengate/tests/src/test073_helpers.py +++ b/opengate/tests/src/test073_helpers.py @@ -170,8 +170,16 @@ def compare_proj_images(crystal, sim, stats, image_filename, path, n=1): img.SetOrigin(origin) itk.imwrite(img, f2) - is_ok = utility.assert_images(fr, f2, stats, tolerance=69, ignore_value_data2=0, axis="y", - fig_name=path / f"test073_test_{n}.png", sum_tolerance=6) + is_ok = utility.assert_images( + fr, + f2, + stats, + tolerance=69, + ignore_value_data2=0, + axis="y", + fig_name=path / f"test073_test_{n}.png", + sum_tolerance=6, + ) return is_ok diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index db7c278b7..cae826a12 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -344,7 +344,9 @@ def assert_images( d2 = data2 else: if ignore_value_data1 is not None and ignore_value_data2 is not None: - mask = np.logical_or(data1 != ignore_value_data1, data2 != ignore_value_data2) + mask = np.logical_or( + data1 != ignore_value_data1, data2 != ignore_value_data2 + ) elif ignore_value_data1 is not None: mask = data1 != ignore_value_data1 else: From 9b7a768bd7546341b343fede7783e8fd8cc5d2bc Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 02:31:51 +0200 Subject: [PATCH 155/183] Make assert_images() backwards compatible - necessary for some tests --- opengate/tests/utility.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index cae826a12..7fb288bf2 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -315,6 +315,7 @@ def assert_images( tolerance=0, ignore_value_data1=None, ignore_value_data2=None, + apply_ignore_mask_to_sum_check=True, axis="z", fig_name=None, sum_tolerance=5, @@ -354,8 +355,17 @@ def assert_images( d1 = data1[mask] d2 = data2[mask] - s1 = np.sum(d1) - s2 = np.sum(d2) + + # this is a patch to make the function back-compatible + # because the ignore value was previously applied only after + # taking the sum and some tests fail after that change + # apply_ignore_mask_to_sum_check = False recreates the old behavior + if apply_ignore_mask_to_sum_check is True: + s1 = np.sum(d1) + s2 = np.sum(d2) + else: + s1 = np.sum(data1) + s2 = np.sum(data2) if s1 == 0 and s2 == 0: t = 0 From 47ccdca32c8163127e50a9b8ced90253c0f4e925 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 02:32:13 +0200 Subject: [PATCH 156/183] Update test073_helpers.py to changes in assert_images() --- opengate/tests/src/test073_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opengate/tests/src/test073_helpers.py b/opengate/tests/src/test073_helpers.py index 48ec93e8b..4e7d22d7a 100644 --- a/opengate/tests/src/test073_helpers.py +++ b/opengate/tests/src/test073_helpers.py @@ -176,6 +176,7 @@ def compare_proj_images(crystal, sim, stats, image_filename, path, n=1): stats, tolerance=69, ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False, axis="y", fig_name=path / f"test073_test_{n}.png", sum_tolerance=6, From 1ece53ea871eed71126bc713158480640af8c5b6 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 02:36:14 +0200 Subject: [PATCH 157/183] Update test059_tpsource_gantry_rot.py --- .../tests/src/test059_tpsource_gantry_rot.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/opengate/tests/src/test059_tpsource_gantry_rot.py b/opengate/tests/src/test059_tpsource_gantry_rot.py index a0ee45411..63eb9c0d8 100755 --- a/opengate/tests/src/test059_tpsource_gantry_rot.py +++ b/opengate/tests/src/test059_tpsource_gantry_rot.py @@ -120,18 +120,18 @@ phantom_rot.translation = [0.0, 0.0, 1000.0] # add dose actor - dose = sim.add_actor("DoseActor", "doseInXYZ") - dose.output_filename = "testTPSgantry.mhd" - dose.attached_to = phantom.name - dose.size = [162, 1620, 162] - dose.spacing = [2.0, 0.2, 2.0] - dose.hit_type = "random" - dose.user_output.dose.active = True - - dose_rot = sim.add_actor("DoseActor", "doseInXYZ_rot") - dose_rot.configure_like(dose) - dose_rot.attached_to = phantom_rot.name - dose_rot.output_filename = "testTPSganry_rot.mhd" + dose_actor = sim.add_actor("DoseActor", "doseInXYZ") + dose_actor.output_filename = "testTPSgantry.mhd" + dose_actor.attached_to = phantom + dose_actor.size = [162, 1620, 162] + dose_actor.spacing = [2.0, 0.2, 2.0] + dose_actor.hit_type = "random" + dose_actor.dose.active = True + + dose_actor_rot = sim.add_actor("DoseActor", "doseInXYZ_rot") + dose_actor_rot.configure_like(dose_actor) + dose_actor_rot.attached_to = phantom_rot + dose_actor_rot.output_filename = "testTPSganry_rot.mhd" # physics sim.physics_manager.physics_list_name = "FTFP_INCLXX_EMZ" @@ -191,10 +191,10 @@ # read output and ref img_mhd_out = itk.imread( - output_path / sim.get_actor("doseInXYZ_rot").get_output_path("edep") + output_path / dose_actor_rot.edep.get_output_path() ) img_mhd_ref = itk.imread( - output_path / sim.get_actor("doseInXYZ").get_output_path("edep") + output_path / dose_actor.edep.get_output_path() ) data = itk.GetArrayViewFromImage(img_mhd_out) data_ref = itk.GetArrayViewFromImage(img_mhd_ref) From fde38946ae7a03a4cf526911ae67e759ae6db9e6 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 02:58:40 +0200 Subject: [PATCH 158/183] Fix in GateDoseActor: correctly consider density of water for dose --- core/opengate_core/opengate_lib/GateDoseActor.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index 1d991205a..2104da023 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -215,8 +215,14 @@ void GateDoseActor::SteppingAction(G4Step *step) { ImageAddValue(cpp_edep_image, index, edep); if (fDoseFlag || fDoseSquaredFlag) { - auto *current_material = step->GetPreStepPoint()->GetMaterial(); - auto density = current_material->GetDensity(); + double density; + if (fToWaterFlag) { + auto *water = G4NistManager::Instance()->FindOrBuildMaterial("G4_WATER"); + density = water->GetDensity(); + } else { + auto *current_material = step->GetPreStepPoint()->GetMaterial(); + density = current_material->GetDensity(); + } dose = edep / density; if (fDoseFlag) { ImageAddValue(cpp_dose_image, index, dose); From a446398052c45603f1775c1220a7c2db0b6a8f1e Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:00:35 +0200 Subject: [PATCH 159/183] Minor code cleaning in test041_dose_actor_dose_to_water_mt.py --- opengate/tests/src/test041_dose_actor_dose_to_water_mt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py index 0587b3fd6..b0af9bef1 100755 --- a/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py +++ b/opengate/tests/src/test041_dose_actor_dose_to_water_mt.py @@ -150,7 +150,7 @@ # so we do not need to manually keep track of the paths here in the script # syntax: dose_actor.dose.get_output_path() - unused = utility.assert_images( + utility.assert_images( dose_actor_IDD_d.dose.get_output_path(), dose_actor_IDD_d2w.dose.get_output_path(), stats, From 16dcabd9eb398220bbbbf038e1737d8226f58d3c Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:09:38 +0200 Subject: [PATCH 160/183] Update test038_gan_phsp_spect_gan_aa.py to changes in assert_images() --- opengate/tests/src/test038_gan_phsp_spect_gan_aa.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py index 9185fe78c..6f6f356c8 100755 --- a/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py +++ b/opengate/tests/src/test038_gan_phsp_spect_gan_aa.py @@ -67,10 +67,11 @@ utility.assert_images( paths.output_ref / "test038_gan_aa_proj.mhd", paths.output / "test038_gan_aa_proj.mhd", - stats, tolerance=70, axis="x", sum_tolerance=2.75, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior ) and is_ok ) From 1e47d1860cc08b5af86023e0bd94177875919b72 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:43:51 +0200 Subject: [PATCH 161/183] Update test033_rotation_spect_aa_helpers.py to changes in assert_images() --- opengate/tests/src/test033_rotation_spect_aa_helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index 582aa340a..9aab1046a 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -187,6 +187,8 @@ def evaluate_test(sim, sources, itol, ref_skipped): tolerance=68, axis="x", sum_tolerance=itol, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False ) and is_ok ) @@ -195,7 +197,7 @@ def evaluate_test(sim, sources, itol, ref_skipped): paths.output_ref / "test033_proj_2.mhd", paths.output / "test033_proj_2.mhd", stats, - tolerance=68, + tolerance=75, axis="x", sum_tolerance=itol, ) From 942ddac292486c856f00bbb70feba2c253b4ea86 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:46:24 +0200 Subject: [PATCH 162/183] Update test028_ge_nm670_spect_4_acc_angle_helpers.py to changes in assert_images() --- opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py index 067b3ebd2..421c05c0e 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py @@ -272,6 +272,7 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8, version=""): axis="x", fig_name=str(paths.output / fig_name), sum_tolerance=sum_tolerance, + apply_ignore_mask_to_sum_check=False ) and is_ok ) From 8b6aa8802acad1793a1255703223209aedc31b21 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:47:42 +0200 Subject: [PATCH 163/183] Update test029 to changes in assert_images() --- opengate/tests/src/test029_volume_time_rotation_1.py | 1 + opengate/tests/src/test029_volume_time_rotation_1_process.py | 1 + opengate/tests/src/test029_volume_time_rotation_2.py | 1 + 3 files changed, 3 insertions(+) diff --git a/opengate/tests/src/test029_volume_time_rotation_1.py b/opengate/tests/src/test029_volume_time_rotation_1.py index c4bad7e28..a04a66ae9 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1.py +++ b/opengate/tests/src/test029_volume_time_rotation_1.py @@ -55,6 +55,7 @@ ignore_value_data2=0, axis="x", sum_tolerance=2, + apply_ignore_mask_to_sum_check=False # reproduce legacy behavior ) and is_ok ) diff --git a/opengate/tests/src/test029_volume_time_rotation_1_process.py b/opengate/tests/src/test029_volume_time_rotation_1_process.py index 39d9b04f6..3b994eed5 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1_process.py +++ b/opengate/tests/src/test029_volume_time_rotation_1_process.py @@ -45,6 +45,7 @@ ignore_value_data2=0, axis="x", sum_tolerance=2, + apply_ignore_mask_to_sum_check=False # reproduce legacy behavior ) and is_ok ) diff --git a/opengate/tests/src/test029_volume_time_rotation_2.py b/opengate/tests/src/test029_volume_time_rotation_2.py index 03d9b93e8..8162c18f7 100755 --- a/opengate/tests/src/test029_volume_time_rotation_2.py +++ b/opengate/tests/src/test029_volume_time_rotation_2.py @@ -55,6 +55,7 @@ ignore_value_data2=0, axis="x", sum_tolerance=6, + apply_ignore_mask_to_sum_check=False # reproduce legacy behavior ) and is_ok ) From 92bb82f8388b11fd0d66c35d232b689ce2164190 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:53:08 +0200 Subject: [PATCH 164/183] Update test023_filters_iec_phantom.py to changes in assert_images() --- opengate/tests/src/test023_filters_iec_phantom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opengate/tests/src/test023_filters_iec_phantom.py b/opengate/tests/src/test023_filters_iec_phantom.py index 9d4298d46..ea4e69b20 100755 --- a/opengate/tests/src/test023_filters_iec_phantom.py +++ b/opengate/tests/src/test023_filters_iec_phantom.py @@ -87,6 +87,8 @@ stat, tolerance=102, sum_tolerance=28, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) utility.test_ok(is_ok) From 37fe1546f18f995f2eed9369f587908e6dfe654a Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:53:29 +0200 Subject: [PATCH 165/183] Update test020_profiling.py to changes in assert_images() --- opengate/tests/src/test020_profiling.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opengate/tests/src/test020_profiling.py b/opengate/tests/src/test020_profiling.py index 8574b60fa..077547b43 100755 --- a/opengate/tests/src/test020_profiling.py +++ b/opengate/tests/src/test020_profiling.py @@ -105,5 +105,7 @@ dose.edep.get_output_path(), stats, tolerance=79, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) utility.test_ok(is_ok) From 76cd9025a4d0490328073c1aac6b8869cc6f2ed2 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:53:51 +0200 Subject: [PATCH 166/183] Add comment in test028_ge_nm670_spect_4_acc_angle_helpers.py --- .../tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py index 421c05c0e..67df62de0 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py @@ -272,7 +272,7 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8, version=""): axis="x", fig_name=str(paths.output / fig_name), sum_tolerance=sum_tolerance, - apply_ignore_mask_to_sum_check=False + apply_ignore_mask_to_sum_check=False # force legacy behavior ) and is_ok ) From 3f11274cda0acc7521ec612a1d6c6ece29ca0300 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 03:54:51 +0200 Subject: [PATCH 167/183] Update test017_repeater.py to changes in assert_images() --- opengate/tests/src/test017_repeater.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opengate/tests/src/test017_repeater.py b/opengate/tests/src/test017_repeater.py index 01c30aa49..e69dfa099 100755 --- a/opengate/tests/src/test017_repeater.py +++ b/opengate/tests/src/test017_repeater.py @@ -107,6 +107,8 @@ stats, tolerance=70, sum_tolerance=6, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) and is_ok ) From a69e28186c0900ad48c16b32d9f528e530c6f72d Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 04:01:41 +0200 Subject: [PATCH 168/183] Update test010 to changes in assert_images() --- opengate/tests/src/test010_generic_source.py | 2 ++ opengate/tests/src/test010_generic_source_confine.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/opengate/tests/src/test010_generic_source.py b/opengate/tests/src/test010_generic_source.py index 8c6a71a7a..d7f0b8201 100755 --- a/opengate/tests/src/test010_generic_source.py +++ b/opengate/tests/src/test010_generic_source.py @@ -129,6 +129,8 @@ dose.edep.get_output_path(), stats_actor, tolerance=30, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source_confine.py b/opengate/tests/src/test010_generic_source_confine.py index 489dbc480..d2dc96b52 100755 --- a/opengate/tests/src/test010_generic_source_confine.py +++ b/opengate/tests/src/test010_generic_source_confine.py @@ -127,6 +127,8 @@ dose_actor.edep.get_output_path(), stats, tolerance=59, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) utility.test_ok(is_ok) From 2c24979a2d31628fe29b70a7c49f029a34df0b5b Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 04:01:59 +0200 Subject: [PATCH 169/183] Update test015 to changes in assert_images() --- opengate/tests/src/test015_iec_phantom_2.py | 1 + opengate/tests/src/test015_iec_phantom_3_mt.py | 1 + opengate/tests/src/test015_iec_phantom_4.py | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/opengate/tests/src/test015_iec_phantom_2.py b/opengate/tests/src/test015_iec_phantom_2.py index e33cfc158..c7a98f83e 100755 --- a/opengate/tests/src/test015_iec_phantom_2.py +++ b/opengate/tests/src/test015_iec_phantom_2.py @@ -80,6 +80,7 @@ ignore_value_data2=0, axis="x", sum_tolerance=2, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) is_ok = is_ok and im_ok diff --git a/opengate/tests/src/test015_iec_phantom_3_mt.py b/opengate/tests/src/test015_iec_phantom_3_mt.py index a1b1b4965..ef2fb596d 100755 --- a/opengate/tests/src/test015_iec_phantom_3_mt.py +++ b/opengate/tests/src/test015_iec_phantom_3_mt.py @@ -80,6 +80,7 @@ stats, tolerance=80, ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False, # force legacy behavior axis="y", sum_tolerance=0.5, sad_profile_tolerance=2, diff --git a/opengate/tests/src/test015_iec_phantom_4.py b/opengate/tests/src/test015_iec_phantom_4.py index 90ce94b35..81db39211 100755 --- a/opengate/tests/src/test015_iec_phantom_4.py +++ b/opengate/tests/src/test015_iec_phantom_4.py @@ -73,9 +73,10 @@ stats, axis="y", tolerance=50, - ignore_value=0, sum_tolerance=1.1, sad_profile_tolerance=3.0, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) is_ok = is_ok and im_ok From 9bfbfc239003130e039d2fd508d229039b476653 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 04:02:46 +0200 Subject: [PATCH 170/183] Remove obsolete test008_dose_actor_2.py --- opengate/tests/src/test008_dose_actor_2.py | 131 --------------------- 1 file changed, 131 deletions(-) delete mode 100755 opengate/tests/src/test008_dose_actor_2.py diff --git a/opengate/tests/src/test008_dose_actor_2.py b/opengate/tests/src/test008_dose_actor_2.py deleted file mode 100755 index d76870be7..000000000 --- a/opengate/tests/src/test008_dose_actor_2.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -from opengate.tests import utility - - -if __name__ == "__main__": - paths = utility.get_default_test_paths(__file__, "", "test008") - - # create the simulation - sim = gate.Simulation() - - # options - sim.visu = False - sim.visu_type = "vrml" - sim.output_dir = paths.output - sim.progress_bar = True - sim.random_seed = 42321 - - # units - m = gate.g4_units.m - cm = gate.g4_units.cm - cm3 = gate.g4_units.cm3 - keV = gate.g4_units.keV - MeV = gate.g4_units.MeV - mm = gate.g4_units.mm - Bq = gate.g4_units.Bq - gcm3 = gate.g4_units.g / cm3 - - # world - world = sim.world - world.size = [1 * m, 1 * m, 1 * m] - world.material = "G4_AIR" - - # pat - patient = sim.add_volume("Image", "patient") - patient.image = paths.data / "patient-4mm.mhd" - print(f"Reading image {patient.image}") - patient.material = "G4_AIR" # material used by default - patient.translation = [0, 0, 272 * mm] - f1 = paths.data / "Schneider2000MaterialsTable.txt" - f2 = paths.data / "Schneider2000DensitiesTable.txt" - tol = 0.1 * gcm3 - ( - patient.voxel_materials, - materials, - ) = gate.geometry.materials.HounsfieldUnit_to_material(sim, tol, f1, f2) - print(f"tol = {tol/gcm3} g/cm3") - print(f"mat : {len(patient.voxel_materials)} materials") - - # physics (opt1 is faster than opt4, but less accurate) - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option1" - sim.physics_manager.set_production_cut("world", "all", 10 * mm) - sim.physics_manager.set_production_cut("patient", "all", 0.1 * mm) - - # source - source = sim.add_source("GenericSource", "mysource") - source.particle = "proton" - source.energy.mono = 150 * MeV - source.position.type = "disc" - source.position.radius = 3 * mm - source.position.translation = [0, 0, 0] - source.direction.type = "momentum" - source.direction.momentum = [0, 0, 1] - source.n = 200 - - # stats - stats = sim.add_actor("SimulationStatisticsActor", "stats") - stats.track_types_flag = True - stats.output_filename = "stats.txt" - - # dose actor 1: depth edep - doseactor1 = sim.add_actor("DoseActor", "depth") - doseactor1.attached_to = "patient" - doseactor1.output_filename = "depth.mhd" - doseactor1.spacing = [5 * mm, 5 * mm, 5 * mm] - doseactor1.size = [50, 50, 50] - doseactor1.output_coordinate_system = "attached_to_image" - doseactor1.dose.active = True - doseactor1.edep_uncertainty.active = True - doseactor1.dose_uncertainty.active = True - doseactor1.dose_uncertainty.write_to_disk = True - doseactor1.density.active = True - - # run - sim.run() - - # print stat - print(stats) - - is_ok = True - print("\nDifference for Dose") - is_ok = ( - utility.assert_images( - paths.output_ref / "depth_dose.mhd", - doseactor1.dose.get_output_path(), - stats, - tolerance=25, - ignore_value_data2=0, - sum_tolerance=1, - ) - and is_ok - ) - - # print("\nDifference for dose uncertainty") - # is_ok = ( - # utility.assert_images( - # paths.output_ref / "depth_dose_uncertainty.mhd", - # doseactor.dose_uncertainty.get_output_path(), - # stats, - # tolerance=5, - # ignore_value_data2=0, - # sum_tolerance=1, - # ) - # and is_ok - # ) - - # print("\nDifference for density: calculated via simulation and from the CT image") - # is_ok = ( - # utility.assert_images( - # paths.output_ref / "depth_density.mhd", - # doseactor1.density.get_output_path(), - # stats, - # tolerance=5, - # sum_tolerance=1, - # ) - # and is_ok - # ) - - utility.test_ok(is_ok) From 4994edc8b1926c93306b752edc26a6145bfae2b5 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 04:03:11 +0200 Subject: [PATCH 171/183] Update test009 to changes in assert_images() --- opengate/tests/src/test009_voxels_dynamic.py | 2 ++ opengate/tests/src/test009_voxels_hu.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/opengate/tests/src/test009_voxels_dynamic.py b/opengate/tests/src/test009_voxels_dynamic.py index 0a1f4995f..ccbab2880 100755 --- a/opengate/tests/src/test009_voxels_dynamic.py +++ b/opengate/tests/src/test009_voxels_dynamic.py @@ -131,6 +131,8 @@ dose.edep.get_output_path(), stats, tolerance=35, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) print(is_ok) diff --git a/opengate/tests/src/test009_voxels_hu.py b/opengate/tests/src/test009_voxels_hu.py index af0eee61c..ed2718993 100755 --- a/opengate/tests/src/test009_voxels_hu.py +++ b/opengate/tests/src/test009_voxels_hu.py @@ -133,6 +133,8 @@ dose.edep.get_output_path(), stats, tolerance=35, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # force legacy behavior ) utility.test_ok(is_ok) From c38473be218e4915018cd1fd85b2a00dae281810 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 02:04:15 +0000 Subject: [PATCH 172/183] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateDoseActor.cpp | 3 +- opengate/tests/src/test009_voxels_dynamic.py | 2 +- opengate/tests/src/test009_voxels_hu.py | 2 +- opengate/tests/src/test010_generic_source.py | 2 +- .../src/test010_generic_source_confine.py | 2 +- opengate/tests/src/test015_iec_phantom_2.py | 2 +- opengate/tests/src/test015_iec_phantom_4.py | 2 +- opengate/tests/src/test017_repeater.py | 2 +- opengate/tests/src/test020_profiling.py | 2 +- .../tests/src/test023_filters_iec_phantom.py | 2 +- .../src/test028_ge_nm670_spect_2_helpers.py | 30 ++++++++++++++----- ...t028_ge_nm670_spect_4_acc_angle_helpers.py | 2 +- .../src/test029_volume_time_rotation_1.py | 2 +- .../test029_volume_time_rotation_1_process.py | 2 +- .../src/test029_volume_time_rotation_2.py | 2 +- .../src/test033_rotation_spect_aa_helpers.py | 2 +- .../tests/src/test059_tpsource_gantry_rot.py | 8 ++--- opengate/tests/utility.py | 1 - 18 files changed, 40 insertions(+), 30 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index 2104da023..36597cd2f 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -217,7 +217,8 @@ void GateDoseActor::SteppingAction(G4Step *step) { if (fDoseFlag || fDoseSquaredFlag) { double density; if (fToWaterFlag) { - auto *water = G4NistManager::Instance()->FindOrBuildMaterial("G4_WATER"); + auto *water = + G4NistManager::Instance()->FindOrBuildMaterial("G4_WATER"); density = water->GetDensity(); } else { auto *current_material = step->GetPreStepPoint()->GetMaterial(); diff --git a/opengate/tests/src/test009_voxels_dynamic.py b/opengate/tests/src/test009_voxels_dynamic.py index ccbab2880..459b83d12 100755 --- a/opengate/tests/src/test009_voxels_dynamic.py +++ b/opengate/tests/src/test009_voxels_dynamic.py @@ -132,7 +132,7 @@ stats, tolerance=35, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) print(is_ok) diff --git a/opengate/tests/src/test009_voxels_hu.py b/opengate/tests/src/test009_voxels_hu.py index ed2718993..9599fd544 100755 --- a/opengate/tests/src/test009_voxels_hu.py +++ b/opengate/tests/src/test009_voxels_hu.py @@ -134,7 +134,7 @@ stats, tolerance=35, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source.py b/opengate/tests/src/test010_generic_source.py index d7f0b8201..3cd60dda2 100755 --- a/opengate/tests/src/test010_generic_source.py +++ b/opengate/tests/src/test010_generic_source.py @@ -130,7 +130,7 @@ stats_actor, tolerance=30, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test010_generic_source_confine.py b/opengate/tests/src/test010_generic_source_confine.py index d2dc96b52..0a3c72158 100755 --- a/opengate/tests/src/test010_generic_source_confine.py +++ b/opengate/tests/src/test010_generic_source_confine.py @@ -128,7 +128,7 @@ stats, tolerance=59, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test015_iec_phantom_2.py b/opengate/tests/src/test015_iec_phantom_2.py index c7a98f83e..1d10c3d49 100755 --- a/opengate/tests/src/test015_iec_phantom_2.py +++ b/opengate/tests/src/test015_iec_phantom_2.py @@ -80,7 +80,7 @@ ignore_value_data2=0, axis="x", sum_tolerance=2, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) is_ok = is_ok and im_ok diff --git a/opengate/tests/src/test015_iec_phantom_4.py b/opengate/tests/src/test015_iec_phantom_4.py index 81db39211..057f82c1e 100755 --- a/opengate/tests/src/test015_iec_phantom_4.py +++ b/opengate/tests/src/test015_iec_phantom_4.py @@ -76,7 +76,7 @@ sum_tolerance=1.1, sad_profile_tolerance=3.0, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) is_ok = is_ok and im_ok diff --git a/opengate/tests/src/test017_repeater.py b/opengate/tests/src/test017_repeater.py index e69dfa099..0e3de5a79 100755 --- a/opengate/tests/src/test017_repeater.py +++ b/opengate/tests/src/test017_repeater.py @@ -108,7 +108,7 @@ tolerance=70, sum_tolerance=6, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) and is_ok ) diff --git a/opengate/tests/src/test020_profiling.py b/opengate/tests/src/test020_profiling.py index 077547b43..6f26f15bb 100755 --- a/opengate/tests/src/test020_profiling.py +++ b/opengate/tests/src/test020_profiling.py @@ -106,6 +106,6 @@ stats, tolerance=79, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test023_filters_iec_phantom.py b/opengate/tests/src/test023_filters_iec_phantom.py index ea4e69b20..c48dc78e5 100755 --- a/opengate/tests/src/test023_filters_iec_phantom.py +++ b/opengate/tests/src/test023_filters_iec_phantom.py @@ -88,7 +88,7 @@ tolerance=102, sum_tolerance=28, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py index 6a44ba156..01ea4157c 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py @@ -354,10 +354,17 @@ def test_spect_proj(sim, paths, proj, version="3"): img.SetOrigin(origin) itk.imwrite(img, str(paths.output / fname_offset)) is_ok = ( - utility.assert_images(paths.gate_output / f"projection{version}.mhd", paths.output / fname_offset, - stats, tolerance=16, ignore_value_data2=0, axis="y", - fig_name=paths.output / f"proj028_{version}_offset.png", sum_tolerance=1.6) - and is_ok + utility.assert_images( + paths.gate_output / f"projection{version}.mhd", + paths.output / fname_offset, + stats, + tolerance=16, + ignore_value_data2=0, + axis="y", + fig_name=paths.output / f"proj028_{version}_offset.png", + sum_tolerance=1.6, + ) + and is_ok ) # compare images with Gate @@ -367,10 +374,17 @@ def test_spect_proj(sim, paths, proj, version="3"): print("Compare images (new spacing/origin") # read image and force change the offset to be similar to old Gate is_ok = ( - utility.assert_images(paths.output_ref / "proj028_ref.mhd", paths.output / fname, stats, - tolerance=14, ignore_value_data2=0, axis="y", - fig_name=paths.output / f"proj028_{version}_no_offset.png", sum_tolerance=1.5) - and is_ok + utility.assert_images( + paths.output_ref / "proj028_ref.mhd", + paths.output / fname, + stats, + tolerance=14, + ignore_value_data2=0, + axis="y", + fig_name=paths.output / f"proj028_{version}_no_offset.png", + sum_tolerance=1.5, + ) + and is_ok ) return is_ok diff --git a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py index 67df62de0..4cfd8070b 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_4_acc_angle_helpers.py @@ -272,7 +272,7 @@ def compare_result(sim, proj, fig_name, sum_tolerance=8, version=""): axis="x", fig_name=str(paths.output / fig_name), sum_tolerance=sum_tolerance, - apply_ignore_mask_to_sum_check=False # force legacy behavior + apply_ignore_mask_to_sum_check=False, # force legacy behavior ) and is_ok ) diff --git a/opengate/tests/src/test029_volume_time_rotation_1.py b/opengate/tests/src/test029_volume_time_rotation_1.py index a04a66ae9..810e10487 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1.py +++ b/opengate/tests/src/test029_volume_time_rotation_1.py @@ -55,7 +55,7 @@ ignore_value_data2=0, axis="x", sum_tolerance=2, - apply_ignore_mask_to_sum_check=False # reproduce legacy behavior + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior ) and is_ok ) diff --git a/opengate/tests/src/test029_volume_time_rotation_1_process.py b/opengate/tests/src/test029_volume_time_rotation_1_process.py index 3b994eed5..e04287d26 100755 --- a/opengate/tests/src/test029_volume_time_rotation_1_process.py +++ b/opengate/tests/src/test029_volume_time_rotation_1_process.py @@ -45,7 +45,7 @@ ignore_value_data2=0, axis="x", sum_tolerance=2, - apply_ignore_mask_to_sum_check=False # reproduce legacy behavior + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior ) and is_ok ) diff --git a/opengate/tests/src/test029_volume_time_rotation_2.py b/opengate/tests/src/test029_volume_time_rotation_2.py index 8162c18f7..677affde0 100755 --- a/opengate/tests/src/test029_volume_time_rotation_2.py +++ b/opengate/tests/src/test029_volume_time_rotation_2.py @@ -55,7 +55,7 @@ ignore_value_data2=0, axis="x", sum_tolerance=6, - apply_ignore_mask_to_sum_check=False # reproduce legacy behavior + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior ) and is_ok ) diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index 9aab1046a..ab5877547 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -188,7 +188,7 @@ def evaluate_test(sim, sources, itol, ref_skipped): axis="x", sum_tolerance=itol, ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False + apply_ignore_mask_to_sum_check=False, ) and is_ok ) diff --git a/opengate/tests/src/test059_tpsource_gantry_rot.py b/opengate/tests/src/test059_tpsource_gantry_rot.py index 63eb9c0d8..3136e4d4a 100755 --- a/opengate/tests/src/test059_tpsource_gantry_rot.py +++ b/opengate/tests/src/test059_tpsource_gantry_rot.py @@ -190,12 +190,8 @@ ok = True # read output and ref - img_mhd_out = itk.imread( - output_path / dose_actor_rot.edep.get_output_path() - ) - img_mhd_ref = itk.imread( - output_path / dose_actor.edep.get_output_path() - ) + img_mhd_out = itk.imread(output_path / dose_actor_rot.edep.get_output_path()) + img_mhd_ref = itk.imread(output_path / dose_actor.edep.get_output_path()) data = itk.GetArrayViewFromImage(img_mhd_out) data_ref = itk.GetArrayViewFromImage(img_mhd_ref) spacing = img_mhd_out.GetSpacing() diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index 7fb288bf2..d1e119bb6 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -355,7 +355,6 @@ def assert_images( d1 = data1[mask] d2 = data2[mask] - # this is a patch to make the function back-compatible # because the ignore value was previously applied only after # taking the sum and some tests fail after that change From 4b2ff0897a5b2c48e675154427da4dbcf8c1110b Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 09:02:19 +0200 Subject: [PATCH 173/183] Rename ImageVolume.read_input_image() to load_input_image() --- opengate/geometry/volumes.py | 8 ++++---- opengate/tests/src/test032_voxel_vs_volume.py | 1 + opengate/tests/src/test070_4d_proton_dose.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index 752a86c16..e37d28a04 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -883,7 +883,7 @@ def construct(self): # make sure the materials are created in Geant4 for m in self.material_to_label_lut: self.volume_manager.find_or_build_material(m) - self.itk_image = self.read_input_image() + self.itk_image = self.load_input_image() self.label_image = self.create_label_image() if self.dump_label_image: self.save_label_image() @@ -968,7 +968,7 @@ def create_material_to_label_lut(self, material=None, voxel_materials=None): return material_to_label_lut - def read_input_image(self, path=None): + def load_input_image(self, path=None): if path is None: itk_image = itk.imread(ensure_filename_is_str(self.image)) self.itk_image = itk_image @@ -980,7 +980,7 @@ def create_label_image(self, itk_image=None): # read image if itk_image is None: if self.itk_image is None: - self.itk_image = self.read_input_image() + self.itk_image = self.load_input_image() itk_image = self.itk_image if self.material_to_label_lut is None: @@ -1085,7 +1085,7 @@ def create_changers(self): # create a LUT of image parametrisations label_image = {} for path_to_image in set(dp["image"]): - itk_image = self.read_input_image(path_to_image) + itk_image = self.load_input_image(path_to_image) label_image[path_to_image] = self.create_label_image(itk_image) new_changer = VolumeImageChanger( name=f"{self.name}_volume_image_changer_{len(changers)}", diff --git a/opengate/tests/src/test032_voxel_vs_volume.py b/opengate/tests/src/test032_voxel_vs_volume.py index 7e1f55af1..bde1aa4cc 100755 --- a/opengate/tests/src/test032_voxel_vs_volume.py +++ b/opengate/tests/src/test032_voxel_vs_volume.py @@ -83,6 +83,7 @@ # the image coordinate space to iec1 or iec2 # Coordinate system of iec1 is pMin (the extend) # Coordinate system of iec2 is the center of the image bounding box + iec2.load_input_image() img = itk.imread(str(iec2.image)) fake1 = gate.image.create_image_like(img) pMin = gate.geometry.utility.vec_g4_as_np(pMin) diff --git a/opengate/tests/src/test070_4d_proton_dose.py b/opengate/tests/src/test070_4d_proton_dose.py index ea00f12cd..bf316bbf7 100755 --- a/opengate/tests/src/test070_4d_proton_dose.py +++ b/opengate/tests/src/test070_4d_proton_dose.py @@ -42,7 +42,7 @@ # image patient = sim.add_volume("Image", "patient") patient.image = path_to_4d_ct / "0.0.mhd" - patient.read_input_image() + patient.load_input_image() patient.material = "G4_AIR" # material used by default f1 = str(paths.gate_data / "Schneider2000MaterialsTable.txt") f2 = str(paths.gate_data / "Schneider2000DensitiesTable.txt") From 79e121fd62b0024ae6b0ca657218c6a2e874d475 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 09:19:23 +0200 Subject: [PATCH 174/183] In ImageVolume: Create getter for itk_image that warns user if not loaded yet --- opengate/geometry/volumes.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index e37d28a04..08f476f5b 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -810,7 +810,7 @@ def __init__(self, *args, **kwargs): self.material_to_label_lut = None # ITK images - self.itk_image = None # the input + self._itk_image = None # the input self.label_image = None # image storing material labels # G4 references (additionally to those in base class) self.g4_physical_x = None @@ -845,6 +845,18 @@ def release_g4_references(self): self.g4_physical_z = None self.g4_voxel_param = None + @property + def itk_image(self): + if self._itk_image is None: + warning(f"The itk_image in {self.type_name} '{self.name}' is None. " + f"If this is unexpected, run my_image_volume.load_input_image() first, " + f"where my_image_volume is the variable name of the {self.type_name} in your script. ") + return self._itk_image + + @itk_image.setter + def itk_image(self, image): + self._itk_image = image + # @requires_fatal('itk_image') # FIXME: replace this property by function in opengate.image @property From 44b63c2949d62de7f8c15f0131ce52d3e825fa4b Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 09:28:22 +0200 Subject: [PATCH 175/183] Update test032_voxel_vs_volume.py to changes in assert_images() and ciimplify code --- opengate/tests/src/test032_voxel_vs_volume.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/opengate/tests/src/test032_voxel_vs_volume.py b/opengate/tests/src/test032_voxel_vs_volume.py index bde1aa4cc..49058ea4f 100755 --- a/opengate/tests/src/test032_voxel_vs_volume.py +++ b/opengate/tests/src/test032_voxel_vs_volume.py @@ -84,12 +84,11 @@ # Coordinate system of iec1 is pMin (the extend) # Coordinate system of iec2 is the center of the image bounding box iec2.load_input_image() - img = itk.imread(str(iec2.image)) - fake1 = gate.image.create_image_like(img) + fake1 = gate.image.create_image_like(iec2.itk_image) pMin = gate.geometry.utility.vec_g4_as_np(pMin) fake1.SetOrigin(pMin) - fake2 = gate.image.create_image_like(img) + fake2 = gate.image.create_image_like(iec2.itk_image) info = gate.image.get_info_from_image(fake2) origin = -info.size * info.spacing / 2.0 + info.spacing / 2.0 fake2.SetOrigin(origin) @@ -107,9 +106,9 @@ # and in the analytical phantom (iec1) p = [31 * mm, 33 * mm, 36 * mm] if i == 1: - p = gate.image.transform_images_point(p, img, fake1) + p = gate.image.transform_images_point(p, iec2.itk_image, fake1) else: - p = gate.image.transform_images_point(p, img, fake2) + p = gate.image.transform_images_point(p, iec2.itk_image, fake2) source.position.translation = p source.activity = activity source.direction.type = "iso" @@ -148,6 +147,8 @@ stats, tolerance=87, axis="x", + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False # reproduce legacy behavior of assert_images() ) utility.test_ok(is_ok) From a5cd12dfb68ab71c92c4134515beaa6697064a1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 07:50:26 +0000 Subject: [PATCH 176/183] [pre-commit.ci] Automatic python and c++ formatting --- opengate/geometry/volumes.py | 8 +++++--- opengate/tests/src/test032_voxel_vs_volume.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index 08f476f5b..f7efea538 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -848,9 +848,11 @@ def release_g4_references(self): @property def itk_image(self): if self._itk_image is None: - warning(f"The itk_image in {self.type_name} '{self.name}' is None. " - f"If this is unexpected, run my_image_volume.load_input_image() first, " - f"where my_image_volume is the variable name of the {self.type_name} in your script. ") + warning( + f"The itk_image in {self.type_name} '{self.name}' is None. " + f"If this is unexpected, run my_image_volume.load_input_image() first, " + f"where my_image_volume is the variable name of the {self.type_name} in your script. " + ) return self._itk_image @itk_image.setter diff --git a/opengate/tests/src/test032_voxel_vs_volume.py b/opengate/tests/src/test032_voxel_vs_volume.py index 49058ea4f..8bf9c5c08 100755 --- a/opengate/tests/src/test032_voxel_vs_volume.py +++ b/opengate/tests/src/test032_voxel_vs_volume.py @@ -148,7 +148,7 @@ tolerance=87, axis="x", ignore_value_data2=0, - apply_ignore_mask_to_sum_check=False # reproduce legacy behavior of assert_images() + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior of assert_images() ) utility.test_ok(is_ok) From eca6b3ed6a4fabb675234c8ef7ad470c40272976 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 10:32:42 +0200 Subject: [PATCH 177/183] Make logger show level name, e.g. WARNING --- opengate/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/logger.py b/opengate/logger.py index b6b8310ad..eb3eac939 100644 --- a/opengate/logger.py +++ b/opengate/logger.py @@ -8,7 +8,7 @@ # main format formatter = colorlog.ColoredFormatter( - "%(log_color)s%(log_color)s%(message)s", + "%(log_color)s%(levelname)-8s%(log_color)s%(message)s", datefmt=None, reset=True, log_colors={"NONE": "cyan", "DEBUG": "cyan", "INFO": "green"}, From 5745cf932e4880117a24d018efe40a5c93232514 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 23:15:36 +0200 Subject: [PATCH 178/183] Update test028 to changes in assert_images() --- opengate/tests/src/test028_ge_nm670_spect_2_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py index 01ea4157c..45872da38 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py @@ -363,6 +363,7 @@ def test_spect_proj(sim, paths, proj, version="3"): axis="y", fig_name=paths.output / f"proj028_{version}_offset.png", sum_tolerance=1.6, + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior of assert_images ) and is_ok ) From 1bc4886fc4ce372cd092fa0feac2608bd24f4260 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Fri, 18 Oct 2024 23:19:42 +0200 Subject: [PATCH 179/183] Update test033_rotation_spect_aa_helpers.py to changes in assert_images() --- opengate/tests/src/test033_rotation_spect_aa_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opengate/tests/src/test033_rotation_spect_aa_helpers.py b/opengate/tests/src/test033_rotation_spect_aa_helpers.py index ab5877547..6f9745ba5 100644 --- a/opengate/tests/src/test033_rotation_spect_aa_helpers.py +++ b/opengate/tests/src/test033_rotation_spect_aa_helpers.py @@ -200,6 +200,8 @@ def evaluate_test(sim, sources, itol, ref_skipped): tolerance=75, axis="x", sum_tolerance=itol, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior of assert_images ) and is_ok ) From 05602aca087030331de24f7d73d6bc0dac504177 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 19 Oct 2024 08:25:47 +0200 Subject: [PATCH 180/183] Adapt test028_ge_nm670_spect_2_helpers.py to changes in assert_images() --- opengate/tests/src/test028_ge_nm670_spect_2_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py index 45872da38..2226aeef2 100644 --- a/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py +++ b/opengate/tests/src/test028_ge_nm670_spect_2_helpers.py @@ -384,6 +384,7 @@ def test_spect_proj(sim, paths, proj, version="3"): axis="y", fig_name=paths.output / f"proj028_{version}_no_offset.png", sum_tolerance=1.5, + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior of assert_images ) and is_ok ) From 0107d376b9004dafe64a148d0670a2ae5313c049 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 19 Oct 2024 08:27:26 +0200 Subject: [PATCH 181/183] Adapt test009_voxels.py to changes in assert_images() --- opengate/tests/src/test009_voxels.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opengate/tests/src/test009_voxels.py b/opengate/tests/src/test009_voxels.py index 80d2658cd..4875cf53e 100755 --- a/opengate/tests/src/test009_voxels.py +++ b/opengate/tests/src/test009_voxels.py @@ -120,6 +120,8 @@ dose_actor.edep.get_output_path(), stats_actor, tolerance=35, + ignore_value_data2=0, + apply_ignore_mask_to_sum_check=False, # reproduce legacy behavior of assert_images ) utility.test_ok(is_ok) From 62ac33f0705a8881cf01e01a8ed511b1e6080a77 Mon Sep 17 00:00:00 2001 From: Nils Krah Date: Sat, 19 Oct 2024 14:59:21 +0200 Subject: [PATCH 182/183] remove obsolete code and clean code --- .../opengate_lib/GateDoseActor.cpp | 75 ------------------- .../opengate_lib/GateDoseActor.h | 38 +--------- 2 files changed, 3 insertions(+), 110 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index 36597cd2f..00392ec16 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -37,34 +37,11 @@ GateDoseActor::GateDoseActor(py::dict &user_info) void GateDoseActor::InitializeUserInput(py::dict &user_info) { // IMPORTANT: call the base class method GateVActor::InitializeUserInput(user_info); - // // Option: compute uncertainty - // fUncertaintyFlag = DictGetBool(user_info, "uncertainty"); - // Option: compute square - // fSquareFlag = DictGetBool(user_info, "square"); - // // Option: compute dose in Gray - // fDoseFlag = DictGetBool(user_info, "dose"); - // Option: compute dose to water - fScoreIn = DictGetStr(user_info, "score_in"); - // DDD(fScoreIn); - // // Option: calculate only edep/edepToWater, and divide by mass image on - // python - // // side - // fOnFlyCalcFlag = DictGetBool(user_info, "dose_calc_on_the_fly"); - - // // Option to stop the simulation when a stat goal is reached (for now only - // // uncertainty goal) - // goalUncertainty = DictGetDouble(user_info, "goal_uncertainty"); - threshEdepPerc = DictGetDouble(user_info, "thresh_voxel_edep_for_unc_calc"); // translation fTranslation = DictGetG4ThreeVector(user_info, "translation"); // Hit type (random, pre, post etc) fHitType = DictGetStr(user_info, "hit_type"); - // // Option: make a copy of the image for each thread, instead of writing on - // // same image - // fcpImageForThreadsFlag = DictGetBool(user_info, "use_more_ram"); - // // Option: calculate the standard error of the mean - // fSTEofMeanFlag = DictGetBool(user_info, "ste_of_mean"); } void GateDoseActor::InitializeCpp() { @@ -190,9 +167,7 @@ void GateDoseActor::SteppingAction(G4Step *step) { if (fToWaterFlag) { auto *current_material = step->GetPreStepPoint()->GetMaterial(); double dedx_cut = DBL_MAX; - // dedx double dedx_currstep = 0., dedx_water = 0.; - // other material const G4ParticleDefinition *p = step->GetTrack()->GetParticleDefinition(); static G4Material *water = G4NistManager::Instance()->FindOrBuildMaterial("G4_WATER"); @@ -250,56 +225,6 @@ void GateDoseActor::SteppingAction(G4Step *step) { } // else: outside of the image } -double GateDoseActor::ComputeMeanUncertainty() { - G4AutoLock mutex(&ComputeUncertaintyMutex); - itk::ImageRegionIterator edep_iterator3D( - cpp_edep_image, cpp_edep_image->GetLargestPossibleRegion()); - double mean_unc = 0.0; - int n_voxel_unc = 0; - double n = 2.0; - n = NbOfEvent; - - if (n < 2.0) { - n = 2.0; - } - double max_edep = GetMaxValueOfImage(cpp_edep_image); - - for (edep_iterator3D.GoToBegin(); !edep_iterator3D.IsAtEnd(); - ++edep_iterator3D) { - Image3DType::IndexType index_f = edep_iterator3D.GetIndex(); - double val = cpp_edep_image->GetPixel(index_f); - - if (val > max_edep * threshEdepPerc) { - val /= n; - n_voxel_unc++; - double val_squared_mean = cpp_edep_squared_image->GetPixel(index_f) / n; - - double unc_i = (1.0 / (n - 1.0)) * (val_squared_mean - pow(val, 2)); - if (unc_i < 0) { - std::cout << "unc_i: " << unc_i << std::endl; - std::cout << "edep: " << val << std::endl; - std::cout << "edep_squared_mean: " << val_squared_mean << std::endl; - } - - unc_i = sqrt(unc_i) / (val); - - if (unc_i > 1) { - std::cout << "unc_i: " << unc_i << std::endl; - std::cout << "edep: " << val << std::endl; - std::cout << "edep_squared_mean: " << val_squared_mean << std::endl; - } - mean_unc += unc_i; - } - }; - - if (n_voxel_unc > 0 && mean_unc > 0) { - mean_unc = mean_unc / n_voxel_unc; - } else { - mean_unc = 1.; - } - std::cout << "unc: " << mean_unc << std::endl; - return mean_unc; -} int GateDoseActor::sub2ind(Image3DType::IndexType index3D) { diff --git a/core/opengate_core/opengate_lib/GateDoseActor.h b/core/opengate_core/opengate_lib/GateDoseActor.h index 098ee3816..914c50929 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.h +++ b/core/opengate_core/opengate_lib/GateDoseActor.h @@ -71,14 +71,11 @@ class GateDoseActor : public GateVActor { inline void SetPhysicalVolumeName(std::string s) { fPhysicalVolumeName = s; } - // virtual void EndSimulationAction(); - // Image type needs to be 3D double by default typedef itk::Image Image3DType; int sub2ind(Image3DType::IndexType index3D); void ind2sub(int index, Image3DType::IndexType &index3D); - double ComputeMeanUncertainty(); double GetMaxValueOfImage(Image3DType::Pointer imageP); // The image is accessible on py side (shared by all threads) @@ -92,14 +89,9 @@ class GateDoseActor : public GateVActor { struct threadLocalT { G4EmCalculator emcalc; - std::vector linear_worker_flatimg; std::vector squared_worker_flatimg; std::vector lastid_worker_flatimg; - // int NbOfEvent_worker = 0; - // Image3DType::IndexType index3D; - // int index_flat; }; - // using ThreadLocalType = struct threadLocalT; void ScoreSquaredValue(threadLocalT &data, Image3DType::Pointer cpp_image, double value, int event_id, @@ -109,20 +101,12 @@ class GateDoseActor : public GateVActor { void PrepareLocalDataForRun(threadLocalT &data, int numberOfVoxels); - // // Option: indicate if we must compute uncertainty - // bool fUncertaintyFlag; - - // Option: indicate if we must compute square - bool fEdepSquaredFlag{}; - - // // Option: indicate if we must compute dose in Gray also - // bool fDoseFlag; - - std::string fScoreIn; - // Option: indicate we must convert to dose to water bool fToWaterFlag{}; + // Option: indicate if we must compute edep squared + bool fEdepSquaredFlag{}; + // Option: Is dose to be scored? bool fDoseFlag{}; bool fDoseSquaredFlag{}; @@ -130,26 +114,10 @@ class GateDoseActor : public GateVActor { // Option: Are counts to be scored bool fCountsFlag{}; - // // Option: calculate dose in stepping action. If False, calc only edep and - // // divide by mass at the end of the simulation, on py side - // bool fOnFlyCalcFlag; - - // // Option: cp image for each thread - // bool fcpImageForThreadsFlag; - // - // // Option: calculate the standard error of the mean - // bool fSTEofMeanFlag; - - // For uncertainty computation, we need temporary images - double fVoxelVolume{}; int NbOfEvent = 0; int NbOfThreads = 0; - // double goalUncertainty; - double threshEdepPerc{}; - // struct timeval mTimeOfLastSaveEvent; - std::string fPhysicalVolumeName; G4ThreeVector fTranslation; From 5698fbb67deaa54c496d0f5651a83a032f1f4b63 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 19 Oct 2024 16:03:03 +0000 Subject: [PATCH 183/183] [pre-commit.ci] Automatic python and c++ formatting --- core/opengate_core/opengate_lib/GateDoseActor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/opengate_core/opengate_lib/GateDoseActor.cpp b/core/opengate_core/opengate_lib/GateDoseActor.cpp index 00392ec16..aea36cf06 100644 --- a/core/opengate_core/opengate_lib/GateDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateDoseActor.cpp @@ -225,7 +225,6 @@ void GateDoseActor::SteppingAction(G4Step *step) { } // else: outside of the image } - int GateDoseActor::sub2ind(Image3DType::IndexType index3D) { return index3D[0] + size_edep[0] * (index3D[1] + size_edep[1] * index3D[2]);