From 438156a7e8acc51fd5fc532431ab4556e2f735d1 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Tue, 11 Oct 2022 11:44:31 +0200 Subject: [PATCH 01/35] Use same FOV definition in Time Reversal Adapter --- ...econstruction_module_delay_and_sum_adapter.py | 2 +- ...tion_module_delay_multiply_and_sum_adapter.py | 2 +- ...dule_signed_delay_multiply_and_sum_adapter.py | 2 +- ...econstruction_module_time_reversal_adapter.py | 7 ++++++- .../reconstruction_utils.py | 16 ++++++++-------- .../device_tests/test_field_of_view.py | 2 +- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_delay_and_sum_adapter.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_delay_and_sum_adapter.py index d008498d..da698299 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_delay_and_sum_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_delay_and_sum_adapter.py @@ -32,7 +32,7 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry: ### ALGORITHM ITSELF ### xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions( - detection_geometry, spacing_in_mm, self.logger) + detection_geometry.field_of_view_extent_mm, spacing_in_mm, self.logger) if zdim == 1: sensor_positions[:, 1] = 0 # Assume imaging plane diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_delay_multiply_and_sum_adapter.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_delay_multiply_and_sum_adapter.py index 65178230..e4a8678b 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_delay_multiply_and_sum_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_delay_multiply_and_sum_adapter.py @@ -32,7 +32,7 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry: ### ALGORITHM ITSELF ### xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions( - detection_geometry, spacing_in_mm, self.logger) + detection_geometry.field_of_view_extent_mm, spacing_in_mm, self.logger) if zdim == 1: sensor_positions[:, 1] = 0 # Assume imaging plane diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_signed_delay_multiply_and_sum_adapter.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_signed_delay_multiply_and_sum_adapter.py index 28c7d84c..bf735f43 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_signed_delay_multiply_and_sum_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_signed_delay_multiply_and_sum_adapter.py @@ -33,7 +33,7 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry: ### ALGORITHM ITSELF ### xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions( - detection_geometry, spacing_in_mm, self.logger) + detection_geometry.field_of_view_extent_mm, spacing_in_mm, self.logger) if zdim == 1: sensor_positions[:, 1] = 0 # Assume imaging plane diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_time_reversal_adapter.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_time_reversal_adapter.py index dc003043..9ef55bb0 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_time_reversal_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_time_reversal_adapter.py @@ -2,6 +2,7 @@ # SPDX-FileCopyrightText: 2021 Janek Groehl # SPDX-License-Identifier: MIT +from simpa.core.simulation_modules.reconstruction_module.reconstruction_utils import compute_image_dimensions from simpa.utils import Tags from simpa.utils.settings import Settings from simpa.core.simulation_modules.reconstruction_module import ReconstructionAdapterBase @@ -188,7 +189,11 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry): reconstructed_data = np.flipud(np.rot90(reconstructed_data, 1, axes)) field_of_view_mm = detection_geometry.get_field_of_view_mm() - field_of_view_voxels = (field_of_view_mm / spacing_in_mm).astype(np.int32) + # field_of_view_voxels = (field_of_view_mm / spacing_in_mm).astype(np.int32) + _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, spacing_in_mm, self.logger) + field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering + field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int + self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 x_offset_correct = 1 if (field_of_view_voxels[1] - field_of_view_voxels[0]) < 1 else 0 diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py index 9e6e415d..a30b3e6d 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py @@ -408,14 +408,15 @@ def preparing_reconstruction_and_obtaining_reconstruction_settings( return (time_series_sensor_data, sensor_positions, speed_of_sound_in_m_per_s, spacing_in_mm, time_spacing_in_ms, torch_device) -def compute_image_dimensions(detection_geometry: DetectionGeometryBase, spacing_in_mm: float, +def compute_image_dimensions(field_of_view_in_mm: np.ndarray, spacing_in_mm: float, logger: Logger) -> Tuple[int, int, int, np.float64, np.float64, np.float64, np.float64, np.float64, np.float64]: """ Computes size of beamformed image from field of view of detection geometry given the spacing. - :param detection_geometry: detection geometry with specified field of view - :type detection_geometry: DetectionGeometryBase + :param field_of_view_in_mm: field of view in mm as list of xdim_start, xdim_end, ydim_start, ydim_end, + zdim_start, zdim_end + :type field_of_view_in_mm: numpy ndarray :param spacing_in_mm: space betwenn pixels in mm :type spacing_in_mm: float :param logger: logger for debugging purposes @@ -426,8 +427,7 @@ def compute_image_dimensions(detection_geometry: DetectionGeometryBase, spacing_ :rtype: Tuple[int, int, int, np.float64, np.float64, np.float64, np.float64, np.float64, np.float64] """ - field_of_view = detection_geometry.field_of_view_extent_mm - logger.debug(f"Field of view: {field_of_view}") + logger.debug(f"Field of view: {field_of_view_in_mm}") def compute_for_one_dimension(start_in_mm: float, end_in_mm: float) -> Tuple[int, np.float64, np.float64]: """ @@ -452,9 +452,9 @@ def compute_for_one_dimension(start_in_mm: float, end_in_mm: float) -> Tuple[int end = end_temp - np.sign(end_temp) * diff/2 return dim, start, end - xdim, xdim_start, xdim_end = compute_for_one_dimension(field_of_view[0], field_of_view[1]) - zdim, zdim_start, zdim_end = compute_for_one_dimension(field_of_view[2], field_of_view[3]) - ydim, ydim_start, ydim_end = compute_for_one_dimension(field_of_view[4], field_of_view[5]) + xdim, xdim_start, xdim_end = compute_for_one_dimension(field_of_view_in_mm[0], field_of_view_in_mm[1]) + zdim, zdim_start, zdim_end = compute_for_one_dimension(field_of_view_in_mm[2], field_of_view_in_mm[3]) + ydim, ydim_start, ydim_end = compute_for_one_dimension(field_of_view_in_mm[4], field_of_view_in_mm[5]) if xdim < 1: xdim = 1 diff --git a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py index bd5468e5..943d5a21 100644 --- a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py +++ b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py @@ -22,7 +22,7 @@ def setUp(self): def _test(self, field_of_view_extent_mm: list, spacing_in_mm: float, detection_geometry: DetectionGeometryBase): detection_geometry.field_of_view_extent_mm = field_of_view_extent_mm xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions( - detection_geometry, spacing_in_mm, self.logger) + detection_geometry.field_of_view_extent_mm, spacing_in_mm, self.logger) assert type(xdim) == int and type(ydim) == int and type(zdim) == int, "dimensions should be integers" assert xdim >= 1 and ydim >= 1 and zdim >= 1, "dimensions should be positive" From 077bfded7e1b55a3fc6340be7fb768b6c4e688d7 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Tue, 11 Oct 2022 11:53:59 +0200 Subject: [PATCH 02/35] Use same definition in FOV cropping component --- .../monospectral/field_of_view_cropping.py | 5 ++++- .../reconstruction_module_time_reversal_adapter.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/simpa/core/processing_components/monospectral/field_of_view_cropping.py b/simpa/core/processing_components/monospectral/field_of_view_cropping.py index bc24bef7..69d93eb1 100644 --- a/simpa/core/processing_components/monospectral/field_of_view_cropping.py +++ b/simpa/core/processing_components/monospectral/field_of_view_cropping.py @@ -2,6 +2,7 @@ # SPDX-FileCopyrightText: 2021 Janek Groehl # SPDX-License-Identifier: MIT +from simpa.core.simulation_modules.reconstruction_module.reconstruction_utils import compute_image_dimensions from simpa.utils import Tags, Settings from simpa.utils.tissue_properties import TissueProperties from simpa.io_handling import load_data_field, save_data_field @@ -47,7 +48,9 @@ def run(self, device: DigitalDeviceTwinBase): else: field_of_view_mm = device.get_field_of_view_mm() self.logger.debug(f"FOV (mm): {field_of_view_mm}") - field_of_view_voxels = np.round(field_of_view_mm / self.global_settings[Tags.SPACING_MM]).astype(np.int32) + _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, self.global_settings[Tags.SPACING_MM], self.logger) + field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering + field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_time_reversal_adapter.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_time_reversal_adapter.py index 9ef55bb0..1b88e38f 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_time_reversal_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_module_time_reversal_adapter.py @@ -189,7 +189,6 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry): reconstructed_data = np.flipud(np.rot90(reconstructed_data, 1, axes)) field_of_view_mm = detection_geometry.get_field_of_view_mm() - # field_of_view_voxels = (field_of_view_mm / spacing_in_mm).astype(np.int32) _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, spacing_in_mm, self.logger) field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int From c180f86e889b5ddb67494573d5afb1345cc999b5 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Tue, 3 Jan 2023 13:46:47 +0100 Subject: [PATCH 03/35] Round instead of floor dimension size to obtain smaller error on average --- .../reconstruction_module/reconstruction_utils.py | 7 +++---- .../automatic_tests/device_tests/test_field_of_view.py | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py index a30b3e6d..1d65ad05 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py @@ -432,8 +432,7 @@ def compute_image_dimensions(field_of_view_in_mm: np.ndarray, spacing_in_mm: flo def compute_for_one_dimension(start_in_mm: float, end_in_mm: float) -> Tuple[int, np.float64, np.float64]: """ Helper function to compute the image dimensions for a single dimension given a start and end point in mm. - Makes sure that image dimesion is an integer by flooring. - Spaces the pixels symmetrically between start and end. + Makes sure that image dimesion is an integer by rounding, thus the resulting dimensions might be slightly larger or smaller than the given field of view. The maximal deviation amounts to a quarter spacing on each side. The algorithm spaces the pixels symmetrically between start and end. :param start_in_mm: lower limit of the field of view in this dimension :type start_in_mm: float @@ -446,8 +445,8 @@ def compute_for_one_dimension(start_in_mm: float, end_in_mm: float) -> Tuple[int start_temp = start_in_mm / spacing_in_mm end_temp = end_in_mm / spacing_in_mm dim_temp = np.abs(end_temp - start_temp) - dim = int(np.floor(dim_temp)) - diff = np.abs(dim_temp - dim) + dim = int(np.round(dim_temp)) + diff = dim_temp - dim # the sign is important here start = start_temp - np.sign(start_temp) * diff/2 end = end_temp - np.sign(end_temp) * diff/2 return dim, start, end diff --git a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py index 943d5a21..f6fda277 100644 --- a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py +++ b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py @@ -77,10 +77,10 @@ def unsymmetric_test(self): xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = image_dimensions assert zdim == 1, "With no FOV extend in z dimension only one slice should be created" - assert xdim == 249 + assert xdim == 250 assert ydim == 100 - self.assertAlmostEqual(xdim_start, -124.75) - self.assertAlmostEqual(xdim_end, 124.25) + self.assertAlmostEqual(xdim_start, -125.25) + self.assertAlmostEqual(xdim_end, 124.75) self.assertAlmostEqual(ydim_start, -60) self.assertAlmostEqual(ydim_end, 40) self.assertAlmostEqual(zdim_start, 0) From 55f0c716798dc3dad7465ba2373a5689e4035613 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Tue, 3 Jan 2023 13:50:39 +0100 Subject: [PATCH 04/35] Added field of view to plot --- simpa_examples/create_a_custom_digital_device_twin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/simpa_examples/create_a_custom_digital_device_twin.py b/simpa_examples/create_a_custom_digital_device_twin.py index 22895e3c..57bb551e 100644 --- a/simpa_examples/create_a_custom_digital_device_twin.py +++ b/simpa_examples/create_a_custom_digital_device_twin.py @@ -4,6 +4,8 @@ import simpa as sp from simpa import Tags +from simpa.log import Logger +from simpa.core.simulation_modules.reconstruction_module.reconstruction_utils import compute_image_dimensions import numpy as np # FIXME temporary workaround for newest Intel architectures import os @@ -40,7 +42,11 @@ def __init__(self): positions = device.get_detection_geometry().get_detector_element_positions_accounting_for_device_position_mm() detector_elements = device.get_detection_geometry().get_detector_element_orientations() positions = np.round(positions/settings[Tags.SPACING_MM]).astype(int) + xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(device.get_detection_geometry().field_of_view_extent_mm, settings[Tags.SPACING_MM], Logger()) + import matplotlib.pyplot as plt + from matplotlib.patches import Rectangle + plt.gca().add_patch(Rectangle((xdim_start ,ydim_start), xdim, -ydim, linewidth=1, edgecolor='r', facecolor='r', alpha=.5)) plt.scatter(positions[:, 0], positions[:, 2]) plt.quiver(positions[:, 0], positions[:, 2], detector_elements[:, 0], detector_elements[:, 2]) plt.show() From aac0ba515f97b02b2deb68ab0253dd4f85392bc2 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Tue, 3 Jan 2023 14:38:39 +0100 Subject: [PATCH 05/35] Added further test to cover more cases --- .../device_tests/test_field_of_view.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py index f6fda277..4a2f6597 100644 --- a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py +++ b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py @@ -103,6 +103,31 @@ def symmetric_test_with_odd_number_of_elements(self): self.assertAlmostEqual(zdim_start, 0) self.assertAlmostEqual(zdim_end, 0) + def test_with_changing_spacing(self): + """ + Tests different spacings with the same field of view. Dimensions and their start and end are expected to change + """ + spacings = [0.1, 0.2, 0.3, 0.4, 0.5] + expected_xdims = [100, 50, 33, 25, 20] + expected_ydims = [200, 100, 67, 50, 40] + expected_xdim_starts = [-50, -25, -16.5, -12.5, -10] + expected_xdim_ends = [50, 25, 16.5, 12.5, 10] + expected_ydim_starts = [-120, -60, -40.16666666, -30, -24] + expected_ydim_ends = [80, 40, 26.83333333, 20, 16] + + for i, spacing in enumerate(spacings): + image_dimensions = self._test([-5, 5, 0, 0, -12, 8], spacing, self.detection_geometry) + xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = image_dimensions + + assert zdim == 1, "With no FOV extend in z dimension only one slice should be created" + assert xdim == expected_xdims[i] + assert ydim == expected_ydims[i] + self.assertAlmostEqual(xdim_start, expected_xdim_starts[i]) + self.assertAlmostEqual(xdim_end, expected_xdim_ends[i]) + self.assertAlmostEqual(ydim_start, expected_ydim_starts[i]) + self.assertAlmostEqual(ydim_end, expected_ydim_ends[i]) + self.assertAlmostEqual(zdim_start, 0) + self.assertAlmostEqual(zdim_end, 0) if __name__ == '__main__': test = TestFieldOfView() @@ -112,3 +137,4 @@ def symmetric_test_with_odd_number_of_elements(self): test.unsymmetric_test_with_small_spacing() test.unsymmetric_test() test.symmetric_test_with_odd_number_of_elements() + test.test_with_changing_spacing() From 7c082b88d36806fa702419740eb955ffeb27f8b8 Mon Sep 17 00:00:00 2001 From: Kris Kristoffer Dreher <40358298+kdreher@users.noreply.github.com> Date: Tue, 25 Jun 2024 17:15:24 +0200 Subject: [PATCH 06/35] Update dependabot.yml Specify target-branch to develop --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f45fb1d8..272046e7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,9 +4,11 @@ updates: directory: "/" schedule: interval: "weekly" + target-branch: "develop" open-pull-requests-limit: 0 - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" + target-branch: "develop" open-pull-requests-limit: 0 From c37bc7838803c15ff363b571b16b1503c32a393b Mon Sep 17 00:00:00 2001 From: Janek Grohl Date: Tue, 13 Aug 2024 14:55:36 +0100 Subject: [PATCH 07/35] added an example script that runs the identical simulation twice, but once in a 2D configuration and once in a 3D configuration --- ...e_vs_two_dimensional_simulation_example.py | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 simpa_examples/three_vs_two_dimensional_simulation_example.py diff --git a/simpa_examples/three_vs_two_dimensional_simulation_example.py b/simpa_examples/three_vs_two_dimensional_simulation_example.py new file mode 100644 index 00000000..0c88bc2a --- /dev/null +++ b/simpa_examples/three_vs_two_dimensional_simulation_example.py @@ -0,0 +1,212 @@ +# SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ +# SPDX-FileCopyrightText: 2021 Janek Groehl +# SPDX-License-Identifier: MIT + +from simpa import Tags +import simpa as sp +import numpy as np +from simpa.utils.profiling import profile +from argparse import ArgumentParser +import matplotlib.pyplot as plt + +# FIXME temporary workaround for newest Intel architectures +import os +os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" + +# TODO: Please make sure that a valid path_config.env file is located in your home directory, or that you +# point to the correct file in the PathManager(). + + +@profile +def run_3Dvs2D_simulation_example(spacing: float | int = 0.2, path_manager=None, + visualise: bool = True): + """ + + :param spacing: The simulation spacing between voxels + :param path_manager: the path manager to be used, typically sp.PathManager + :param visualise: If VISUALIZE is set to True, the reconstruction result will be plotted + :return: a run through of the example + """ + + def run_sim(run_3D:bool = True, path_manager=path_manager): + if path_manager is None: + path_manager = sp.PathManager() + VOLUME_TRANSDUCER_DIM_IN_MM = 75 + VOLUME_PLANAR_DIM_IN_MM = 20 + VOLUME_HEIGHT_IN_MM = 25 + RANDOM_SEED = 4711 + + # If VISUALIZE is set to True, the simulation result will be plotted + VISUALIZE = True + + def create_example_tissue(): + """ + This is a very simple example script of how to create a tissue definition. + It contains a muscular background, an epidermis layer on top of the muscles + and a blood vessel. + """ + background_dictionary = sp.Settings() + background_dictionary[Tags.MOLECULE_COMPOSITION] = sp.TISSUE_LIBRARY.constant(1e-10, 1e-10, 1.0) + background_dictionary[Tags.STRUCTURE_TYPE] = Tags.BACKGROUND + + tissue_dict = sp.Settings() + tissue_dict[Tags.BACKGROUND] = background_dictionary + tissue_dict["muscle"] = sp.define_horizontal_layer_structure_settings(z_start_mm=0, thickness_mm=100, + molecular_composition=sp.TISSUE_LIBRARY.constant( + 0.05, 100, 0.9), + priority=1, + consider_partial_volume=True, + adhere_to_deformation=True) + tissue_dict["epidermis"] = sp.define_horizontal_layer_structure_settings(z_start_mm=1, thickness_mm=0.1, + molecular_composition=sp.TISSUE_LIBRARY.epidermis(), + priority=8, + consider_partial_volume=True, + adhere_to_deformation=True) + tissue_dict["vessel_1"] = sp.define_circular_tubular_structure_settings( + tube_start_mm=[VOLUME_TRANSDUCER_DIM_IN_MM/2 - 10, 0, 5], + tube_end_mm=[VOLUME_TRANSDUCER_DIM_IN_MM/2 - 10, VOLUME_PLANAR_DIM_IN_MM, 5], + molecular_composition=sp.TISSUE_LIBRARY.blood(), + radius_mm=2, priority=3, consider_partial_volume=True, + adhere_to_deformation=False + ) + tissue_dict["vessel_2"] = sp.define_circular_tubular_structure_settings( + tube_start_mm=[VOLUME_TRANSDUCER_DIM_IN_MM/2, 0, 10], + tube_end_mm=[VOLUME_TRANSDUCER_DIM_IN_MM/2, VOLUME_PLANAR_DIM_IN_MM, 10], + molecular_composition=sp.TISSUE_LIBRARY.blood(), + radius_mm=3, priority=3, consider_partial_volume=True, + adhere_to_deformation=False + ) + return tissue_dict + + # Seed the numpy random configuration prior to creating the global_settings file in + # order to ensure that the same volume + # is generated with the same random seed every time. + + np.random.seed(RANDOM_SEED) + + general_settings = { + # These parameters set the general properties of the simulated volume + Tags.RANDOM_SEED: RANDOM_SEED, + Tags.VOLUME_NAME: f"2Dvs3D_3D{run_3D}_" + str(RANDOM_SEED), + Tags.SIMULATION_PATH: path_manager.get_hdf5_file_save_path(), + Tags.SPACING_MM: spacing, + Tags.DIM_VOLUME_Z_MM: VOLUME_HEIGHT_IN_MM, + Tags.DIM_VOLUME_X_MM: VOLUME_TRANSDUCER_DIM_IN_MM, + Tags.DIM_VOLUME_Y_MM: VOLUME_PLANAR_DIM_IN_MM, + Tags.VOLUME_CREATOR: Tags.VOLUME_CREATOR_VERSATILE, + Tags.GPU: True, + Tags.WAVELENGTHS: [800], + Tags.DO_FILE_COMPRESSION: True, + Tags.DO_IPASC_EXPORT: True + } + settings = sp.Settings(general_settings) + np.random.seed(RANDOM_SEED) + + settings.set_volume_creation_settings({ + Tags.STRUCTURES: create_example_tissue(), + Tags.SIMULATE_DEFORMED_LAYERS: True + }) + + settings.set_optical_settings({ + Tags.OPTICAL_MODEL_NUMBER_PHOTONS: 1e7, + Tags.OPTICAL_MODEL_BINARY_PATH: path_manager.get_mcx_binary_path(), + Tags.ILLUMINATION_TYPE: Tags.ILLUMINATION_TYPE_MSOT_ACUITY_ECHO, + Tags.LASER_PULSE_ENERGY_IN_MILLIJOULE: 50, + Tags.MCX_ASSUMED_ANISOTROPY: 0.9, + Tags.ADDITIONAL_FLAGS: ['--printgpu'] # to print MCX GPU information + }) + + settings.set_acoustic_settings({ + Tags.ACOUSTIC_SIMULATION_3D: run_3D, + Tags.ACOUSTIC_MODEL_BINARY_PATH: path_manager.get_matlab_binary_path(), + Tags.KWAVE_PROPERTY_ALPHA_POWER: 0.00, + Tags.KWAVE_PROPERTY_SENSOR_RECORD: "p", + Tags.KWAVE_PROPERTY_PMLInside: False, + Tags.KWAVE_PROPERTY_PMLSize: [31, 32], + Tags.KWAVE_PROPERTY_PMLAlpha: 1.5, + Tags.KWAVE_PROPERTY_PlotPML: False, + Tags.RECORDMOVIE: False, + Tags.MOVIENAME: "visualization_log", + Tags.ACOUSTIC_LOG_SCALE: True + }) + + settings.set_reconstruction_settings({ + Tags.RECONSTRUCTION_PERFORM_BANDPASS_FILTERING: False, + Tags.ACOUSTIC_MODEL_BINARY_PATH: path_manager.get_matlab_binary_path(), + Tags.ACOUSTIC_SIMULATION_3D: run_3D, + Tags.KWAVE_PROPERTY_ALPHA_POWER: 0.00, + Tags.TUKEY_WINDOW_ALPHA: 0.5, + Tags.BANDPASS_CUTOFF_LOWPASS_IN_HZ: int(8e6), + Tags.BANDPASS_CUTOFF_HIGHPASS_IN_HZ: int(0.1e4), + Tags.RECONSTRUCTION_BMODE_AFTER_RECONSTRUCTION: False, + Tags.RECONSTRUCTION_BMODE_METHOD: Tags.RECONSTRUCTION_BMODE_METHOD_HILBERT_TRANSFORM, + Tags.RECONSTRUCTION_APODIZATION_METHOD: Tags.RECONSTRUCTION_APODIZATION_BOX, + Tags.RECONSTRUCTION_MODE: Tags.RECONSTRUCTION_MODE_PRESSURE, + Tags.KWAVE_PROPERTY_SENSOR_RECORD: "p", + Tags.KWAVE_PROPERTY_PMLInside: False, + Tags.KWAVE_PROPERTY_PMLSize: [31, 32], + Tags.KWAVE_PROPERTY_PMLAlpha: 1.5, + Tags.KWAVE_PROPERTY_PlotPML: False, + Tags.RECORDMOVIE: False, + Tags.MOVIENAME: "visualization_log", + Tags.ACOUSTIC_LOG_SCALE: True, + Tags.DATA_FIELD_SPEED_OF_SOUND: 1540, + Tags.DATA_FIELD_ALPHA_COEFF: 0.01, + Tags.DATA_FIELD_DENSITY: 1000, + Tags.SPACING_MM: spacing + }) + + device = sp.PhotoacousticDevice(device_position_mm=np.array([VOLUME_TRANSDUCER_DIM_IN_MM/2, + VOLUME_PLANAR_DIM_IN_MM/2, + 0]), + field_of_view_extent_mm=np.asarray([-15, 15, 0, 0, 0, 20])) + device.set_detection_geometry(sp.LinearArrayDetectionGeometry(device_position_mm=device.device_position_mm, + pitch_mm=0.25, + number_detector_elements=100, + field_of_view_extent_mm=np.asarray([-15, 15, 0, 0, 0, 20]))) + device.add_illumination_geometry(sp.SlitIlluminationGeometry(slit_vector_mm=[100, 0, 0])) + + SIMULATION_PIPELINE = [ + sp.ModelBasedAdapter(settings), + sp.MCXAdapter(settings), + sp.KWaveAdapter(settings), + sp.TimeReversalAdapter(settings), + sp.FieldOfViewCropping(settings) + ] + + sp.simulate(SIMULATION_PIPELINE, settings, device) + + if Tags.WAVELENGTH in settings: + WAVELENGTH = settings[Tags.WAVELENGTH] + else: + WAVELENGTH = 800 + + return (sp.load_data_field(settings[Tags.SIMPA_OUTPUT_PATH], sp.Tags.DATA_FIELD_TIME_SERIES_DATA, WAVELENGTH), + sp.load_data_field(settings[Tags.SIMPA_OUTPUT_PATH], sp.Tags.DATA_FIELD_RECONSTRUCTED_DATA, WAVELENGTH)) + + two_d_time_series, two_d_recon = run_sim(False) + three_d_time_series, three_d_recon = run_sim(True) + + if visualise: + fig, (ax1, ax2, ax3) = plt.subplots(1, 3, layout="constrained", figsize=(12, 4)) + + ax1.imshow(two_d_recon.T) + ax1.set_title("2D Simulation") + ax2.imshow(three_d_recon.T) + ax2.set_title("3D Simulation") + ax3.plot(two_d_time_series[49], label="2D simulation") + ax3.plot(three_d_time_series[49], label="3D simulation") + plt.legend() + + plt.show() + + +if __name__ == "__main__": + parser = ArgumentParser(description='Run the optical and acoustic simulation example') + parser.add_argument("--spacing", default=0.25, type=float, help='the voxel spacing in mm') + parser.add_argument("--path_manager", default=None, help='the path manager, None uses sp.PathManager') + parser.add_argument("--visualise", default=True, type=bool, help='whether to visualise the result') + config = parser.parse_args() + + run_3Dvs2D_simulation_example(spacing=config.spacing, path_manager=config.path_manager, + visualise=config.visualise) From a769052dca8af91f56c9e12ecd8773a92596f197 Mon Sep 17 00:00:00 2001 From: Janek Grohl Date: Tue, 13 Aug 2024 14:58:18 +0100 Subject: [PATCH 08/35] add some minor documentation --- .../three_vs_two_dimensional_simulation_example.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/simpa_examples/three_vs_two_dimensional_simulation_example.py b/simpa_examples/three_vs_two_dimensional_simulation_example.py index 0c88bc2a..824445af 100644 --- a/simpa_examples/three_vs_two_dimensional_simulation_example.py +++ b/simpa_examples/three_vs_two_dimensional_simulation_example.py @@ -1,5 +1,5 @@ -# SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ -# SPDX-FileCopyrightText: 2021 Janek Groehl +# SPDX-FileCopyrightText: 2024 Division of Intelligent Medical Systems, DKFZ +# SPDX-FileCopyrightText: 2024 Janek Groehl # SPDX-License-Identifier: MIT from simpa import Tags @@ -22,10 +22,12 @@ def run_3Dvs2D_simulation_example(spacing: float | int = 0.2, path_manager=None, visualise: bool = True): """ + A run through of the 3D vs 2D example. In this example, the same simulation script is run twice, with the + only difference being the simulation run is either 2D or 3D. + :param spacing: The simulation spacing between voxels :param path_manager: the path manager to be used, typically sp.PathManager - :param visualise: If VISUALIZE is set to True, the reconstruction result will be plotted - :return: a run through of the example + :param visualise: If visualise is set to True, the result swill be plotted using matplotlib """ def run_sim(run_3D:bool = True, path_manager=path_manager): From 0e3761bffe97798a33ba85fc64470b7d1c51e2b7 Mon Sep 17 00:00:00 2001 From: Janek Grohl Date: Tue, 13 Aug 2024 15:23:07 +0100 Subject: [PATCH 09/35] let the 2D vs 3D example run during benchmarking --- simpa_examples/__init__.py | 1 + simpa_examples/benchmarking/performance_check.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/simpa_examples/__init__.py b/simpa_examples/__init__.py index 49f13e4d..c6335f02 100644 --- a/simpa_examples/__init__.py +++ b/simpa_examples/__init__.py @@ -10,3 +10,4 @@ from simpa_examples.perform_image_reconstruction import run_perform_image_reconstruction from simpa_examples.perform_iterative_qPAI_reconstruction import run_perform_iterative_qPAI_reconstruction from simpa_examples.segmentation_loader import run_segmentation_loader +from simpa_examples.three_vs_two_dimensional_simulation_example import run_3Dvs2D_simulation_example diff --git a/simpa_examples/benchmarking/performance_check.py b/simpa_examples/benchmarking/performance_check.py index f933f2de..c98e445c 100644 --- a/simpa_examples/benchmarking/performance_check.py +++ b/simpa_examples/benchmarking/performance_check.py @@ -31,7 +31,8 @@ def run_benchmarking_tests(spacing=0.4, profile: str = "TIME", savefolder: str = examples = [simpa_examples.run_minimal_optical_simulation, simpa_examples.run_minimal_optical_simulation_uniform_cube, simpa_examples.run_optical_and_acoustic_simulation, - simpa_examples.run_segmentation_loader] + simpa_examples.run_segmentation_loader, + simpa_examples.run_3Dvs2D_simulation_example] for example in examples: example(spacing=spacing, path_manager=None, visualise=False) From b735d38bf4347d8655add5298f831dcc5972e32a Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Tue, 13 Aug 2024 17:38:13 +0200 Subject: [PATCH 10/35] Fixed multiple bugs in benchmarking --- ...ice_digital_twins.detection_geometries.rst | 6 ++++ ..._digital_twins.illumination_geometries.rst | 6 ++++ ...a.core.device_digital_twins.pa_devices.rst | 6 ++++ .../simpa.core.device_digital_twins.rst | 5 +++ ...re.processing_components.multispectral.rst | 6 ++++ .../simpa.core.processing_components.rst | 5 +++ docs/source/simpa.core.rst | 6 ++++ ...mulation_modules.reconstruction_module.rst | 18 ++++++---- docs/source/simpa.core.simulation_modules.rst | 9 +++-- ...ulation_modules.volume_creation_module.rst | 10 ++++-- docs/source/simpa_examples.rst | 1 + .../simulation_module_base.py | 2 +- simpa_examples/__init__.py | 10 ------ .../benchmarking/extract_benchmarking_data.py | 34 +++++++++---------- .../benchmarking/performance_check.py | 19 +++++++---- simpa_examples/linear_unmixing.py | 1 - simpa_examples/msot_invision_simulation.py | 1 - .../perform_iterative_qPAI_reconstruction.py | 1 - ...e_vs_two_dimensional_simulation_example.py | 6 +++- .../automatic_tests/test_additional_flags.py | 27 ++++++++++----- 20 files changed, 121 insertions(+), 58 deletions(-) diff --git a/docs/source/simpa.core.device_digital_twins.detection_geometries.rst b/docs/source/simpa.core.device_digital_twins.detection_geometries.rst index 3eb9caa8..413d22fa 100644 --- a/docs/source/simpa.core.device_digital_twins.detection_geometries.rst +++ b/docs/source/simpa.core.device_digital_twins.detection_geometries.rst @@ -12,6 +12,12 @@ detection\_geometries :show-inheritance: +.. automodule:: simpa.core.device_digital_twins.detection_geometries.detection_geometry_base + :members: + :undoc-members: + :show-inheritance: + + .. automodule:: simpa.core.device_digital_twins.detection_geometries.linear_array :members: :undoc-members: diff --git a/docs/source/simpa.core.device_digital_twins.illumination_geometries.rst b/docs/source/simpa.core.device_digital_twins.illumination_geometries.rst index eb29ecbd..b398a489 100644 --- a/docs/source/simpa.core.device_digital_twins.illumination_geometries.rst +++ b/docs/source/simpa.core.device_digital_twins.illumination_geometries.rst @@ -18,6 +18,12 @@ illumination\_geometries :show-inheritance: +.. automodule:: simpa.core.device_digital_twins.illumination_geometries.illumination_geometry_base + :members: + :undoc-members: + :show-inheritance: + + .. automodule:: simpa.core.device_digital_twins.illumination_geometries.ithera_msot_acuity_illumination :members: :undoc-members: diff --git a/docs/source/simpa.core.device_digital_twins.pa_devices.rst b/docs/source/simpa.core.device_digital_twins.pa_devices.rst index dd796554..462b9051 100644 --- a/docs/source/simpa.core.device_digital_twins.pa_devices.rst +++ b/docs/source/simpa.core.device_digital_twins.pa_devices.rst @@ -22,3 +22,9 @@ pa\_devices :members: :undoc-members: :show-inheritance: + + +.. automodule:: simpa.core.device_digital_twins.pa_devices.photoacoustic_device + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/simpa.core.device_digital_twins.rst b/docs/source/simpa.core.device_digital_twins.rst index 5b25dcc9..a8aeb366 100644 --- a/docs/source/simpa.core.device_digital_twins.rst +++ b/docs/source/simpa.core.device_digital_twins.rst @@ -12,3 +12,8 @@ device\_digital\_twins simpa.core.device_digital_twins.detection_geometries simpa.core.device_digital_twins.illumination_geometries simpa.core.device_digital_twins.pa_devices + +.. automodule:: simpa.core.device_digital_twins.digital_device_twin_base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/simpa.core.processing_components.multispectral.rst b/docs/source/simpa.core.processing_components.multispectral.rst index ef6f10e1..9a2e368b 100644 --- a/docs/source/simpa.core.processing_components.multispectral.rst +++ b/docs/source/simpa.core.processing_components.multispectral.rst @@ -10,3 +10,9 @@ multispectral :members: :undoc-members: :show-inheritance: + + +.. automodule:: simpa.core.processing_components.multispectral.multispectral_processing_algorithm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/simpa.core.processing_components.rst b/docs/source/simpa.core.processing_components.rst index 7780b57d..52a88285 100644 --- a/docs/source/simpa.core.processing_components.rst +++ b/docs/source/simpa.core.processing_components.rst @@ -11,3 +11,8 @@ processing\_components simpa.core.processing_components.monospectral simpa.core.processing_components.multispectral + +.. automodule:: simpa.core.processing_components.processing_component_base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/simpa.core.rst b/docs/source/simpa.core.rst index e86ea34b..78a560aa 100644 --- a/docs/source/simpa.core.rst +++ b/docs/source/simpa.core.rst @@ -13,6 +13,12 @@ core simpa.core.processing_components simpa.core.simulation_modules +.. automodule:: simpa.core.pipeline_element_base + :members: + :undoc-members: + :show-inheritance: + + .. automodule:: simpa.core.simulation :members: :undoc-members: diff --git a/docs/source/simpa.core.simulation_modules.reconstruction_module.rst b/docs/source/simpa.core.simulation_modules.reconstruction_module.rst index f14dd281..a875b39f 100644 --- a/docs/source/simpa.core.simulation_modules.reconstruction_module.rst +++ b/docs/source/simpa.core.simulation_modules.reconstruction_module.rst @@ -6,37 +6,43 @@ reconstruction\_module :undoc-members: :show-inheritance: -.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_module_delay_and_sum_adapter +.. automodule:: simpa.core.simulation_modules.reconstruction_module.delay_and_sum_adapter :members: :undoc-members: :show-inheritance: -.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_module_delay_multiply_and_sum_adapter +.. automodule:: simpa.core.simulation_modules.reconstruction_module.delay_multiply_and_sum_adapter :members: :undoc-members: :show-inheritance: -.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_module_signed_delay_multiply_and_sum_adapter +.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_adapter_base :members: :undoc-members: :show-inheritance: -.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_module_test_adapter +.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_test_adapter :members: :undoc-members: :show-inheritance: -.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_module_time_reversal_adapter +.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_utils :members: :undoc-members: :show-inheritance: -.. automodule:: simpa.core.simulation_modules.reconstruction_module.reconstruction_utils +.. automodule:: simpa.core.simulation_modules.reconstruction_module.signed_delay_multiply_and_sum_adapter + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: simpa.core.simulation_modules.reconstruction_module.time_reversal_adapter :members: :undoc-members: :show-inheritance: diff --git a/docs/source/simpa.core.simulation_modules.rst b/docs/source/simpa.core.simulation_modules.rst index 6ffa2e19..f2f2e85e 100644 --- a/docs/source/simpa.core.simulation_modules.rst +++ b/docs/source/simpa.core.simulation_modules.rst @@ -9,7 +9,12 @@ simulation\_modules .. toctree:: :maxdepth: 4 - simpa.core.simulation_modules.acoustic_forward_module - simpa.core.simulation_modules.optical_simulation_module + simpa.core.simulation_modules.acoustic_module + simpa.core.simulation_modules.optical_module simpa.core.simulation_modules.reconstruction_module simpa.core.simulation_modules.volume_creation_module + +.. automodule:: simpa.core.simulation_modules.simulation_module_base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/simpa.core.simulation_modules.volume_creation_module.rst b/docs/source/simpa.core.simulation_modules.volume_creation_module.rst index 8fda1e0b..acf284c9 100644 --- a/docs/source/simpa.core.simulation_modules.volume_creation_module.rst +++ b/docs/source/simpa.core.simulation_modules.volume_creation_module.rst @@ -6,13 +6,19 @@ volume\_creation\_module :undoc-members: :show-inheritance: -.. automodule:: simpa.core.simulation_modules.volume_creation_module.volume_creation_module_model_based_adapter +.. automodule:: simpa.core.simulation_modules.volume_creation_module.model_based_adapter :members: :undoc-members: :show-inheritance: -.. automodule:: simpa.core.simulation_modules.volume_creation_module.volume_creation_module_segmentation_based_adapter +.. automodule:: simpa.core.simulation_modules.volume_creation_module.segmentation_based_adapter + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: simpa.core.simulation_modules.volume_creation_module.volume_creation_adapter_base :members: :undoc-members: :show-inheritance: diff --git a/docs/source/simpa_examples.rst b/docs/source/simpa_examples.rst index 9d229be6..06c1cb14 100644 --- a/docs/source/simpa_examples.rst +++ b/docs/source/simpa_examples.rst @@ -15,3 +15,4 @@ simpa\_examples perform_image_reconstruction perform_iterative_qPAI_reconstruction segmentation_loader + three_vs_two_dimensional_simulation_example diff --git a/simpa/core/simulation_modules/simulation_module_base.py b/simpa/core/simulation_modules/simulation_module_base.py index 1ac50a33..39415038 100644 --- a/simpa/core/simulation_modules/simulation_module_base.py +++ b/simpa/core/simulation_modules/simulation_module_base.py @@ -41,4 +41,4 @@ def get_additional_flags(self) -> List[str]: if Tags.ADDITIONAL_FLAGS in self.component_settings: for flag in self.component_settings[Tags.ADDITIONAL_FLAGS]: cmd.append(str(flag)) - return cmd \ No newline at end of file + return cmd diff --git a/simpa_examples/__init__.py b/simpa_examples/__init__.py index c6335f02..89cc8954 100644 --- a/simpa_examples/__init__.py +++ b/simpa_examples/__init__.py @@ -1,13 +1,3 @@ # SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ # SPDX-FileCopyrightText: 2021 Janek Groehl # SPDX-License-Identifier: MIT - -from simpa_examples.linear_unmixing import run_linear_unmixing -from simpa_examples.minimal_optical_simulation import run_minimal_optical_simulation -from simpa_examples.minimal_optical_simulation_uniform_cube import run_minimal_optical_simulation_uniform_cube -from simpa_examples.msot_invision_simulation import run_msot_invision_simulation -from simpa_examples.optical_and_acoustic_simulation import run_optical_and_acoustic_simulation -from simpa_examples.perform_image_reconstruction import run_perform_image_reconstruction -from simpa_examples.perform_iterative_qPAI_reconstruction import run_perform_iterative_qPAI_reconstruction -from simpa_examples.segmentation_loader import run_segmentation_loader -from simpa_examples.three_vs_two_dimensional_simulation_example import run_3Dvs2D_simulation_example diff --git a/simpa_examples/benchmarking/extract_benchmarking_data.py b/simpa_examples/benchmarking/extract_benchmarking_data.py index 452b92e9..0b102a6d 100644 --- a/simpa_examples/benchmarking/extract_benchmarking_data.py +++ b/simpa_examples/benchmarking/extract_benchmarking_data.py @@ -67,17 +67,21 @@ def read_out_benchmarking_data(profiles: list = None, start: float = .2, stop: f else: break - benchmarking_file = open(file_name, 'r') - lines_with_sp_simulate = lines_that_contain("sp.simulate", benchmarking_file) + with open(file_name, 'r') as benchmarking_file: + lines_with_sp_simulate = lines_that_contain("sp.simulate", benchmarking_file) - examples_counter = 0 - for lwss in lines_with_sp_simulate: + for e_idx, example in enumerate(current_examples): try: - value = float(lwss[info_starts[profile]:info_ends[profile]]) + value = float(lines_with_sp_simulate[e_idx][info_starts[profile]:info_ends[profile]]) + unit = str(lines_with_sp_simulate[e_idx][info_ends[profile]]) except ValueError: - continue + with open(file_name, 'r') as benchmarking_file: + lines_with_run_sim = lines_that_contain("= run_sim", benchmarking_file) + value = 0 + for line in lines_with_run_sim: + value += float(line[info_starts[profile]:info_ends[profile]]) + unit = str(line[info_ends[profile]]) - unit = str(lwss[info_ends[profile]]) if profile == "TIME": value_with_unit = value else: @@ -90,14 +94,8 @@ def read_out_benchmarking_data(profiles: list = None, start: float = .2, stop: f else: raise ImportError(f'Unit {unit} not supported') - example = current_examples[examples_counter] benchmarking_lists.append([example, spacing, profile, value_with_unit]) - # lets you know which example you are on - examples_counter += 1 - if examples_counter == len(current_examples): - examples_counter = 0 - # creating data frame new_df = pd.DataFrame(benchmarking_lists, columns=['Example', 'Spacing', 'Profile', 'Value']) new_df.astype(dtype={"Example": "str", "Spacing": "float64", "Profile": "str", "Value": "float64"}) @@ -112,11 +110,11 @@ def read_out_benchmarking_data(profiles: list = None, start: float = .2, stop: f if __name__ == "__main__": parser = ArgumentParser(description='Run benchmarking tests') - parser.add_argument("--start", default=.2, + parser.add_argument("--start", default=.15, help='start spacing default .2mm') - parser.add_argument("--stop", default=.4, + parser.add_argument("--stop", default=.25, help='stop spacing default .4mm') - parser.add_argument("--step", default=.1, + parser.add_argument("--step", default=.05, help='step size mm') parser.add_argument("--profiles", default=None, type=str, help='the profile to run') @@ -124,7 +122,9 @@ def read_out_benchmarking_data(profiles: list = None, start: float = .2, stop: f config = parser.parse_args() profiles = config.profiles - if profiles: + if profiles and "%" in profiles: profiles = profiles.split('%')[:-1] + elif profiles: + profiles = [profiles] read_out_benchmarking_data(start=float(config.start), stop=float(config.stop), step=float(config.step), profiles=profiles, savefolder=config.savefolder) diff --git a/simpa_examples/benchmarking/performance_check.py b/simpa_examples/benchmarking/performance_check.py index c98e445c..ef58fc72 100644 --- a/simpa_examples/benchmarking/performance_check.py +++ b/simpa_examples/benchmarking/performance_check.py @@ -26,13 +26,18 @@ def run_benchmarking_tests(spacing=0.4, profile: str = "TIME", savefolder: str = elif len(savefolder) > 0: os.environ["SIMPA_PROFILE_SAVE_FILE"] = savefolder+"/benchmarking_data_"+profile+"_"+str(spacing)+".txt" - import simpa_examples - - examples = [simpa_examples.run_minimal_optical_simulation, - simpa_examples.run_minimal_optical_simulation_uniform_cube, - simpa_examples.run_optical_and_acoustic_simulation, - simpa_examples.run_segmentation_loader, - simpa_examples.run_3Dvs2D_simulation_example] + from simpa_examples.minimal_optical_simulation import run_minimal_optical_simulation + from simpa_examples.minimal_optical_simulation_uniform_cube import run_minimal_optical_simulation_uniform_cube + from simpa_examples.optical_and_acoustic_simulation import run_optical_and_acoustic_simulation + from simpa_examples.segmentation_loader import run_segmentation_loader + from simpa_examples.three_vs_two_dimensional_simulation_example import run_3Dvs2D_simulation_example + + examples = [run_minimal_optical_simulation, + run_minimal_optical_simulation_uniform_cube, + run_optical_and_acoustic_simulation, + run_segmentation_loader, + run_3Dvs2D_simulation_example + ] for example in examples: example(spacing=spacing, path_manager=None, visualise=False) diff --git a/simpa_examples/linear_unmixing.py b/simpa_examples/linear_unmixing.py index e1b78f7f..a0ffb8d6 100644 --- a/simpa_examples/linear_unmixing.py +++ b/simpa_examples/linear_unmixing.py @@ -15,7 +15,6 @@ os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" -@profile def run_linear_unmixing(spacing: float | int = 0.25, path_manager=None, visualise: bool = True): """ diff --git a/simpa_examples/msot_invision_simulation.py b/simpa_examples/msot_invision_simulation.py index d7eb2cb6..6676de5a 100644 --- a/simpa_examples/msot_invision_simulation.py +++ b/simpa_examples/msot_invision_simulation.py @@ -11,7 +11,6 @@ path_manager = sp.PathManager() -@profile def run_msot_invision_simulation(spacing: float | int = 0.5, path_manager=None, visualise: bool = True): """ diff --git a/simpa_examples/perform_iterative_qPAI_reconstruction.py b/simpa_examples/perform_iterative_qPAI_reconstruction.py index 784e741b..7a299733 100644 --- a/simpa_examples/perform_iterative_qPAI_reconstruction.py +++ b/simpa_examples/perform_iterative_qPAI_reconstruction.py @@ -22,7 +22,6 @@ # point to the correct file in the PathManager(). -@profile def run_perform_iterative_qPAI_reconstruction(spacing: float | int = 0.2, path_manager=None, visualise: bool = True): """ diff --git a/simpa_examples/three_vs_two_dimensional_simulation_example.py b/simpa_examples/three_vs_two_dimensional_simulation_example.py index 824445af..4c08bf19 100644 --- a/simpa_examples/three_vs_two_dimensional_simulation_example.py +++ b/simpa_examples/three_vs_two_dimensional_simulation_example.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ +# SPDX-FileCopyrightText: 2021 Janek Groehl +# SPDX-License-Identifier: MIT + # SPDX-FileCopyrightText: 2024 Division of Intelligent Medical Systems, DKFZ # SPDX-FileCopyrightText: 2024 Janek Groehl # SPDX-License-Identifier: MIT @@ -30,7 +34,7 @@ def run_3Dvs2D_simulation_example(spacing: float | int = 0.2, path_manager=None, :param visualise: If visualise is set to True, the result swill be plotted using matplotlib """ - def run_sim(run_3D:bool = True, path_manager=path_manager): + def run_sim(run_3D: bool = True, path_manager=path_manager): if path_manager is None: path_manager = sp.PathManager() VOLUME_TRANSDUCER_DIM_IN_MM = 75 diff --git a/simpa_tests/automatic_tests/test_additional_flags.py b/simpa_tests/automatic_tests/test_additional_flags.py index c8ad88b1..95e6d078 100644 --- a/simpa_tests/automatic_tests/test_additional_flags.py +++ b/simpa_tests/automatic_tests/test_additional_flags.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ +# SPDX-FileCopyrightText: 2021 Janek Groehl +# SPDX-License-Identifier: MIT + import unittest import numpy as np @@ -9,17 +13,18 @@ class TestAdditionalFlags(unittest.TestCase): def setUp(self) -> None: self.additional_flags = ('-l', '-a') self.settings = Settings() - + def test_get_cmd_mcx_reflectance_adapter(self): self.settings.set_optical_settings({ Tags.OPTICAL_MODEL_BINARY_PATH: '.', Tags.ADDITIONAL_FLAGS: self.additional_flags }) - mcx_reflectance_adapter = MCXReflectanceAdapter(global_settings=self.settings) + mcx_reflectance_adapter = MCXReflectanceAdapter(global_settings=self.settings) cmd = mcx_reflectance_adapter.get_command() for flag in self.additional_flags: - self.assertIn(flag, cmd, f"{flag} was not in command returned by mcx reflectance adapter but was defined as additional flag") - + self.assertIn( + flag, cmd, f"{flag} was not in command returned by mcx reflectance adapter but was defined as additional flag") + def test_get_cmd_mcx_adapter(self): self.settings.set_optical_settings({ Tags.OPTICAL_MODEL_BINARY_PATH: '.', @@ -28,8 +33,9 @@ def test_get_cmd_mcx_adapter(self): mcx_adapter = MCXAdapter(global_settings=self.settings) cmd = mcx_adapter.get_command() for flag in self.additional_flags: - self.assertIn(flag, cmd, f"{flag} was not in command returned by mcx adapter but was defined as additional flag") - + self.assertIn( + flag, cmd, f"{flag} was not in command returned by mcx adapter but was defined as additional flag") + def test_get_cmd_kwave_adapter(self): self.settings.set_acoustic_settings({ Tags.ADDITIONAL_FLAGS: self.additional_flags @@ -37,16 +43,19 @@ def test_get_cmd_kwave_adapter(self): kwave_adapter = KWaveAdapter(global_settings=self.settings) cmd = generate_matlab_cmd("./matlab.exe", "simulate_2D.m", "my_hdf5.mat", kwave_adapter.get_additional_flags()) for flag in self.additional_flags: - self.assertIn(flag, cmd, f"{flag} was not in command returned by kwave adapter but was defined as additional flag") + self.assertIn( + flag, cmd, f"{flag} was not in command returned by kwave adapter but was defined as additional flag") def test_get_cmd_time_reversal_adapter(self): self.settings.set_reconstruction_settings({ Tags.ADDITIONAL_FLAGS: self.additional_flags }) time_reversal_adapter = TimeReversalAdapter(global_settings=self.settings) - cmd = generate_matlab_cmd("./matlab.exe", "time_reversal_2D.m", "my_hdf5.mat", time_reversal_adapter.get_additional_flags()) + cmd = generate_matlab_cmd("./matlab.exe", "time_reversal_2D.m", "my_hdf5.mat", + time_reversal_adapter.get_additional_flags()) for flag in self.additional_flags: - self.assertIn(flag, cmd, f"{flag} was not in command returned by time reversal adapter but was defined as additional flag") + self.assertIn( + flag, cmd, f"{flag} was not in command returned by time reversal adapter but was defined as additional flag") if __name__ == '__main__': From fad5015a9feb6f649fae905304e2c991ad422bfd Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Thu, 15 Aug 2024 09:50:28 +0200 Subject: [PATCH 11/35] Refactored new example for benchmarking --- simpa_examples/benchmarking/performance_check.py | 5 +++-- .../three_vs_two_dimensional_simulation_example.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/simpa_examples/benchmarking/performance_check.py b/simpa_examples/benchmarking/performance_check.py index ef58fc72..ac3bd7be 100644 --- a/simpa_examples/benchmarking/performance_check.py +++ b/simpa_examples/benchmarking/performance_check.py @@ -30,13 +30,14 @@ def run_benchmarking_tests(spacing=0.4, profile: str = "TIME", savefolder: str = from simpa_examples.minimal_optical_simulation_uniform_cube import run_minimal_optical_simulation_uniform_cube from simpa_examples.optical_and_acoustic_simulation import run_optical_and_acoustic_simulation from simpa_examples.segmentation_loader import run_segmentation_loader - from simpa_examples.three_vs_two_dimensional_simulation_example import run_3Dvs2D_simulation_example + from simpa_examples.three_vs_two_dimensional_simulation_example import ( + run_three_vs_two_dimensional_simulation_example) examples = [run_minimal_optical_simulation, run_minimal_optical_simulation_uniform_cube, run_optical_and_acoustic_simulation, run_segmentation_loader, - run_3Dvs2D_simulation_example + run_three_vs_two_dimensional_simulation_example ] for example in examples: diff --git a/simpa_examples/three_vs_two_dimensional_simulation_example.py b/simpa_examples/three_vs_two_dimensional_simulation_example.py index 4c08bf19..49aa79b7 100644 --- a/simpa_examples/three_vs_two_dimensional_simulation_example.py +++ b/simpa_examples/three_vs_two_dimensional_simulation_example.py @@ -22,8 +22,8 @@ @profile -def run_3Dvs2D_simulation_example(spacing: float | int = 0.2, path_manager=None, - visualise: bool = True): +def run_three_vs_two_dimensional_simulation_example(spacing: float | int = 0.2, path_manager=None, + visualise: bool = True): """ A run through of the 3D vs 2D example. In this example, the same simulation script is run twice, with the From a8ccd5f64086baa8365e32b396ea7a274339f656 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Thu, 15 Aug 2024 10:07:00 +0200 Subject: [PATCH 12/35] Add nice formatting for benchmarking results --- pyproject.toml | 14 ++++++++------ simpa_examples/benchmarking/get_final_table.py | 3 +++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fc64165c..657a0551 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,14 +38,16 @@ dependencies = [ [project.optional-dependencies] docs = [ - "sphinx-rtd-theme>=2.0.0,<3.0.0", - "Sphinx>=5.1.1,<6.0.0", - "myst-parser>=0.18.0,<1.1" + "sphinx-rtd-theme>=2.0.0,<3.0.0", # Uses MIT-License (MIT compatible) + "Sphinx>=5.1.1,<6.0.0", # Uses BSD-License (MIT compatible) + "myst-parser>=0.18.0,<1.1" # Uses MIT-License (MIT compatible) ] profile = [ - "pytorch_memlab>=0.3.0,<0.4.0", - "line_profiler>=4.0.0,<5.0.0", - "memory_profiler>=0.61.0,<0.62.0" + "pytorch_memlab>=0.3.0", # Uses MIT-License (MIT compatible) + "line_profiler>=4.0.0", # Uses BSD-License (MIT compatible) + "memory_profiler>=0.61.0,<0.62.0", # Uses BSD-License (MIT compatible) + "tabulate>=0.9.0" # Uses BSD-License (MIT compatible) + ] testing = [ "mdutils>=1.4.0", # Uses MIT-License (MIT compatible) diff --git a/simpa_examples/benchmarking/get_final_table.py b/simpa_examples/benchmarking/get_final_table.py index 8bc84484..fc969dfe 100644 --- a/simpa_examples/benchmarking/get_final_table.py +++ b/simpa_examples/benchmarking/get_final_table.py @@ -56,6 +56,9 @@ def get_final_table(savefolder: str = None): # save to csv at input location mean_df.to_csv(str(df_file).replace('.csv', '_mean.csv'), index=False) + # save to markdown for nice visualization + mean_df.to_markdown(str(df_file).replace('.csv', '_mean.md'), index=False) + if __name__ == "__main__": parser = ArgumentParser(description='Run benchmarking tests') From cd3e57cdf8ce04c0bef23c8b42d5fc48a31799e5 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Thu, 15 Aug 2024 10:07:21 +0200 Subject: [PATCH 13/35] Remove benchmarking output if it already exists --- simpa_examples/benchmarking/extract_benchmarking_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simpa_examples/benchmarking/extract_benchmarking_data.py b/simpa_examples/benchmarking/extract_benchmarking_data.py index 0b102a6d..91fb5ea7 100644 --- a/simpa_examples/benchmarking/extract_benchmarking_data.py +++ b/simpa_examples/benchmarking/extract_benchmarking_data.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ # SPDX-FileCopyrightText: 2021 Janek Groehl # SPDX-License-Identifier: MIT +import os import numpy as np import pandas as pd @@ -103,8 +104,7 @@ def read_out_benchmarking_data(profiles: list = None, start: float = .2, stop: f # if exists: load old dataframe and append OR just save df df_file = savefolder / 'benchmarking_data_frame.csv' if df_file.is_file(): - old_df = pd.read_csv(df_file) - new_df = pd.concat([old_df, new_df]) + os.remove(df_file) new_df.to_csv(df_file, index=False) From aadb23dfc513b62ed97915d17f641af4d24ca749 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Thu, 15 Aug 2024 10:08:08 +0200 Subject: [PATCH 14/35] Change comment on remove benchmarking output if it already exists --- simpa_examples/benchmarking/extract_benchmarking_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpa_examples/benchmarking/extract_benchmarking_data.py b/simpa_examples/benchmarking/extract_benchmarking_data.py index 91fb5ea7..345397a1 100644 --- a/simpa_examples/benchmarking/extract_benchmarking_data.py +++ b/simpa_examples/benchmarking/extract_benchmarking_data.py @@ -101,7 +101,7 @@ def read_out_benchmarking_data(profiles: list = None, start: float = .2, stop: f new_df = pd.DataFrame(benchmarking_lists, columns=['Example', 'Spacing', 'Profile', 'Value']) new_df.astype(dtype={"Example": "str", "Spacing": "float64", "Profile": "str", "Value": "float64"}) - # if exists: load old dataframe and append OR just save df + # if exists: remove old file df_file = savefolder / 'benchmarking_data_frame.csv' if df_file.is_file(): os.remove(df_file) From 6491ebf16b74de2cf1175d31c9b2fd3c83af193e Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Thu, 15 Aug 2024 14:01:59 +0200 Subject: [PATCH 15/35] Change comment on remove benchmarking output if it already exists --- .../monospectral/field_of_view_cropping.py | 4 +-- .../acoustic_module/k_wave_adapter.py | 1 - .../reconstruction_utils.py | 7 +++-- .../time_reversal_adapter.py | 6 ++--- simpa/utils/__init__.py | 13 ++++----- simpa/utils/calculate.py | 27 +++++++++++++++++-- .../libraries/heterogeneity_generator.py | 4 +-- .../structure_library/StructureBase.py | 8 +++--- .../create_a_custom_digital_device_twin.py | 16 +++++------ simpa_examples/segmentation_loader.py | 4 +-- .../volume_creation/SegmentationLoader.py | 4 +-- 11 files changed, 60 insertions(+), 34 deletions(-) diff --git a/simpa/core/processing_components/monospectral/field_of_view_cropping.py b/simpa/core/processing_components/monospectral/field_of_view_cropping.py index e4fcbf5d..683b6c28 100644 --- a/simpa/core/processing_components/monospectral/field_of_view_cropping.py +++ b/simpa/core/processing_components/monospectral/field_of_view_cropping.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT from simpa.core.simulation_modules.reconstruction_module.reconstruction_utils import compute_image_dimensions -from simpa.utils import Tags, Settings +from simpa.utils import Tags, Settings, round_away_from_zero from simpa.utils.constants import property_tags, wavelength_independent_properties, toolkit_tags from simpa.io_handling import load_data_field, save_data_field from simpa.core.processing_components import ProcessingComponentBase @@ -48,7 +48,7 @@ def run(self, device: DigitalDeviceTwinBase): self.logger.debug(f"FOV (mm): {field_of_view_mm}") _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, self.global_settings[Tags.SPACING_MM], self.logger) field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering - field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int + field_of_view_voxels = [round_away_from_zero(dim) for dim in field_of_view_voxels] # cast to int self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 diff --git a/simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py b/simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py index 9e4db45e..09c93ae1 100644 --- a/simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py +++ b/simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py @@ -5,7 +5,6 @@ import gc import os import subprocess -from typing import List import numpy as np import scipy.io as sio diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py index 5bfcafe4..8066ab92 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py @@ -9,6 +9,7 @@ from simpa.utils.settings import Settings from simpa.io_handling.io_hdf5 import load_data_field from simpa.utils import Tags +from simpa.utils import round_away_from_zero import torch import torch.fft from torch import Tensor @@ -468,7 +469,9 @@ def compute_image_dimensions(field_of_view_in_mm: np.ndarray, spacing_in_mm: flo def compute_for_one_dimension(start_in_mm: float, end_in_mm: float) -> Tuple[int, np.float64, np.float64]: """ Helper function to compute the image dimensions for a single dimension given a start and end point in mm. - Makes sure that image dimesion is an integer by rounding, thus the resulting dimensions might be slightly larger or smaller than the given field of view. The maximal deviation amounts to a quarter spacing on each side. The algorithm spaces the pixels symmetrically between start and end. + Makes sure that image dimension is an integer by rounding, thus the resulting dimensions might be slightly + larger or smaller than the given field of view. The maximal deviation amounts to a quarter spacing on each side. + The algorithm spaces the pixels symmetrically between start and end. :param start_in_mm: lower limit of the field of view in this dimension :type start_in_mm: float @@ -481,7 +484,7 @@ def compute_for_one_dimension(start_in_mm: float, end_in_mm: float) -> Tuple[int start_temp = start_in_mm / spacing_in_mm end_temp = end_in_mm / spacing_in_mm dim_temp = np.abs(end_temp - start_temp) - dim = int(np.round(dim_temp)) + dim = round_away_from_zero(dim_temp) diff = dim_temp - dim # the sign is important here start = start_temp - np.sign(start_temp) * diff/2 end = end_temp - np.sign(end_temp) * diff/2 diff --git a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py index de335939..9704073c 100644 --- a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT from simpa.core.simulation_modules.reconstruction_module.reconstruction_utils import compute_image_dimensions -from simpa.utils import Tags +from simpa.utils import Tags, round_away_from_zero from simpa.utils.matlab import generate_matlab_cmd from simpa.utils.settings import Settings from simpa.core.simulation_modules.reconstruction_module import ReconstructionAdapterBase @@ -57,7 +57,7 @@ def get_acoustic_properties(self, input_data: dict, detection_geometry): detector_positions = detection_geometry.get_detector_element_positions_accounting_for_device_position_mm() # we add eps of 1e-10 because numpy rounds 0.5 to the next even number - detector_positions_voxels = np.round(detector_positions / spacing_in_mm + 1e-10).astype(int) + detector_positions_voxels = round_away_from_zero(detector_positions / spacing_in_mm + 1e-10) # plus 2 because of off- volume_x_dim = int(np.ceil(self.global_settings[Tags.DIM_VOLUME_X_MM] / spacing_in_mm) + 1) @@ -185,7 +185,7 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry): field_of_view_mm = detection_geometry.get_field_of_view_mm() _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, spacing_in_mm, self.logger) field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering - field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int + field_of_view_voxels = [round_away_from_zero(dim) for dim in field_of_view_voxels] # cast to int self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 diff --git a/simpa/utils/__init__.py b/simpa/utils/__init__.py index 2393f806..c532cd61 100644 --- a/simpa/utils/__init__.py +++ b/simpa/utils/__init__.py @@ -11,12 +11,6 @@ from .libraries.literature_values import OpticalTissueProperties from .constants import SegmentationClasses -# Heterogeneity - -from .libraries.heterogeneity_generator import RandomHeterogeneity -from .libraries.heterogeneity_generator import BlobHeterogeneity -from .libraries.heterogeneity_generator import ImageHeterogeneity - # Then load classes and methods with an increasing amount of internal dependencies. # If there are import errors in the tests, it is probably due to an incorrect # initialization order @@ -37,6 +31,7 @@ from .calculate import calculate_oxygenation from .calculate import calculate_gruneisen_parameter_from_temperature from .calculate import randomize_uniform +from .calculate import round_away_from_zero from .deformation_manager import create_deformation_settings from .deformation_manager import get_functional_from_deformation_settings @@ -62,5 +57,11 @@ from .libraries.structure_library.SphericalStructure import SphericalStructure, define_spherical_structure_settings from .libraries.structure_library.VesselStructure import VesselStructure, define_vessel_structure_settings +# Heterogeneity + +from .libraries.heterogeneity_generator import RandomHeterogeneity +from .libraries.heterogeneity_generator import BlobHeterogeneity +from .libraries.heterogeneity_generator import ImageHeterogeneity + if __name__ == "__main__": view_saved_spectra() diff --git a/simpa/utils/calculate.py b/simpa/utils/calculate.py index 31b59299..693c7686 100644 --- a/simpa/utils/calculate.py +++ b/simpa/utils/calculate.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT -from typing import Union, List, Dict, Optional +from typing import Union, List, Dict, Optional, Sized import numpy as np import torch from scipy.interpolate import interp1d @@ -135,7 +135,7 @@ def spline_evaluator2d_voxel(x: int, y: int, spline: Union[list, np.ndarray], of :return: True if the point (x, y) lies within the range around the spline, False otherwise. """ elevation = spline[x] - y_value = np.round(elevation + offset_voxel) + y_value = round_away_from_zero(elevation + offset_voxel) if y_value <= y < thickness_voxel + y_value: return True else: @@ -292,3 +292,26 @@ def are_equal(obj1: Union[list, tuple, np.ndarray, object], obj2: Union[list, tu # For other types, use standard equality check which also works for lists else: return obj1 == obj2 + + +def round_away_from_zero(x): + """ + Round a number away from zero. The np.round function rounds 0.5 to the nearest even number, which is not always the + desired behavior. This function always rounds 0.5 away from zero. For example, 0.5 will be rounded to 1, and -0.5 + will be rounded to -1. All other numbers are rounded to the nearest integer. + :param x: input number or array of numbers + :return: rounded number or array of numbers + :rtype: int or np.ndarray of int + """ + + def round_single_value(value): + # If the value is positive, add 0.5 and use floor to round away from zero + # If the value is negative, subtract 0.5 and use ceil to round away from zero + return int(np.floor(value + 0.5)) if value > 0 else int(np.ceil(value - 0.5)) + + if isinstance(x, (np.ndarray, list, tuple)): + # Apply rounding function to each element in the array + return np.array([round_away_from_zero(val) for val in x], dtype=int) + else: + # Apply rounding to a single value + return round_single_value(x) diff --git a/simpa/utils/libraries/heterogeneity_generator.py b/simpa/utils/libraries/heterogeneity_generator.py index 8d01b3f1..6cbaa683 100644 --- a/simpa/utils/libraries/heterogeneity_generator.py +++ b/simpa/utils/libraries/heterogeneity_generator.py @@ -6,7 +6,7 @@ from sklearn.datasets import make_blobs from scipy.ndimage.filters import gaussian_filter from skimage import transform -from simpa.utils import Tags +from simpa.utils import Tags, round_away_from_zero from typing import Union, Optional from simpa.log import Logger @@ -135,7 +135,7 @@ def __init__(self, xdim, ydim, zdim, spacing_mm, num_centers=None, cluster_std=N super().__init__(xdim, ydim, zdim, spacing_mm, target_mean, target_std, target_min, target_max) if num_centers is None: - num_centers = int(np.round(np.float_power((xdim * ydim * zdim) * spacing_mm, 1 / 3))) + num_centers = round_away_from_zero(np.float_power((xdim * ydim * zdim) * spacing_mm, 1 / 3)) if cluster_std is None: cluster_std = 1 diff --git a/simpa/utils/libraries/structure_library/StructureBase.py b/simpa/utils/libraries/structure_library/StructureBase.py index 22c39790..fc8dc2c4 100644 --- a/simpa/utils/libraries/structure_library/StructureBase.py +++ b/simpa/utils/libraries/structure_library/StructureBase.py @@ -7,7 +7,7 @@ import numpy as np from simpa.log import Logger -from simpa.utils import Settings, Tags, get_functional_from_deformation_settings +from simpa.utils import Settings, Tags, get_functional_from_deformation_settings, round_away_from_zero from simpa.utils.libraries.molecule_library import MolecularComposition from simpa.utils.tissue_properties import TissueProperties from simpa.utils.processing_device import get_processing_device @@ -30,9 +30,9 @@ def __init__(self, global_settings: Settings, self.logger = Logger() self.voxel_spacing = global_settings[Tags.SPACING_MM] - volume_x_dim = int(np.round(global_settings[Tags.DIM_VOLUME_X_MM] / self.voxel_spacing)) - volume_y_dim = int(np.round(global_settings[Tags.DIM_VOLUME_Y_MM] / self.voxel_spacing)) - volume_z_dim = int(np.round(global_settings[Tags.DIM_VOLUME_Z_MM] / self.voxel_spacing)) + volume_x_dim = round_away_from_zero(global_settings[Tags.DIM_VOLUME_X_MM] / self.voxel_spacing) + volume_y_dim = round_away_from_zero(global_settings[Tags.DIM_VOLUME_Y_MM] / self.voxel_spacing) + volume_z_dim = round_away_from_zero(global_settings[Tags.DIM_VOLUME_Z_MM] / self.voxel_spacing) self.volume_dimensions_voxels = np.asarray([volume_x_dim, volume_y_dim, volume_z_dim]) self.volume_dimensions_mm = self.volume_dimensions_voxels * self.voxel_spacing diff --git a/simpa_examples/create_a_custom_digital_device_twin.py b/simpa_examples/create_a_custom_digital_device_twin.py index 57bb551e..bf5a5f02 100644 --- a/simpa_examples/create_a_custom_digital_device_twin.py +++ b/simpa_examples/create_a_custom_digital_device_twin.py @@ -18,19 +18,19 @@ class ExampleDeviceSlitIlluminationLinearDetector(sp.PhotoacousticDevice): """ - def __init__(self): - super().__init__() + def __init__(self, device_position_mm): + super().__init__(device_position_mm=device_position_mm) # You can choose your detection geometries from simpa/core/device_digital_twins/detection_geometries # You can choose your illumination geometries from simpa/core/device_digital_twins/illumination_geometries - self.set_detection_geometry(sp.LinearArrayDetectionGeometry()) - self.add_illumination_geometry(sp.SlitIlluminationGeometry()) + self.set_detection_geometry(sp.LinearArrayDetectionGeometry(device_position_mm=device_position_mm)) + self.add_illumination_geometry(sp.SlitIlluminationGeometry(device_position_mm=device_position_mm)) if __name__ == "__main__": - device = ExampleDeviceSlitIlluminationLinearDetector() + device = ExampleDeviceSlitIlluminationLinearDetector(device_position_mm=np.array([25, 25, 0])) settings = sp.Settings() - settings[Tags.DIM_VOLUME_X_MM] = 20 + settings[Tags.DIM_VOLUME_X_MM] = 50 settings[Tags.DIM_VOLUME_Y_MM] = 50 settings[Tags.DIM_VOLUME_Z_MM] = 20 settings[Tags.SPACING_MM] = 0.5 @@ -41,12 +41,12 @@ def __init__(self): positions = device.get_detection_geometry().get_detector_element_positions_accounting_for_device_position_mm() detector_elements = device.get_detection_geometry().get_detector_element_orientations() - positions = np.round(positions/settings[Tags.SPACING_MM]).astype(int) + positions = sp.round_away_from_zero(positions/settings[Tags.SPACING_MM]) xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(device.get_detection_geometry().field_of_view_extent_mm, settings[Tags.SPACING_MM], Logger()) import matplotlib.pyplot as plt from matplotlib.patches import Rectangle - plt.gca().add_patch(Rectangle((xdim_start ,ydim_start), xdim, -ydim, linewidth=1, edgecolor='r', facecolor='r', alpha=.5)) + plt.gca().add_patch(Rectangle((xdim_start + 50/2, ydim_start), xdim + 50/2, -ydim, linewidth=1, edgecolor='r', facecolor='r', alpha=.5)) plt.scatter(positions[:, 0], positions[:, 2]) plt.quiver(positions[:, 0], positions[:, 2], detector_elements[:, 0], detector_elements[:, 2]) plt.show() diff --git a/simpa_examples/segmentation_loader.py b/simpa_examples/segmentation_loader.py index 685240a8..08a2478e 100644 --- a/simpa_examples/segmentation_loader.py +++ b/simpa_examples/segmentation_loader.py @@ -40,8 +40,8 @@ def run_segmentation_loader(spacing: float | int = 1.0, input_spacing: float | i label_mask = np.reshape(label_mask, (label_mask.shape[0], 1, label_mask.shape[1])) segmentation_volume_tiled = np.tile(label_mask, (1, 128, 1)) - segmentation_volume_mask = np.round(zoom(segmentation_volume_tiled, input_spacing/spacing, - order=0)).astype(int) + segmentation_volume_mask = sp.round_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/spacing, + order=0)).astype(int) def segmentation_class_mapping(): ret_dict = dict() diff --git a/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py b/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py index 230a8e3c..08be86fe 100644 --- a/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py +++ b/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py @@ -24,8 +24,8 @@ def setup(self): label_mask = np.reshape(label_mask, (400, 1, 400)) input_spacing = 0.2 segmentation_volume_tiled = np.tile(label_mask, (1, 128, 1)) - segmentation_volume_mask = np.round(zoom(segmentation_volume_tiled, input_spacing/target_spacing, - order=0)).astype(int) + segmentation_volume_mask = sp.round_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/target_spacing, + order=0)).astype(int) def segmentation_class_mapping(): ret_dict = dict() From 2685743b61a6392f33f12f47a5c2318ac35910ef Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Thu, 15 Aug 2024 14:28:46 +0200 Subject: [PATCH 16/35] Added descriptions and instructions for contributing with benchmarking --- CONTRIBUTING.md | 24 +++++++++++++----------- README.md | 3 ++- docs/source/benchmarking.md | 10 ++++++---- pyproject.toml | 14 ++++++++------ 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d108c76d..2d1ec333 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,22 +25,24 @@ Once you reached out to us, you will be provided with the information on how to In general the following steps are involved during a contribution: ### Contribution process -1. Create feature request / bug report on the [SIMPA issues page](https://github.com/IMSY-DKFZ/simpa/issues) -2. Discuss potential contribution with core development team -3. Fork the [SIMPA repository](https://github.com/IMSY-DKFZ/simpa) -4. Create feature branch from develop using the naming convention T_, - where represent the number github assigned the created issue and describes +1. Create feature request / bug report on the [SIMPA issues page](https://github.com/IMSY-DKFZ/simpa/issues) +2. Discuss potential contribution with core development team +3. Fork the [SIMPA repository](https://github.com/IMSY-DKFZ/simpa) +4. Make sure that you've installed all the optional dependencies by running `pip install .[docs,profile,testing]` + in the root directory of the repository. +5. Create feature branch from develop using the naming convention T_, + where represent the number Github assigned the created issue and describes what is being developed in CamelCaseNotation. Examples: `T13_FixSimulatorBug`, `T27_AddNewSimulator` -5. Perform test driven development on feature branch. +6. Perform test driven development on feature branch. A new implemented feature / a bug fix should be accompanied by a test. Additionally, all previously existing tests must still pass after the contribution. -6. Run pre-commit hooks and make sure all hooks are passing. -7. If you want to benchmark your contributions please use the benchmarking bash script (see [benchmarking.md](docs/source/benchmarking.md) for more details). -8. Once development is finished, create a pull request including your changes. +7. Run pre-commit hooks and make sure all hooks are passing. +8. If you want to benchmark your contributions please use the benchmarking bash script (see [benchmarking.md](docs/source/benchmarking.md) for more details). +9. Once development is finished, create a pull request including your changes. For more information on how to create pull request, see GitHub's [about pull requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). -9. If there are conflicts between the simpa develop branch and your branch, you should update your feature branch with the simpa develop branch using a "merge" strategy instead of "rebase". -10. A member of the core development team will review your pull request and potentially require further changes +10. If there are conflicts between the simpa develop branch and your branch, you should update your feature branch with the simpa develop branch using a "merge" strategy instead of "rebase". +11. A member of the core development team will review your pull request and potentially require further changes (see [Contribution review and integration](#contribution-review-and-integration)). Once all remarks have been resolved, your changes will be merged into the develop branch. diff --git a/README.md b/README.md index 4dd5e9b3..851adc5b 100755 --- a/README.md +++ b/README.md @@ -162,7 +162,8 @@ It is also easily possible to build the SIMPA documentation from scratch. When the installation succeeded, and you want to make sure that you have the latest documentation you should do the following steps in a command line: -1. Navigate to the `simpa/docs` directory +1. Make sure that you've installed the optional dependencies needed for the documentation by running `pip install .[docs]` +2. Navigate to the `simpa/docs` directory 2. If you would like the documentation to have the https://readthedocs.org/ style, type `pip install sphinx-rtd-theme` 3. Type `make html` 4. Open the `index.html` file in the `simpa/docs/build/html` directory with your favourite browser. diff --git a/docs/source/benchmarking.md b/docs/source/benchmarking.md index 669ccfc1..f76207d0 100644 --- a/docs/source/benchmarking.md +++ b/docs/source/benchmarking.md @@ -69,12 +69,14 @@ Here are some examples of how to use the script: bash ./run_benchmark.sh -t -g -n 3 ``` -To read the csv you can use the following code: +To read the results, just click on the generated `benchmarking_data_frame_mean.md` file. +Or you can also read the csv with: ```python import pandas as pd -my_simpa_dir = '/home/user/workspace/...' -benchmarking_results = pd.read_csv(my_simpa_dir + 'simpa/simpa_examples/benchmarking/benchmarking_data_frame_mean.csv') -display(benchmarking_results) # display works for ipynb - for py files use print(benchmarking_results) +from tabulate import tabulate +benchmarking_results = pd.read_csv('path/to/simpa/simpa_examples/benchmarking/benchmarking_data_frame_mean.csv') +print(tabulate(benchmarking_results)) +# or use display(benchmarking_results) which works for ipynb ``` The expected outcome should look something similar to the below: diff --git a/pyproject.toml b/pyproject.toml index fc64165c..65100c0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,14 +38,16 @@ dependencies = [ [project.optional-dependencies] docs = [ - "sphinx-rtd-theme>=2.0.0,<3.0.0", - "Sphinx>=5.1.1,<6.0.0", - "myst-parser>=0.18.0,<1.1" + "sphinx-rtd-theme>=2.0.0,<3.0.0", # Uses MIT-License (MIT compatible) + "Sphinx>=5.1.1,<6.0.0", # Uses BSD-License (MIT compatible) + "myst-parser>=0.18.0,<1.1" # Uses MIT-License (MIT compatible) ] profile = [ - "pytorch_memlab>=0.3.0,<0.4.0", - "line_profiler>=4.0.0,<5.0.0", - "memory_profiler>=0.61.0,<0.62.0" + "pytorch_memlab>=0.3.0", # Uses MIT-License (MIT compatible) + "line_profiler>=4.0.0", # Uses BSD-License (MIT compatible) + "memory_profiler>=0.61.0", # Uses BSD-License (MIT compatible) + "tabulate>=0.9.0" # Uses MIT-License (MIT compatible) + ] testing = [ "mdutils>=1.4.0", # Uses MIT-License (MIT compatible) From 138ee51bc9cf54938521458d0c26f35dc535ce15 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Thu, 15 Aug 2024 14:29:59 +0200 Subject: [PATCH 17/35] Added instruction for contributing with benchmarking --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d1ec333..405e6957 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,8 @@ In general the following steps are involved during a contribution: A new implemented feature / a bug fix should be accompanied by a test. Additionally, all previously existing tests must still pass after the contribution. 7. Run pre-commit hooks and make sure all hooks are passing. -8. If you want to benchmark your contributions please use the benchmarking bash script (see [benchmarking.md](docs/source/benchmarking.md) for more details). +8. Please also make sure that you benchmark your contributions please use the benchmarking bash script (see [benchmarking.md](docs/source/benchmarking.md) for more details). + Please add the results to the PR and compare them to the current develop. 9. Once development is finished, create a pull request including your changes. For more information on how to create pull request, see GitHub's [about pull requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). 10. If there are conflicts between the simpa develop branch and your branch, you should update your feature branch with the simpa develop branch using a "merge" strategy instead of "rebase". From 0bbae55ee196192af51e3f9548392c0a5567e469 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 16 Aug 2024 14:36:08 +0200 Subject: [PATCH 18/35] Renamed simpa paths for better readability --- .../monospectral/field_of_view_cropping.py | 4 ++-- .../monospectral/iterative_qPAI_algorithm.py | 8 ++++---- .../monospectral/noise/gamma_noise.py | 4 ++-- .../monospectral/noise/gaussian_noise.py | 4 ++-- .../monospectral/noise/poisson_noise.py | 4 ++-- .../noise/salt_and_pepper_noise.py | 4 ++-- .../monospectral/noise/uniform_noise.py | 4 ++-- .../multispectral/linear_unmixing.py | 2 +- .../multispectral_processing_algorithm.py | 2 +- simpa/core/simulation.py | 16 ++++++++-------- .../acoustic_module/acoustic_adapter_base.py | 2 +- .../acoustic_module/k_wave_adapter.py | 6 +++--- .../optical_module/optical_adapter_base.py | 4 ++-- .../reconstruction_adapter_base.py | 4 ++-- .../reconstruction_utils.py | 2 +- .../time_reversal_adapter.py | 2 +- .../volume_creation_adapter_base.py | 2 +- simpa/utils/path_manager.py | 4 ++-- simpa/utils/tags.py | 4 ++-- .../optical_and_acoustic_simulation.py | 2 +- simpa_examples/path_config.env.example | 2 +- .../automatic_tests/test_IPASC_export.py | 18 +++++++++--------- .../automatic_tests/test_create_a_volume.py | 6 +++--- .../automatic_tests/test_linear_unmixing.py | 18 +++++++++--------- .../automatic_tests/test_noise_models.py | 8 ++++---- .../automatic_tests/test_path_manager.py | 6 +++--- simpa_tests/automatic_tests/test_pipeline.py | 6 +++--- simpa_tests/manual_tests/__init__.py | 2 +- .../KWaveAcousticForwardConvenienceFunction.py | 4 ++-- .../MinimalKWaveTest.py | 10 +++++----- .../SimulationWithMSOTInvision.py | 2 +- .../DelayAndSumReconstruction.py | 4 ++-- .../DelayMultiplyAndSumReconstruction.py | 4 ++-- .../SignedDelayMultiplyAndSumReconstruction.py | 4 ++-- .../TimeReversalReconstruction.py | 2 +- ...atteringWithInifinitesimalSlabExperiment.py | 10 +++++----- ...ptionAndScatteringWithinHomogenousMedium.py | 6 +++--- .../CompareMCXResultsWithDiffusionTheory.py | 2 +- .../ComputeDiffuseReflectance.py | 6 +++--- .../QPAIReconstruction.py | 4 ++-- .../TestLinearUnmixingVisual.py | 8 ++++---- .../ReproduceDISMeasurements.py | 2 +- .../volume_creation/SegmentationLoader.py | 2 +- 43 files changed, 110 insertions(+), 110 deletions(-) diff --git a/simpa/core/processing_components/monospectral/field_of_view_cropping.py b/simpa/core/processing_components/monospectral/field_of_view_cropping.py index 68ae16a2..9899c7ad 100644 --- a/simpa/core/processing_components/monospectral/field_of_view_cropping.py +++ b/simpa/core/processing_components/monospectral/field_of_view_cropping.py @@ -64,7 +64,7 @@ def run(self, device: DigitalDeviceTwinBase): continue try: self.logger.debug(f"Cropping data field {data_field}...") - data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) self.logger.debug(f"data array shape before cropping: {np.shape(data_array)}") self.logger.debug(f"data array shape len: {len(np.shape(data_array))}") @@ -101,6 +101,6 @@ def run(self, device: DigitalDeviceTwinBase): self.logger.debug(f"data array shape after cropping: {np.shape(data_array)}") # save - save_data_field(data_array, self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + save_data_field(data_array, self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) self.logger.info("Cropping field of view...[Done]") diff --git a/simpa/core/processing_components/monospectral/iterative_qPAI_algorithm.py b/simpa/core/processing_components/monospectral/iterative_qPAI_algorithm.py index df4c61da..fda821a9 100644 --- a/simpa/core/processing_components/monospectral/iterative_qPAI_algorithm.py +++ b/simpa/core/processing_components/monospectral/iterative_qPAI_algorithm.py @@ -106,7 +106,7 @@ def run(self, pa_device): else: wavelength = self.global_settings[Tags.WAVELENGTHS][0] data_field = Tags.ITERATIVE_qPAI_RESULT - save_data_field(reconstructed_absorption, self.global_settings[Tags.SIMPA_OUTPUT_PATH], + save_data_field(reconstructed_absorption, self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) # save a list of all intermediate absorption (2-d only) updates in npy file if intended @@ -224,13 +224,13 @@ def extract_initial_data_from_hdf5(self) -> Tuple[np.ndarray, np.ndarray, np.nda wavelength = self.global_settings[Tags.WAVELENGTHS][0] self.logger.debug(f"Wavelength: {wavelength}") # get initial pressure and scattering - initial_pressure = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], + initial_pressure = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_INITIAL_PRESSURE, wavelength) - scattering = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_SCATTERING_PER_CM, + scattering = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_SCATTERING_PER_CM, wavelength) - anisotropy = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_ANISOTROPY, + anisotropy = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_ANISOTROPY, wavelength) # function returns the last iteration result as a numpy array and all iteration results in a list diff --git a/simpa/core/processing_components/monospectral/noise/gamma_noise.py b/simpa/core/processing_components/monospectral/noise/gamma_noise.py index 5d87ca6c..23a0ea0d 100644 --- a/simpa/core/processing_components/monospectral/noise/gamma_noise.py +++ b/simpa/core/processing_components/monospectral/noise/gamma_noise.py @@ -51,7 +51,7 @@ def run(self, device): self.logger.debug(f"Noise model scale: {scale}") wavelength = self.global_settings[Tags.WAVELENGTH] - data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) data_tensor = torch.as_tensor(data_array, dtype=torch.float32, device=self.torch_device) dist = torch.distributions.gamma.Gamma(torch.tensor(shape, dtype=torch.float32, device=self.torch_device), torch.tensor(1.0/scale, dtype=torch.float32, device=self.torch_device)) @@ -65,6 +65,6 @@ def run(self, device): assert_array_well_defined(data_tensor) save_data_field(data_tensor.cpu().numpy().astype(np.float64, copy=False), - self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) self.logger.info("Applying Gamma Noise Model...[Done]") diff --git a/simpa/core/processing_components/monospectral/noise/gaussian_noise.py b/simpa/core/processing_components/monospectral/noise/gaussian_noise.py index 98dba54f..5b391597 100644 --- a/simpa/core/processing_components/monospectral/noise/gaussian_noise.py +++ b/simpa/core/processing_components/monospectral/noise/gaussian_noise.py @@ -56,7 +56,7 @@ def run(self, device): self.logger.debug(f"Noise model non-negative: {non_negative}") wavelength = self.global_settings[Tags.WAVELENGTH] - data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) data_tensor = torch.as_tensor(data_array, dtype=torch.float32, device=self.torch_device) dist = torch.distributions.normal.Normal(torch.tensor(mean, dtype=torch.float32, device=self.torch_device), torch.tensor(std, dtype=torch.float32, device=self.torch_device)) @@ -72,6 +72,6 @@ def run(self, device): if non_negative: data_tensor[data_tensor < EPS] = EPS save_data_field(data_tensor.cpu().numpy().astype(np.float64, copy=False), - self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) self.logger.info("Applying Gaussian Noise Model...[Done]") diff --git a/simpa/core/processing_components/monospectral/noise/poisson_noise.py b/simpa/core/processing_components/monospectral/noise/poisson_noise.py index 0f847f17..159a5f51 100644 --- a/simpa/core/processing_components/monospectral/noise/poisson_noise.py +++ b/simpa/core/processing_components/monospectral/noise/poisson_noise.py @@ -44,7 +44,7 @@ def run(self, device): self.logger.debug(f"Noise model mean: {mean}") wavelength = self.global_settings[Tags.WAVELENGTH] - data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) data_tensor = torch.as_tensor(data_array, dtype=torch.float32, device=self.torch_device) dist = torch.distributions.poisson.Poisson(torch.tensor(mean, dtype=torch.float32, device=self.torch_device)) @@ -57,6 +57,6 @@ def run(self, device): assert_array_well_defined(data_tensor) save_data_field(data_tensor.cpu().numpy().astype(np.float64, copy=False), - self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) self.logger.info("Applying Poisson Noise Model...[Done]") diff --git a/simpa/core/processing_components/monospectral/noise/salt_and_pepper_noise.py b/simpa/core/processing_components/monospectral/noise/salt_and_pepper_noise.py index b6c70fba..5e82a6f4 100644 --- a/simpa/core/processing_components/monospectral/noise/salt_and_pepper_noise.py +++ b/simpa/core/processing_components/monospectral/noise/salt_and_pepper_noise.py @@ -38,7 +38,7 @@ def run(self, device): data_field = self.component_settings[Tags.DATA_FIELD] wavelength = self.global_settings[Tags.WAVELENGTH] - data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) data_tensor = torch.as_tensor(data_array, dtype=torch.float32, device=self.torch_device) min_noise = torch.min(data_tensor).item() @@ -69,6 +69,6 @@ def run(self, device): assert_array_well_defined(data_tensor) save_data_field(data_tensor.cpu().numpy().astype(np.float64, copy=False), - self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) self.logger.info("Applying Salt And Pepper Noise Model...[Done]") diff --git a/simpa/core/processing_components/monospectral/noise/uniform_noise.py b/simpa/core/processing_components/monospectral/noise/uniform_noise.py index befc0de4..fb6c831d 100644 --- a/simpa/core/processing_components/monospectral/noise/uniform_noise.py +++ b/simpa/core/processing_components/monospectral/noise/uniform_noise.py @@ -52,7 +52,7 @@ def run(self, device): self.logger.debug(f"Noise model max: {max_noise}") wavelength = self.global_settings[Tags.WAVELENGTH] - data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + data_array = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) data_tensor = torch.as_tensor(data_array, dtype=torch.float32, device=self.torch_device) dist = torch.distributions.uniform.Uniform(torch.tensor(min_noise, dtype=torch.float32, device=self.torch_device), torch.tensor(max_noise, dtype=torch.float32, device=self.torch_device)) @@ -66,6 +66,6 @@ def run(self, device): assert_array_well_defined(data_tensor) save_data_field(data_tensor.cpu().numpy().astype(np.float64, copy=False), - self.global_settings[Tags.SIMPA_OUTPUT_PATH], data_field, wavelength) + self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field, wavelength) self.logger.info("Applying Uniform Noise Model...[Done]") diff --git a/simpa/core/processing_components/multispectral/linear_unmixing.py b/simpa/core/processing_components/multispectral/linear_unmixing.py index 04e24ff2..bb496df2 100644 --- a/simpa/core/processing_components/multispectral/linear_unmixing.py +++ b/simpa/core/processing_components/multispectral/linear_unmixing.py @@ -109,7 +109,7 @@ def run(self): save_dict["sO2"] = self.calculate_sO2() # save linear unmixing result in hdf5 - save_data_field(save_dict, self.global_settings[Tags.SIMPA_OUTPUT_PATH], + save_data_field(save_dict, self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.LINEAR_UNMIXING_RESULT, wavelength=None) self.logger.info("Performing linear spectral unmixing......[Done]") diff --git a/simpa/core/processing_components/multispectral/multispectral_processing_algorithm.py b/simpa/core/processing_components/multispectral/multispectral_processing_algorithm.py index d179c455..a3abfdc1 100644 --- a/simpa/core/processing_components/multispectral/multispectral_processing_algorithm.py +++ b/simpa/core/processing_components/multispectral/multispectral_processing_algorithm.py @@ -40,7 +40,7 @@ def __init__(self, global_settings, component_settings_key: str): self.data = list() for i in range(len(self.wavelengths)): - self.data.append(load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], + self.data.append(load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], self.data_field, self.wavelengths[i])) diff --git a/simpa/core/simulation.py b/simpa/core/simulation.py index 157ff678..66170a42 100644 --- a/simpa/core/simulation.py +++ b/simpa/core/simulation.py @@ -55,7 +55,7 @@ def simulate(simulation_pipeline: list, settings: Settings, digital_device_twin: else: simpa_output_path = path + settings[Tags.VOLUME_NAME] - settings[Tags.SIMPA_OUTPUT_PATH] = simpa_output_path + ".hdf5" + settings[Tags.SIMPA_OUTPUT_FILE_PATH] = simpa_output_path + ".hdf5" simpa_output[Tags.SIMPA_VERSION] = __version__ simpa_output[Tags.SETTINGS] = settings @@ -68,18 +68,18 @@ def simulate(simulation_pipeline: list, settings: Settings, digital_device_twin: if Tags.CONTINUE_SIMULATION in settings and settings[Tags.CONTINUE_SIMULATION]: try: - old_pipe = load_data_field(settings[Tags.SIMPA_OUTPUT_PATH], Tags.SIMULATION_PIPELINE) + old_pipe = load_data_field(settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.SIMULATION_PIPELINE) except KeyError as e: old_pipe = list() simpa_output[Tags.SIMULATION_PIPELINE] = old_pipe + simpa_output[Tags.SIMULATION_PIPELINE] - previous_settings = load_data_field(settings[Tags.SIMPA_OUTPUT_PATH], Tags.SETTINGS) + previous_settings = load_data_field(settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.SETTINGS) previous_settings.update(settings) simpa_output[Tags.SETTINGS] = previous_settings for i in [Tags.SETTINGS, Tags.DIGITAL_DEVICE, Tags.SIMULATION_PIPELINE]: - save_data_field(simpa_output[i], settings[Tags.SIMPA_OUTPUT_PATH], i) + save_data_field(simpa_output[i], settings[Tags.SIMPA_OUTPUT_FILE_PATH], i) else: - save_hdf5(simpa_output, settings[Tags.SIMPA_OUTPUT_PATH]) + save_hdf5(simpa_output, settings[Tags.SIMPA_OUTPUT_FILE_PATH]) logger.debug("Saving settings dictionary...[Done]") for wavelength in settings[Tags.WAVELENGTHS]: @@ -105,15 +105,15 @@ def simulate(simulation_pipeline: list, settings: Settings, digital_device_twin: # by the user manually. Active by default. if not (Tags.DO_FILE_COMPRESSION in settings and not settings[Tags.DO_FILE_COMPRESSION]): - all_data = load_hdf5(settings[Tags.SIMPA_OUTPUT_PATH]) + all_data = load_hdf5(settings[Tags.SIMPA_OUTPUT_FILE_PATH]) if Tags.VOLUME_CREATION_MODEL_SETTINGS in all_data[Tags.SETTINGS] and \ Tags.INPUT_SEGMENTATION_VOLUME in all_data[Tags.SETTINGS][Tags.VOLUME_CREATION_MODEL_SETTINGS]: del all_data[Tags.SETTINGS][Tags.VOLUME_CREATION_MODEL_SETTINGS][Tags.INPUT_SEGMENTATION_VOLUME] - save_hdf5(all_data, settings[Tags.SIMPA_OUTPUT_PATH], file_compression="gzip") + save_hdf5(all_data, settings[Tags.SIMPA_OUTPUT_FILE_PATH], file_compression="gzip") # Export simulation result to the IPASC format. if Tags.DO_IPASC_EXPORT in settings and settings[Tags.DO_IPASC_EXPORT]: logger.info("Exporting to IPASC....") - export_to_ipasc(settings[Tags.SIMPA_OUTPUT_PATH], device=digital_device_twin) + export_to_ipasc(settings[Tags.SIMPA_OUTPUT_FILE_PATH], device=digital_device_twin) logger.info(f"The entire simulation pipeline required {time.time() - start_time} seconds.") diff --git a/simpa/core/simulation_modules/acoustic_module/acoustic_adapter_base.py b/simpa/core/simulation_modules/acoustic_module/acoustic_adapter_base.py index 85c4dd07..1dd4855c 100644 --- a/simpa/core/simulation_modules/acoustic_module/acoustic_adapter_base.py +++ b/simpa/core/simulation_modules/acoustic_module/acoustic_adapter_base.py @@ -79,6 +79,6 @@ def run(self, digital_device_twin): acoustic_output_path = generate_dict_path( Tags.DATA_FIELD_TIME_SERIES_DATA, wavelength=self.global_settings[Tags.WAVELENGTH]) - save_hdf5(time_series_data, self.global_settings[Tags.SIMPA_OUTPUT_PATH], acoustic_output_path) + save_hdf5(time_series_data, self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], acoustic_output_path) self.logger.info("Simulating the acoustic forward process...[Done]") diff --git a/simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py b/simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py index 9e4db45e..b45063f2 100644 --- a/simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py +++ b/simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py @@ -84,7 +84,7 @@ def forward_model(self, detection_geometry: DetectionGeometryBase) -> np.ndarray self.logger.debug(f"OPTICAL_PATH: {str(optical_path)}") data_dict = {} - file_path = self.global_settings[Tags.SIMPA_OUTPUT_PATH] + file_path = self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH] data_dict[Tags.DATA_FIELD_INITIAL_PRESSURE] = load_data_field(file_path, Tags.DATA_FIELD_INITIAL_PRESSURE, wavelength=wavelength) data_dict[Tags.DATA_FIELD_SPEED_OF_SOUND] = load_data_field(file_path, Tags.DATA_FIELD_SPEED_OF_SOUND) @@ -122,8 +122,8 @@ def forward_model(self, detection_geometry: DetectionGeometryBase) -> np.ndarray data_dict[Tags.DATA_FIELD_DENSITY], data_dict[Tags.DATA_FIELD_ALPHA_COEFF], data_dict[Tags.DATA_FIELD_INITIAL_PRESSURE], - optical_path=self.global_settings[Tags.SIMPA_OUTPUT_PATH]) - save_hdf5(global_settings, global_settings[Tags.SIMPA_OUTPUT_PATH], "/settings/") + optical_path=self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH]) + save_hdf5(global_settings, global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], "/settings/") return time_series_data diff --git a/simpa/core/simulation_modules/optical_module/optical_adapter_base.py b/simpa/core/simulation_modules/optical_module/optical_adapter_base.py index fe7e3e78..bc231a98 100644 --- a/simpa/core/simulation_modules/optical_module/optical_adapter_base.py +++ b/simpa/core/simulation_modules/optical_module/optical_adapter_base.py @@ -65,7 +65,7 @@ def run(self, device: Union[IlluminationGeometryBase, PhotoacousticDevice]) -> N self.logger.info("Simulating the optical forward process...") - file_path = self.global_settings[Tags.SIMPA_OUTPUT_PATH] + file_path = self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH] wl = str(self.global_settings[Tags.WAVELENGTH]) absorption = load_data_field(file_path, Tags.DATA_FIELD_ABSORPTION_PER_CM, wl) @@ -112,7 +112,7 @@ def run(self, device: Union[IlluminationGeometryBase, PhotoacousticDevice]) -> N optical_output[k] = {self.global_settings[Tags.WAVELENGTH]: item} optical_output_path = generate_dict_path(Tags.OPTICAL_MODEL_OUTPUT_NAME) - save_hdf5(optical_output, self.global_settings[Tags.SIMPA_OUTPUT_PATH], optical_output_path) + save_hdf5(optical_output, self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], optical_output_path) self.logger.info("Simulating the optical forward process...[Done]") def run_forward_model(self, diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_adapter_base.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_adapter_base.py index 90d17099..89a483c4 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_adapter_base.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_adapter_base.py @@ -48,7 +48,7 @@ def reconstruction_algorithm(self, time_series_sensor_data, def run(self, device): self.logger.info("Performing reconstruction...") - time_series_sensor_data = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_PATH], + time_series_sensor_data = load_data_field(self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_TIME_SERIES_DATA, self.global_settings[Tags.WAVELENGTH]) _device = None @@ -89,7 +89,7 @@ def run(self, device): reconstruction_output_path = generate_dict_path( Tags.DATA_FIELD_RECONSTRUCTED_DATA, self.global_settings[Tags.WAVELENGTH]) - save_hdf5(reconstruction, self.global_settings[Tags.SIMPA_OUTPUT_PATH], + save_hdf5(reconstruction, self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], reconstruction_output_path) self.logger.info("Performing reconstruction...[Done]") diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py index 0b33a67a..e1e32125 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py @@ -381,7 +381,7 @@ def preparing_reconstruction_and_obtaining_reconstruction_settings( if Tags.DATA_FIELD_SPEED_OF_SOUND in component_settings and component_settings[Tags.DATA_FIELD_SPEED_OF_SOUND]: speed_of_sound_in_m_per_s = component_settings[Tags.DATA_FIELD_SPEED_OF_SOUND] elif Tags.WAVELENGTH in global_settings and global_settings[Tags.WAVELENGTH]: - sound_speed_m = load_data_field(global_settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_SPEED_OF_SOUND) + sound_speed_m = load_data_field(global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_SPEED_OF_SOUND) speed_of_sound_in_m_per_s = np.mean(sound_speed_m) else: raise AttributeError("Please specify a value for DATA_FIELD_SPEED_OF_SOUND " diff --git a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py index e266c7a1..079539ce 100644 --- a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py @@ -128,7 +128,7 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry): input_data[Tags.DATA_FIELD_TIME_SERIES_DATA] = time_series_sensor_data input_data, spacing_in_mm = self.get_acoustic_properties(input_data, detection_geometry) - acoustic_path = self.global_settings[Tags.SIMPA_OUTPUT_PATH] + ".mat" + acoustic_path = self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH] + ".mat" possible_k_wave_parameters = [Tags.MODEL_SENSOR_FREQUENCY_RESPONSE, Tags.KWAVE_PROPERTY_ALPHA_POWER, Tags.GPU, Tags.KWAVE_PROPERTY_PMLInside, Tags.KWAVE_PROPERTY_PMLAlpha, Tags.KWAVE_PROPERTY_PlotPML, diff --git a/simpa/core/simulation_modules/volume_creation_module/volume_creation_adapter_base.py b/simpa/core/simulation_modules/volume_creation_module/volume_creation_adapter_base.py index af3f47d6..9f435601 100644 --- a/simpa/core/simulation_modules/volume_creation_module/volume_creation_adapter_base.py +++ b/simpa/core/simulation_modules/volume_creation_module/volume_creation_adapter_base.py @@ -74,5 +74,5 @@ def run(self, device): assert_array_well_defined(volumes[_volume_name], array_name=_volume_name) for key, value in volumes.items(): - save_data_field(value, self.global_settings[Tags.SIMPA_OUTPUT_PATH], + save_data_field(value, self.global_settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field=key, wavelength=self.global_settings[Tags.WAVELENGTH]) diff --git a/simpa/utils/path_manager.py b/simpa/utils/path_manager.py index 74f41f5d..3d02f61d 100644 --- a/simpa/utils/path_manager.py +++ b/simpa/utils/path_manager.py @@ -81,8 +81,8 @@ def detect_local_path_config(self): return None def get_hdf5_file_save_path(self): - path = self.get_path_from_environment(f"{Tags.SIMPA_SAVE_PATH_VARNAME}") - self.logger.debug(f"Retrieved {Tags.SIMPA_SAVE_PATH_VARNAME}={path}") + path = self.get_path_from_environment(f"{Tags.SIMPA_SAVE_DIRECTORY_VARNAME}") + self.logger.debug(f"Retrieved {Tags.SIMPA_SAVE_DIRECTORY_VARNAME}={path}") return path def get_mcx_binary_path(self): diff --git a/simpa/utils/tags.py b/simpa/utils/tags.py index 1da32334..c8e29025 100644 --- a/simpa/utils/tags.py +++ b/simpa/utils/tags.py @@ -1252,7 +1252,7 @@ class Tags: IO settings """ - SIMPA_OUTPUT_PATH = ("simpa_output_path", str) + SIMPA_OUTPUT_FILE_PATH = ("simpa_output_path", str) """ Default path of the SIMPA output if not specified otherwise.\n Usage: SIMPA package @@ -1558,7 +1558,7 @@ class Tags: Usage: simpa.utils.libraries.structure_library.heterogeneity_generator """ - SIMPA_SAVE_PATH_VARNAME = "SIMPA_SAVE_PATH" + SIMPA_SAVE_DIRECTORY_VARNAME = "SIMPA_SAVE_DIRECTORY" """ Identifier for the environment variable that defines where the results generated with SIMPA will be sotred """ diff --git a/simpa_examples/optical_and_acoustic_simulation.py b/simpa_examples/optical_and_acoustic_simulation.py index 72b732c6..34887450 100644 --- a/simpa_examples/optical_and_acoustic_simulation.py +++ b/simpa_examples/optical_and_acoustic_simulation.py @@ -203,7 +203,7 @@ def create_example_tissue(): WAVELENGTH = 700 if visualise: - sp.visualise_data(path_to_hdf5_file=settings[Tags.SIMPA_OUTPUT_PATH], + sp.visualise_data(path_to_hdf5_file=settings[Tags.SIMPA_OUTPUT_FILE_PATH], wavelength=WAVELENGTH, show_time_series_data=True, show_initial_pressure=True, diff --git a/simpa_examples/path_config.env.example b/simpa_examples/path_config.env.example index 23b1bd16..0a9e3d41 100644 --- a/simpa_examples/path_config.env.example +++ b/simpa_examples/path_config.env.example @@ -2,6 +2,6 @@ # Afterwards, either copy this file to your current working directory, to your home directory, # or to the SIMPA base directory, and rename it to path_config.env -SIMPA_SAVE_PATH=/workplace/data # Path to a directory where all data will be stored. This path is always required. +SIMPA_SAVE_DIRECTORY=/workplace/data # Path to a directory where all data will be stored. This path is always required. MCX_BINARY_PATH=/workplace/mcx # On Linux systems, the .exe at the end must be omitted. This path is required if you plan to run optical simulations. MATLAB_BINARY_PATH=/path/to/matlab.exe # On Linux systems, the .exe at the end must be omitted. This path is required if you plan to run acoustic simulations. diff --git a/simpa_tests/automatic_tests/test_IPASC_export.py b/simpa_tests/automatic_tests/test_IPASC_export.py index 29cbc91f..1b555464 100644 --- a/simpa_tests/automatic_tests/test_IPASC_export.py +++ b/simpa_tests/automatic_tests/test_IPASC_export.py @@ -86,11 +86,11 @@ def setUp(self) -> None: self.expected_ipasc_output_path = None def clean_up(self): - print(f"Attempting to clean {self.settings[Tags.SIMPA_OUTPUT_PATH]}") - if (os.path.exists(self.settings[Tags.SIMPA_OUTPUT_PATH]) and - os.path.isfile(self.settings[Tags.SIMPA_OUTPUT_PATH])): + print(f"Attempting to clean {self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]}") + if (os.path.exists(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) and + os.path.isfile(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH])): # Delete the created file - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) print(f"Attempting to clean {self.expected_ipasc_output_path}") if (os.path.exists(self.expected_ipasc_output_path) and @@ -131,24 +131,24 @@ def assert_ipasc_file_binary_contents_is_matching_simpa_simulation(self, simpa_p def test_file_is_not_created_on_only_optical_simulation(self): simulate(self.optical_simulation_pipeline, self.settings, self.device) - self.expected_ipasc_output_path = self.settings[Tags.SIMPA_OUTPUT_PATH].replace(".hdf5", "_ipasc.hdf5") + self.expected_ipasc_output_path = self.settings[Tags.SIMPA_OUTPUT_FILE_PATH].replace(".hdf5", "_ipasc.hdf5") self.assertTrue(not os.path.exists(self.expected_ipasc_output_path)) self.clean_up() @expectedFailure def test_file_is_created_on_acoustic_simulation(self): simulate(self.acoustic_simulation_pipeline, self.settings, self.device) - self.expected_ipasc_output_path = self.settings[Tags.SIMPA_OUTPUT_PATH].replace(".hdf5", "_ipasc.hdf5") + self.expected_ipasc_output_path = self.settings[Tags.SIMPA_OUTPUT_FILE_PATH].replace(".hdf5", "_ipasc.hdf5") self.assertTrue(os.path.exists(self.expected_ipasc_output_path)) - self.assert_ipasc_file_binary_contents_is_matching_simpa_simulation(self.settings[Tags.SIMPA_OUTPUT_PATH], + self.assert_ipasc_file_binary_contents_is_matching_simpa_simulation(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], self.expected_ipasc_output_path) self.clean_up() @expectedFailure def test_file_is_created_on_full_simulation(self): simulate(self.full_simulation_pipeline, self.settings, self.device) - self.expected_ipasc_output_path = self.settings[Tags.SIMPA_OUTPUT_PATH].replace(".hdf5", "_ipasc.hdf5") + self.expected_ipasc_output_path = self.settings[Tags.SIMPA_OUTPUT_FILE_PATH].replace(".hdf5", "_ipasc.hdf5") self.assertTrue(os.path.exists(self.expected_ipasc_output_path)) - self.assert_ipasc_file_binary_contents_is_matching_simpa_simulation(self.settings[Tags.SIMPA_OUTPUT_PATH], + self.assert_ipasc_file_binary_contents_is_matching_simpa_simulation(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], self.expected_ipasc_output_path) self.clean_up() diff --git a/simpa_tests/automatic_tests/test_create_a_volume.py b/simpa_tests/automatic_tests/test_create_a_volume.py index 05652702..727a0752 100644 --- a/simpa_tests/automatic_tests/test_create_a_volume.py +++ b/simpa_tests/automatic_tests/test_create_a_volume.py @@ -37,6 +37,6 @@ def test_create_volume(self): simulate(simulation_pipeline, settings, RSOMExplorerP50(0.1, 1, 1)) - if (os.path.exists(settings[Tags.SIMPA_OUTPUT_PATH]) and - os.path.isfile(settings[Tags.SIMPA_OUTPUT_PATH])): - os.remove(settings[Tags.SIMPA_OUTPUT_PATH]) + if (os.path.exists(settings[Tags.SIMPA_OUTPUT_FILE_PATH]) and + os.path.isfile(settings[Tags.SIMPA_OUTPUT_FILE_PATH])): + os.remove(settings[Tags.SIMPA_OUTPUT_FILE_PATH]) diff --git a/simpa_tests/automatic_tests/test_linear_unmixing.py b/simpa_tests/automatic_tests/test_linear_unmixing.py index dc6e955e..d9556f9b 100644 --- a/simpa_tests/automatic_tests/test_linear_unmixing.py +++ b/simpa_tests/automatic_tests/test_linear_unmixing.py @@ -78,11 +78,11 @@ def test(self): lu.run() # Load blood oxygen saturation - lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.LINEAR_UNMIXING_RESULT) + lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.LINEAR_UNMIXING_RESULT) self.assert_correct_so2_vales(lu_results["sO2"]) def assert_correct_so2_vales(self, estimates, tolerance=1e-7): - ground_truth = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_OXYGENATION) + ground_truth = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_OXYGENATION) msg = f"expected {estimates.reshape((-1,))[0]} but was {ground_truth.reshape((-1,))[0]}" self.logger.info(msg) self.assertTrue(np.allclose(estimates, ground_truth, atol=tolerance), msg=msg) @@ -113,7 +113,7 @@ def test_non_negative_least_squares(self): lu.run() # Load blood oxygen saturation - lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.LINEAR_UNMIXING_RESULT) + lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.LINEAR_UNMIXING_RESULT) self.assert_correct_so2_vales(lu_results["sO2"]) @expectedFailure @@ -164,7 +164,7 @@ def test_subset_of_2_wavelengths(self): lu.run() # assert correct estimates - lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.LINEAR_UNMIXING_RESULT) + lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.LINEAR_UNMIXING_RESULT) self.assert_correct_so2_vales(lu_results["sO2"]) def test_subset_of_3_wavelengths(self): @@ -189,7 +189,7 @@ def test_subset_of_3_wavelengths(self): # Run linear unmixing component lu = sp.LinearUnmixing(self.settings, "linear_unmixing") lu.run() - lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.LINEAR_UNMIXING_RESULT) + lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.LINEAR_UNMIXING_RESULT) self.assert_correct_so2_vales(lu_results["sO2"]) @expectedFailure @@ -248,12 +248,12 @@ def test_with_all_absorbers(self): # Run linear unmixing component lu = sp.LinearUnmixing(self.settings, "linear_unmixing") lu.run() - lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.LINEAR_UNMIXING_RESULT) + lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.LINEAR_UNMIXING_RESULT) self.assert_correct_so2_vales(lu_results["sO2"], tolerance=1e-2) def tearDown(self): # Clean up file after testing - if (os.path.exists(self.settings[Tags.SIMPA_OUTPUT_PATH]) and - os.path.isfile(self.settings[Tags.SIMPA_OUTPUT_PATH])): + if (os.path.exists(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) and + os.path.isfile(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH])): # Delete the created file - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) diff --git a/simpa_tests/automatic_tests/test_noise_models.py b/simpa_tests/automatic_tests/test_noise_models.py index d4dd256f..d6912cd4 100644 --- a/simpa_tests/automatic_tests/test_noise_models.py +++ b/simpa_tests/automatic_tests/test_noise_models.py @@ -70,7 +70,7 @@ def validate_noise_model_results(self, noise_model, noise_model_settings, try: simulate(simulation_pipeline, settings, RSOMExplorerP50(0.1, 1, 1)) - absorption = load_data_field(file_path=settings[Tags.SIMPA_OUTPUT_PATH], + absorption = load_data_field(file_path=settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field=Tags.DATA_FIELD_ABSORPTION_PER_CM, wavelength=800) actual_mean = np.mean(absorption) @@ -82,10 +82,10 @@ def validate_noise_model_results(self, noise_model, noise_model_settings, np.abs(actual_std - expected_std) / expected_std < error_margin, f"The std was not as expected. Expected {expected_std} but was {actual_std}") finally: - if (os.path.exists(settings[Tags.SIMPA_OUTPUT_PATH]) and - os.path.isfile(settings[Tags.SIMPA_OUTPUT_PATH])): + if (os.path.exists(settings[Tags.SIMPA_OUTPUT_FILE_PATH]) and + os.path.isfile(settings[Tags.SIMPA_OUTPUT_FILE_PATH])): # Delete the created file - os.remove(settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(settings[Tags.SIMPA_OUTPUT_FILE_PATH]) def setUp(self): diff --git a/simpa_tests/automatic_tests/test_path_manager.py b/simpa_tests/automatic_tests/test_path_manager.py index eb462943..f7fbc61c 100644 --- a/simpa_tests/automatic_tests/test_path_manager.py +++ b/simpa_tests/automatic_tests/test_path_manager.py @@ -22,7 +22,7 @@ def setUp(self): self.file_content = (f"# Example path_config file. Please define all required paths for your simulation here.\n" f"# Afterwards, either copy this file to your current working directory, to your home directory,\n" f"# or to the SIMPA base directry.\n" - f"SIMPA_SAVE_PATH={self.save_path}\n" + f"SIMPA_SAVE_DIRECTORY={self.save_path}\n" f"MCX_BINARY_PATH={self.mcx_path}\n" f"MATLAB_BINARY_PATH={self.matlab_path}") self.home_file = str(Path.home()) + self.path @@ -34,7 +34,7 @@ def setUp(self): self.simpa_home_exists = os.path.exists(self.simpa_home) @unittest.expectedFailure - @patch.dict(os.environ, {Tags.SIMPA_SAVE_PATH_VARNAME: None, + @patch.dict(os.environ, {Tags.SIMPA_SAVE_DIRECTORY_VARNAME: None, Tags.MCX_BINARY_PATH_VARNAME: None}) def test_variables_not_set(self): path_manager = PathManager("/path/to/nowhere/") @@ -42,7 +42,7 @@ def test_variables_not_set(self): _ = path_manager.get_hdf5_file_save_path() _ = path_manager.get_matlab_binary_path() - @patch.dict(os.environ, {Tags.SIMPA_SAVE_PATH_VARNAME: "test_simpa_save_path", + @patch.dict(os.environ, {Tags.SIMPA_SAVE_DIRECTORY_VARNAME: "test_simpa_save_path", Tags.MCX_BINARY_PATH_VARNAME: "test_mcx_path"}) def test_instantiate_without_file(self): path_manager = PathManager("/path/to/nowhere/") diff --git a/simpa_tests/automatic_tests/test_pipeline.py b/simpa_tests/automatic_tests/test_pipeline.py index e251a178..0e0213c8 100644 --- a/simpa_tests/automatic_tests/test_pipeline.py +++ b/simpa_tests/automatic_tests/test_pipeline.py @@ -79,7 +79,7 @@ def test_pipeline(self): simulate(simulation_pipeline, settings, RSOMExplorerP50(0.1, 1, 1)) - if (os.path.exists(settings[Tags.SIMPA_OUTPUT_PATH]) and - os.path.isfile(settings[Tags.SIMPA_OUTPUT_PATH])): + if (os.path.exists(settings[Tags.SIMPA_OUTPUT_FILE_PATH]) and + os.path.isfile(settings[Tags.SIMPA_OUTPUT_FILE_PATH])): # Delete the created file - os.remove(settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(settings[Tags.SIMPA_OUTPUT_FILE_PATH]) diff --git a/simpa_tests/manual_tests/__init__.py b/simpa_tests/manual_tests/__init__.py index 79a6a5b1..2ff337b0 100644 --- a/simpa_tests/manual_tests/__init__.py +++ b/simpa_tests/manual_tests/__init__.py @@ -222,7 +222,7 @@ def tear_down(self): def visualise_result(self, show_figure_on_screen=True, save_path=None): initial_pressure = load_data_field( - self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_INITIAL_PRESSURE, + self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_INITIAL_PRESSURE, wavelength=self.settings[Tags.WAVELENGTH]) plt.figure(figsize=(9, 3)) diff --git a/simpa_tests/manual_tests/acoustic_forward_models/KWaveAcousticForwardConvenienceFunction.py b/simpa_tests/manual_tests/acoustic_forward_models/KWaveAcousticForwardConvenienceFunction.py index bf9f4e5f..de3e6f5d 100644 --- a/simpa_tests/manual_tests/acoustic_forward_models/KWaveAcousticForwardConvenienceFunction.py +++ b/simpa_tests/manual_tests/acoustic_forward_models/KWaveAcousticForwardConvenienceFunction.py @@ -33,7 +33,7 @@ class KWaveAcousticForwardConvenienceFunction(ManualIntegrationTestClass): def setup(self): """ Runs a pipeline consisting of volume creation and optical simulation. The resulting hdf5 file of the - simple test volume is saved at SIMPA_SAVE_PATH location defined in the path_config.env file. + simple test volume is saved at SIMPA_SAVE_DIRECTORY location defined in the path_config.env file. """ self.path_manager = PathManager() @@ -94,7 +94,7 @@ def setup(self): ] def teardown(self): - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) def perform_test(self): simulate(self.pipeline, self.settings, self.device) diff --git a/simpa_tests/manual_tests/acoustic_forward_models/MinimalKWaveTest.py b/simpa_tests/manual_tests/acoustic_forward_models/MinimalKWaveTest.py index 35e966f8..0d0fd120 100644 --- a/simpa_tests/manual_tests/acoustic_forward_models/MinimalKWaveTest.py +++ b/simpa_tests/manual_tests/acoustic_forward_models/MinimalKWaveTest.py @@ -89,31 +89,31 @@ def perform_test(self): Tags.DATA_FIELD_ALPHA_COEFF: self.alpha } save_file_path = generate_dict_path(Tags.SIMULATION_PROPERTIES) - save_hdf5(acoustic_properties, self.settings[Tags.SIMPA_OUTPUT_PATH], save_file_path) + save_hdf5(acoustic_properties, self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], save_file_path) optical_output = { Tags.DATA_FIELD_INITIAL_PRESSURE: {self.settings[Tags.WAVELENGTHS][0]: self.initial_pressure} } optical_output_path = generate_dict_path(Tags.OPTICAL_MODEL_OUTPUT_NAME) - save_hdf5(optical_output, self.settings[Tags.SIMPA_OUTPUT_PATH], optical_output_path) + save_hdf5(optical_output, self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], optical_output_path) KWaveAdapter(self.settings).run(self.pa_device) DelayAndSumAdapter(self.settings).run(self.pa_device) - self.reconstructed_image_1000 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], + self.reconstructed_image_1000 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field=Tags.DATA_FIELD_RECONSTRUCTED_DATA, wavelength=self.settings[Tags.WAVELENGTHS][0]) self.settings.get_reconstruction_settings()[Tags.DATA_FIELD_SPEED_OF_SOUND] = self.SPEED_OF_SOUND * 1.025 DelayAndSumAdapter(self.settings).run(self.pa_device) - self.reconstructed_image_1050 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], + self.reconstructed_image_1050 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field=Tags.DATA_FIELD_RECONSTRUCTED_DATA, wavelength=self.settings[Tags.WAVELENGTHS][0]) self.settings.get_reconstruction_settings()[Tags.DATA_FIELD_SPEED_OF_SOUND] = self.SPEED_OF_SOUND * 0.975 DelayAndSumAdapter(self.settings).run(self.pa_device) - self.reconstructed_image_950 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], + self.reconstructed_image_950 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], data_field=Tags.DATA_FIELD_RECONSTRUCTED_DATA, wavelength=self.settings[Tags.WAVELENGTHS][0]) diff --git a/simpa_tests/manual_tests/digital_device_twins/SimulationWithMSOTInvision.py b/simpa_tests/manual_tests/digital_device_twins/SimulationWithMSOTInvision.py index 81f5017b..f3e32a56 100644 --- a/simpa_tests/manual_tests/digital_device_twins/SimulationWithMSOTInvision.py +++ b/simpa_tests/manual_tests/digital_device_twins/SimulationWithMSOTInvision.py @@ -194,7 +194,7 @@ def perform_test(self): simulate(simulation_pipeline=pipeline, digital_device_twin=device, settings=self.settings) def tear_down(self): - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) def visualise_result(self, show_figure_on_screen=True, save_path=None): visualise_data(settings=self.settings, diff --git a/simpa_tests/manual_tests/image_reconstruction/DelayAndSumReconstruction.py b/simpa_tests/manual_tests/image_reconstruction/DelayAndSumReconstruction.py index c20d2901..b73677f0 100644 --- a/simpa_tests/manual_tests/image_reconstruction/DelayAndSumReconstruction.py +++ b/simpa_tests/manual_tests/image_reconstruction/DelayAndSumReconstruction.py @@ -37,12 +37,12 @@ def test_reconstruction_of_simulation(self): simulate(SIMUATION_PIPELINE, self.settings, self.device) - self.reconstructed_image_pipeline = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_RECONSTRUCTED_DATA, + self.reconstructed_image_pipeline = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_RECONSTRUCTED_DATA, self.settings[Tags.WAVELENGTH]) def test_convenience_function(self): # Load simulated time series data - time_series_sensor_data = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], + time_series_sensor_data = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_TIME_SERIES_DATA, self.settings[Tags.WAVELENGTH]) reconstruction_settings = self.settings.get_reconstruction_settings() diff --git a/simpa_tests/manual_tests/image_reconstruction/DelayMultiplyAndSumReconstruction.py b/simpa_tests/manual_tests/image_reconstruction/DelayMultiplyAndSumReconstruction.py index e9a458a6..66f4b84d 100644 --- a/simpa_tests/manual_tests/image_reconstruction/DelayMultiplyAndSumReconstruction.py +++ b/simpa_tests/manual_tests/image_reconstruction/DelayMultiplyAndSumReconstruction.py @@ -36,12 +36,12 @@ def test_reconstruction_of_simulation(self): simulate(SIMUATION_PIPELINE, self.settings, self.device) - self.reconstructed_image_pipeline = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_RECONSTRUCTED_DATA, + self.reconstructed_image_pipeline = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_RECONSTRUCTED_DATA, self.settings[Tags.WAVELENGTH]) def test_convenience_function(self): # Load simulated time series data - time_series_sensor_data = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], + time_series_sensor_data = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_TIME_SERIES_DATA, self.settings[Tags.WAVELENGTH]) reconstruction_settings = self.settings.get_reconstruction_settings() diff --git a/simpa_tests/manual_tests/image_reconstruction/SignedDelayMultiplyAndSumReconstruction.py b/simpa_tests/manual_tests/image_reconstruction/SignedDelayMultiplyAndSumReconstruction.py index ff61cc4e..00b0540b 100644 --- a/simpa_tests/manual_tests/image_reconstruction/SignedDelayMultiplyAndSumReconstruction.py +++ b/simpa_tests/manual_tests/image_reconstruction/SignedDelayMultiplyAndSumReconstruction.py @@ -37,12 +37,12 @@ def test_reconstruction_of_simulation(self): simulate(SIMUATION_PIPELINE, self.settings, self.device) - self.reconstructed_image_pipeline = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_RECONSTRUCTED_DATA, + self.reconstructed_image_pipeline = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_RECONSTRUCTED_DATA, self.settings[Tags.WAVELENGTH]) def test_convenience_function(self): # Load simulated time series data - time_series_sensor_data = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], + time_series_sensor_data = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_TIME_SERIES_DATA, self.settings[Tags.WAVELENGTH]) reconstruction_settings = self.settings.get_reconstruction_settings() diff --git a/simpa_tests/manual_tests/image_reconstruction/TimeReversalReconstruction.py b/simpa_tests/manual_tests/image_reconstruction/TimeReversalReconstruction.py index cad47201..ba703151 100644 --- a/simpa_tests/manual_tests/image_reconstruction/TimeReversalReconstruction.py +++ b/simpa_tests/manual_tests/image_reconstruction/TimeReversalReconstruction.py @@ -36,7 +36,7 @@ def test_reconstruction_of_simulation(self): simulate(SIMULATION_PIPELINE, self.settings, self.device) - self.reconstructed_image_pipeline = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_RECONSTRUCTED_DATA, + self.reconstructed_image_pipeline = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_RECONSTRUCTED_DATA, self.settings[Tags.WAVELENGTH]) def test_convenience_function(self): diff --git a/simpa_tests/manual_tests/optical_forward_models/AbsorptionAndScatteringWithInifinitesimalSlabExperiment.py b/simpa_tests/manual_tests/optical_forward_models/AbsorptionAndScatteringWithInifinitesimalSlabExperiment.py index 04199a5e..3917e17d 100644 --- a/simpa_tests/manual_tests/optical_forward_models/AbsorptionAndScatteringWithInifinitesimalSlabExperiment.py +++ b/simpa_tests/manual_tests/optical_forward_models/AbsorptionAndScatteringWithInifinitesimalSlabExperiment.py @@ -115,7 +115,7 @@ def setup(self): self.device.add_illumination_geometry(PencilBeamIlluminationGeometry()) def teardown(self): - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) def test_both(self): """ @@ -215,13 +215,13 @@ def test_simulation(self, distance=10, expected_decay_ratio=np.e, scattering_val simulate(pipeline, self.settings, self.device) # run_optical_forward_model(self.settings) - fluence = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_FLUENCE, + fluence = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_FLUENCE, self.settings[Tags.WAVELENGTH]) - absorption = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_ABSORPTION_PER_CM, + absorption = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_ABSORPTION_PER_CM, self.settings[Tags.WAVELENGTH]) - scattering = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_SCATTERING_PER_CM, + scattering = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_SCATTERING_PER_CM, self.settings[Tags.WAVELENGTH]) - anisotropy = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_ANISOTROPY, + anisotropy = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_ANISOTROPY, self.settings[Tags.WAVELENGTH]) early_point = int((self.z_dim / 2 - distance / 2) / self.settings[Tags.SPACING_MM]) diff --git a/simpa_tests/manual_tests/optical_forward_models/AbsorptionAndScatteringWithinHomogenousMedium.py b/simpa_tests/manual_tests/optical_forward_models/AbsorptionAndScatteringWithinHomogenousMedium.py index 1d62a943..d3508b27 100644 --- a/simpa_tests/manual_tests/optical_forward_models/AbsorptionAndScatteringWithinHomogenousMedium.py +++ b/simpa_tests/manual_tests/optical_forward_models/AbsorptionAndScatteringWithinHomogenousMedium.py @@ -94,7 +94,7 @@ def setup(self): self.device.add_illumination_geometry(PencilBeamIlluminationGeometry()) def teardown(self): - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) def test_low_scattering(self): """ @@ -232,7 +232,7 @@ def test_simulation(self, scattering_value_1=1e-30, simulate(pipeline, self.settings, self.device) - fluence_1 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_FLUENCE, + fluence_1 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_FLUENCE, self.settings[Tags.WAVELENGTH]) # RUN SIMULATION 2 @@ -253,7 +253,7 @@ def test_simulation(self, scattering_value_1=1e-30, simulate(pipeline, self.settings, self.device) - fluence_2 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_FLUENCE, + fluence_2 = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_FLUENCE, self.settings[Tags.WAVELENGTH]) illuminator_point = int((self.xy_dim / 2) / self.settings[Tags.SPACING_MM]) - 1 diff --git a/simpa_tests/manual_tests/optical_forward_models/CompareMCXResultsWithDiffusionTheory.py b/simpa_tests/manual_tests/optical_forward_models/CompareMCXResultsWithDiffusionTheory.py index 939fc870..e86b5a2a 100644 --- a/simpa_tests/manual_tests/optical_forward_models/CompareMCXResultsWithDiffusionTheory.py +++ b/simpa_tests/manual_tests/optical_forward_models/CompareMCXResultsWithDiffusionTheory.py @@ -145,7 +145,7 @@ def perform_test(self): self.results.append(self.test_spacing_long()) def assertDiffusionTheory(self, distance, spacing): - fluence = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_FLUENCE, + fluence = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_FLUENCE, self.settings[Tags.WAVELENGTH]) number_of_measurements = np.arange(0, int(distance/self.settings[Tags.SPACING_MM]), 1) measurement_distances = number_of_measurements * self.settings[Tags.SPACING_MM] diff --git a/simpa_tests/manual_tests/optical_forward_models/ComputeDiffuseReflectance.py b/simpa_tests/manual_tests/optical_forward_models/ComputeDiffuseReflectance.py index 45304974..a748bd55 100644 --- a/simpa_tests/manual_tests/optical_forward_models/ComputeDiffuseReflectance.py +++ b/simpa_tests/manual_tests/optical_forward_models/ComputeDiffuseReflectance.py @@ -151,11 +151,11 @@ def perform_test(self): self.results.append(self.test_spacing_long()) def assertDiffusionTheory(self, distance, spacing): - fluence = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_FLUENCE, + fluence = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_FLUENCE, self.settings[Tags.WAVELENGTH]) - ref = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_DIFFUSE_REFLECTANCE, + ref = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_DIFFUSE_REFLECTANCE, self.settings[Tags.WAVELENGTH]) - ref_pos = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_DIFFUSE_REFLECTANCE_POS, + ref_pos = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_DIFFUSE_REFLECTANCE_POS, self.settings[Tags.WAVELENGTH]) reflectance = np.zeros((ref_pos[:, 0].max() + 1, ref_pos[:, 1].max() + 1)) reflectance[ref_pos[:, 0], ref_pos[:, 1], ...] = ref diff --git a/simpa_tests/manual_tests/processing_components/QPAIReconstruction.py b/simpa_tests/manual_tests/processing_components/QPAIReconstruction.py index 21bf4e56..d82f99f4 100644 --- a/simpa_tests/manual_tests/processing_components/QPAIReconstruction.py +++ b/simpa_tests/manual_tests/processing_components/QPAIReconstruction.py @@ -30,7 +30,7 @@ class TestqPAIReconstruction(ManualIntegrationTestClass): def setup(self): """ Runs a pipeline consisting of volume creation and optical simulation. The resulting hdf5 file of the - simple test volume is saved at SIMPA_SAVE_PATH location defined in the path_config.env file. + simple test volume is saved at SIMPA_SAVE_DIRECTORY location defined in the path_config.env file. """ self.path_manager = PathManager() @@ -110,7 +110,7 @@ def perform_test(self): self.settings["iterative_qpai_reconstruction"] = component_settings self.wavelength = self.settings[Tags.WAVELENGTH] - absorption_gt = load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_ABSORPTION_PER_CM, + absorption_gt = load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_ABSORPTION_PER_CM, self.wavelength) # if the initial pressure is resampled the ground truth has to be resampled to allow for comparison diff --git a/simpa_tests/manual_tests/processing_components/TestLinearUnmixingVisual.py b/simpa_tests/manual_tests/processing_components/TestLinearUnmixingVisual.py index 7f76e1ac..22241b61 100644 --- a/simpa_tests/manual_tests/processing_components/TestLinearUnmixingVisual.py +++ b/simpa_tests/manual_tests/processing_components/TestLinearUnmixingVisual.py @@ -82,22 +82,22 @@ def perform_test(self): self.logger.info("Testing linear unmixing...") # Load blood oxygen saturation - self.lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.LINEAR_UNMIXING_RESULT) + self.lu_results = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.LINEAR_UNMIXING_RESULT) self.sO2 = self.lu_results["sO2"] # Load reference absorption for the first wavelength - self.mua = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_ABSORPTION_PER_CM, + self.mua = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_ABSORPTION_PER_CM, wavelength=self.VISUAL_WAVELENGTHS[0]) def tear_down(self): # clean up file after testing - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) def visualise_result(self, show_figure_on_screen=True, save_path=None): # Visualize linear unmixing result # The shape of the linear unmixing result should take after the reference absorption - ground_truth_sO2 = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_PATH], Tags.DATA_FIELD_OXYGENATION) + ground_truth_sO2 = sp.load_data_field(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH], Tags.DATA_FIELD_OXYGENATION) y_dim = int(self.mua.shape[1] / 2) plt.figure(figsize=(9, 3)) diff --git a/simpa_tests/manual_tests/test_with_experimental_measurements/ReproduceDISMeasurements.py b/simpa_tests/manual_tests/test_with_experimental_measurements/ReproduceDISMeasurements.py index 9c8ab38f..73e0fd78 100644 --- a/simpa_tests/manual_tests/test_with_experimental_measurements/ReproduceDISMeasurements.py +++ b/simpa_tests/manual_tests/test_with_experimental_measurements/ReproduceDISMeasurements.py @@ -40,7 +40,7 @@ class TestDoubleIntegratingSphereSimulation(ManualIntegrationTestClass): def tear_down(self): - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) def setup(self): diff --git a/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py b/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py index 230a8e3c..412700d2 100644 --- a/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py +++ b/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py @@ -83,7 +83,7 @@ def perform_test(self): device_position_mm=np.asarray([20, 10, 0]))) def tear_down(self): - os.remove(self.settings[Tags.SIMPA_OUTPUT_PATH]) + os.remove(self.settings[Tags.SIMPA_OUTPUT_FILE_PATH]) def visualise_result(self, show_figure_on_screen=True, save_path=None): From e2b2d19f369e16d36a313d039d7a188901473097 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 16 Aug 2024 14:37:52 +0200 Subject: [PATCH 19/35] Renamed rounding function for clarity --- .../monospectral/field_of_view_cropping.py | 4 ++-- .../reconstruction_module/reconstruction_utils.py | 4 ++-- .../reconstruction_module/time_reversal_adapter.py | 6 +++--- simpa/utils/__init__.py | 2 +- simpa/utils/calculate.py | 10 +++++----- simpa/utils/libraries/heterogeneity_generator.py | 4 ++-- .../utils/libraries/structure_library/StructureBase.py | 8 ++++---- simpa_examples/create_a_custom_digital_device_twin.py | 2 +- simpa_examples/segmentation_loader.py | 2 +- .../manual_tests/volume_creation/SegmentationLoader.py | 2 +- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/simpa/core/processing_components/monospectral/field_of_view_cropping.py b/simpa/core/processing_components/monospectral/field_of_view_cropping.py index 683b6c28..cbbfcafb 100644 --- a/simpa/core/processing_components/monospectral/field_of_view_cropping.py +++ b/simpa/core/processing_components/monospectral/field_of_view_cropping.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT from simpa.core.simulation_modules.reconstruction_module.reconstruction_utils import compute_image_dimensions -from simpa.utils import Tags, Settings, round_away_from_zero +from simpa.utils import Tags, Settings, round_x5_away_from_zero from simpa.utils.constants import property_tags, wavelength_independent_properties, toolkit_tags from simpa.io_handling import load_data_field, save_data_field from simpa.core.processing_components import ProcessingComponentBase @@ -48,7 +48,7 @@ def run(self, device: DigitalDeviceTwinBase): self.logger.debug(f"FOV (mm): {field_of_view_mm}") _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, self.global_settings[Tags.SPACING_MM], self.logger) field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering - field_of_view_voxels = [round_away_from_zero(dim) for dim in field_of_view_voxels] # cast to int + field_of_view_voxels = [round_x5_away_from_zero(dim) for dim in field_of_view_voxels] # cast to int self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py index 8066ab92..790f3b33 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py @@ -9,7 +9,7 @@ from simpa.utils.settings import Settings from simpa.io_handling.io_hdf5 import load_data_field from simpa.utils import Tags -from simpa.utils import round_away_from_zero +from simpa.utils import round_x5_away_from_zero import torch import torch.fft from torch import Tensor @@ -484,7 +484,7 @@ def compute_for_one_dimension(start_in_mm: float, end_in_mm: float) -> Tuple[int start_temp = start_in_mm / spacing_in_mm end_temp = end_in_mm / spacing_in_mm dim_temp = np.abs(end_temp - start_temp) - dim = round_away_from_zero(dim_temp) + dim = round_x5_away_from_zero(dim_temp) diff = dim_temp - dim # the sign is important here start = start_temp - np.sign(start_temp) * diff/2 end = end_temp - np.sign(end_temp) * diff/2 diff --git a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py index 9704073c..1c0a52f8 100644 --- a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT from simpa.core.simulation_modules.reconstruction_module.reconstruction_utils import compute_image_dimensions -from simpa.utils import Tags, round_away_from_zero +from simpa.utils import Tags, round_x5_away_from_zero from simpa.utils.matlab import generate_matlab_cmd from simpa.utils.settings import Settings from simpa.core.simulation_modules.reconstruction_module import ReconstructionAdapterBase @@ -57,7 +57,7 @@ def get_acoustic_properties(self, input_data: dict, detection_geometry): detector_positions = detection_geometry.get_detector_element_positions_accounting_for_device_position_mm() # we add eps of 1e-10 because numpy rounds 0.5 to the next even number - detector_positions_voxels = round_away_from_zero(detector_positions / spacing_in_mm + 1e-10) + detector_positions_voxels = round_x5_away_from_zero(detector_positions / spacing_in_mm + 1e-10) # plus 2 because of off- volume_x_dim = int(np.ceil(self.global_settings[Tags.DIM_VOLUME_X_MM] / spacing_in_mm) + 1) @@ -185,7 +185,7 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry): field_of_view_mm = detection_geometry.get_field_of_view_mm() _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, spacing_in_mm, self.logger) field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering - field_of_view_voxels = [round_away_from_zero(dim) for dim in field_of_view_voxels] # cast to int + field_of_view_voxels = [round_x5_away_from_zero(dim) for dim in field_of_view_voxels] # cast to int self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 diff --git a/simpa/utils/__init__.py b/simpa/utils/__init__.py index c532cd61..c8484709 100644 --- a/simpa/utils/__init__.py +++ b/simpa/utils/__init__.py @@ -31,7 +31,7 @@ from .calculate import calculate_oxygenation from .calculate import calculate_gruneisen_parameter_from_temperature from .calculate import randomize_uniform -from .calculate import round_away_from_zero +from .calculate import round_x5_away_from_zero from .deformation_manager import create_deformation_settings from .deformation_manager import get_functional_from_deformation_settings diff --git a/simpa/utils/calculate.py b/simpa/utils/calculate.py index 693c7686..ed5aa76e 100644 --- a/simpa/utils/calculate.py +++ b/simpa/utils/calculate.py @@ -135,7 +135,7 @@ def spline_evaluator2d_voxel(x: int, y: int, spline: Union[list, np.ndarray], of :return: True if the point (x, y) lies within the range around the spline, False otherwise. """ elevation = spline[x] - y_value = round_away_from_zero(elevation + offset_voxel) + y_value = round_x5_away_from_zero(elevation + offset_voxel) if y_value <= y < thickness_voxel + y_value: return True else: @@ -294,10 +294,10 @@ def are_equal(obj1: Union[list, tuple, np.ndarray, object], obj2: Union[list, tu return obj1 == obj2 -def round_away_from_zero(x): +def round_x5_away_from_zero(x): """ - Round a number away from zero. The np.round function rounds 0.5 to the nearest even number, which is not always the - desired behavior. This function always rounds 0.5 away from zero. For example, 0.5 will be rounded to 1, and -0.5 + Round a number away from zero. The np.round function rounds x.5 to the nearest even number, which is not always the + desired behavior. This function always rounds x.5 away from zero. For example, x.5 will be rounded to 1, and -x.5 will be rounded to -1. All other numbers are rounded to the nearest integer. :param x: input number or array of numbers :return: rounded number or array of numbers @@ -311,7 +311,7 @@ def round_single_value(value): if isinstance(x, (np.ndarray, list, tuple)): # Apply rounding function to each element in the array - return np.array([round_away_from_zero(val) for val in x], dtype=int) + return np.array([round_x5_away_from_zero(val) for val in x], dtype=int) else: # Apply rounding to a single value return round_single_value(x) diff --git a/simpa/utils/libraries/heterogeneity_generator.py b/simpa/utils/libraries/heterogeneity_generator.py index 6cbaa683..b9f8cea2 100644 --- a/simpa/utils/libraries/heterogeneity_generator.py +++ b/simpa/utils/libraries/heterogeneity_generator.py @@ -6,7 +6,7 @@ from sklearn.datasets import make_blobs from scipy.ndimage.filters import gaussian_filter from skimage import transform -from simpa.utils import Tags, round_away_from_zero +from simpa.utils import Tags, round_x5_away_from_zero from typing import Union, Optional from simpa.log import Logger @@ -135,7 +135,7 @@ def __init__(self, xdim, ydim, zdim, spacing_mm, num_centers=None, cluster_std=N super().__init__(xdim, ydim, zdim, spacing_mm, target_mean, target_std, target_min, target_max) if num_centers is None: - num_centers = round_away_from_zero(np.float_power((xdim * ydim * zdim) * spacing_mm, 1 / 3)) + num_centers = round_x5_away_from_zero(np.float_power((xdim * ydim * zdim) * spacing_mm, 1 / 3)) if cluster_std is None: cluster_std = 1 diff --git a/simpa/utils/libraries/structure_library/StructureBase.py b/simpa/utils/libraries/structure_library/StructureBase.py index fc8dc2c4..3ffc31d1 100644 --- a/simpa/utils/libraries/structure_library/StructureBase.py +++ b/simpa/utils/libraries/structure_library/StructureBase.py @@ -7,7 +7,7 @@ import numpy as np from simpa.log import Logger -from simpa.utils import Settings, Tags, get_functional_from_deformation_settings, round_away_from_zero +from simpa.utils import Settings, Tags, get_functional_from_deformation_settings, round_x5_away_from_zero from simpa.utils.libraries.molecule_library import MolecularComposition from simpa.utils.tissue_properties import TissueProperties from simpa.utils.processing_device import get_processing_device @@ -30,9 +30,9 @@ def __init__(self, global_settings: Settings, self.logger = Logger() self.voxel_spacing = global_settings[Tags.SPACING_MM] - volume_x_dim = round_away_from_zero(global_settings[Tags.DIM_VOLUME_X_MM] / self.voxel_spacing) - volume_y_dim = round_away_from_zero(global_settings[Tags.DIM_VOLUME_Y_MM] / self.voxel_spacing) - volume_z_dim = round_away_from_zero(global_settings[Tags.DIM_VOLUME_Z_MM] / self.voxel_spacing) + volume_x_dim = round_x5_away_from_zero(global_settings[Tags.DIM_VOLUME_X_MM] / self.voxel_spacing) + volume_y_dim = round_x5_away_from_zero(global_settings[Tags.DIM_VOLUME_Y_MM] / self.voxel_spacing) + volume_z_dim = round_x5_away_from_zero(global_settings[Tags.DIM_VOLUME_Z_MM] / self.voxel_spacing) self.volume_dimensions_voxels = np.asarray([volume_x_dim, volume_y_dim, volume_z_dim]) self.volume_dimensions_mm = self.volume_dimensions_voxels * self.voxel_spacing diff --git a/simpa_examples/create_a_custom_digital_device_twin.py b/simpa_examples/create_a_custom_digital_device_twin.py index bf5a5f02..b8a6c0cb 100644 --- a/simpa_examples/create_a_custom_digital_device_twin.py +++ b/simpa_examples/create_a_custom_digital_device_twin.py @@ -41,7 +41,7 @@ def __init__(self, device_position_mm): positions = device.get_detection_geometry().get_detector_element_positions_accounting_for_device_position_mm() detector_elements = device.get_detection_geometry().get_detector_element_orientations() - positions = sp.round_away_from_zero(positions/settings[Tags.SPACING_MM]) + positions = sp.round_x5_away_from_zero(positions/settings[Tags.SPACING_MM]) xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(device.get_detection_geometry().field_of_view_extent_mm, settings[Tags.SPACING_MM], Logger()) import matplotlib.pyplot as plt diff --git a/simpa_examples/segmentation_loader.py b/simpa_examples/segmentation_loader.py index 08a2478e..bf219538 100644 --- a/simpa_examples/segmentation_loader.py +++ b/simpa_examples/segmentation_loader.py @@ -40,7 +40,7 @@ def run_segmentation_loader(spacing: float | int = 1.0, input_spacing: float | i label_mask = np.reshape(label_mask, (label_mask.shape[0], 1, label_mask.shape[1])) segmentation_volume_tiled = np.tile(label_mask, (1, 128, 1)) - segmentation_volume_mask = sp.round_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/spacing, + segmentation_volume_mask = sp.round_x5_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/spacing, order=0)).astype(int) def segmentation_class_mapping(): diff --git a/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py b/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py index 08be86fe..f464b13b 100644 --- a/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py +++ b/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py @@ -24,7 +24,7 @@ def setup(self): label_mask = np.reshape(label_mask, (400, 1, 400)) input_spacing = 0.2 segmentation_volume_tiled = np.tile(label_mask, (1, 128, 1)) - segmentation_volume_mask = sp.round_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/target_spacing, + segmentation_volume_mask = sp.round_x5_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/target_spacing, order=0)).astype(int) def segmentation_class_mapping(): From e1699296b37340ff18a390917452b8e9e2594229 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 16 Aug 2024 14:44:59 +0200 Subject: [PATCH 20/35] Added some tests for rounding function --- simpa/utils/calculate.py | 2 +- .../automatic_tests/test_calculation_utils.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/simpa/utils/calculate.py b/simpa/utils/calculate.py index ed5aa76e..b8fd6372 100644 --- a/simpa/utils/calculate.py +++ b/simpa/utils/calculate.py @@ -294,7 +294,7 @@ def are_equal(obj1: Union[list, tuple, np.ndarray, object], obj2: Union[list, tu return obj1 == obj2 -def round_x5_away_from_zero(x): +def round_x5_away_from_zero(x: Union[float, np.ndarray]) -> Union[int, np.ndarray]: """ Round a number away from zero. The np.round function rounds x.5 to the nearest even number, which is not always the desired behavior. This function always rounds x.5 away from zero. For example, x.5 will be rounded to 1, and -x.5 diff --git a/simpa_tests/automatic_tests/test_calculation_utils.py b/simpa_tests/automatic_tests/test_calculation_utils.py index fc5c4fd9..02c34f34 100644 --- a/simpa_tests/automatic_tests/test_calculation_utils.py +++ b/simpa_tests/automatic_tests/test_calculation_utils.py @@ -9,6 +9,7 @@ from simpa.utils.calculate import randomize_uniform from simpa.utils.calculate import calculate_gruneisen_parameter_from_temperature from simpa.utils.calculate import positive_gauss +from simpa.utils.calculate import round_x5_away_from_zero import numpy as np @@ -135,3 +136,26 @@ def test_positive_Gauss(self): std = np.random.rand(1)[0] random_value = positive_gauss(mean, std) assert random_value > float(0), "positive Gauss value outside the desired range and negative" + + def test_rounding_function(self): + assert round_x5_away_from_zero(0.5) == 1 + assert round_x5_away_from_zero(0.4) == 0 + assert round_x5_away_from_zero(0.6) == 1 + assert round_x5_away_from_zero(0.1) == 0 + assert round_x5_away_from_zero(0.9) == 1 + assert round_x5_away_from_zero(0.0) == 0 + assert round_x5_away_from_zero(1.0) == 1 + assert (round_x5_away_from_zero(np.arange(0, 15) + 0.5) == np.arange(0, 15) + 1).all() + assert (round_x5_away_from_zero(np.arange(0, 15) + 0.4) == np.arange(0, 15)).all() + assert (round_x5_away_from_zero(np.arange(0, 15) + 0.6) == np.arange(0, 15) + 1).all() + assert (round_x5_away_from_zero(np.arange(0, 15) + 0.1) == np.arange(0, 15)).all() + assert (round_x5_away_from_zero(np.arange(0, 15) + 0.9) == np.arange(0, 15) + 1).all() + assert (round_x5_away_from_zero(np.arange(0, 15) + 0.0) == np.arange(0, 15)).all() + assert (round_x5_away_from_zero(np.arange(0, 15) + 1.0) == np.arange(0, 15) + 1).all() + assert (round_x5_away_from_zero(np.arange(-15, 0) - 0.5) == np.arange(-15, 0) - 1).all() + assert (round_x5_away_from_zero(np.arange(-15, 0) - 0.4) == np.arange(-15, 0)).all() + assert (round_x5_away_from_zero(np.arange(-15, 0) - 0.6) == np.arange(-15, 0) - 1).all() + assert (round_x5_away_from_zero(np.arange(-15, 0) - 0.1) == np.arange(-15, 0)).all() + assert (round_x5_away_from_zero(np.arange(-15, 0) - 0.9) == np.arange(-15, 0) - 1).all() + assert (round_x5_away_from_zero(np.arange(-15, 0) - 0.0) == np.arange(-15, 0)).all() + assert (round_x5_away_from_zero(np.arange(-15, 0) - 1.0) == np.arange(-15, 0) - 1).all() From cb194ee977918f30bfb11d69c198d35b00607df6 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 16 Aug 2024 14:51:49 +0200 Subject: [PATCH 21/35] Switched to truncating floats in places where we account for rounding errors --- .../monospectral/field_of_view_cropping.py | 2 +- .../reconstruction_module/time_reversal_adapter.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/simpa/core/processing_components/monospectral/field_of_view_cropping.py b/simpa/core/processing_components/monospectral/field_of_view_cropping.py index cbbfcafb..890e8ac5 100644 --- a/simpa/core/processing_components/monospectral/field_of_view_cropping.py +++ b/simpa/core/processing_components/monospectral/field_of_view_cropping.py @@ -48,7 +48,7 @@ def run(self, device: DigitalDeviceTwinBase): self.logger.debug(f"FOV (mm): {field_of_view_mm}") _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, self.global_settings[Tags.SPACING_MM], self.logger) field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering - field_of_view_voxels = [round_x5_away_from_zero(dim) for dim in field_of_view_voxels] # cast to int + field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 diff --git a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py index 1c0a52f8..e8115896 100644 --- a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py @@ -57,7 +57,7 @@ def get_acoustic_properties(self, input_data: dict, detection_geometry): detector_positions = detection_geometry.get_detector_element_positions_accounting_for_device_position_mm() # we add eps of 1e-10 because numpy rounds 0.5 to the next even number - detector_positions_voxels = round_x5_away_from_zero(detector_positions / spacing_in_mm + 1e-10) + detector_positions_voxels = round_x5_away_from_zero(detector_positions / spacing_in_mm) # plus 2 because of off- volume_x_dim = int(np.ceil(self.global_settings[Tags.DIM_VOLUME_X_MM] / spacing_in_mm) + 1) @@ -185,7 +185,7 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry): field_of_view_mm = detection_geometry.get_field_of_view_mm() _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, spacing_in_mm, self.logger) field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering - field_of_view_voxels = [round_x5_away_from_zero(dim) for dim in field_of_view_voxels] # cast to int + field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 From 033f0e9ee3a6f9c40ce4624251c8b5871a95ee96 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 16 Aug 2024 14:52:03 +0200 Subject: [PATCH 22/35] Corrected device example --- simpa_examples/create_a_custom_digital_device_twin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpa_examples/create_a_custom_digital_device_twin.py b/simpa_examples/create_a_custom_digital_device_twin.py index b8a6c0cb..73dc7e0c 100644 --- a/simpa_examples/create_a_custom_digital_device_twin.py +++ b/simpa_examples/create_a_custom_digital_device_twin.py @@ -46,7 +46,7 @@ def __init__(self, device_position_mm): import matplotlib.pyplot as plt from matplotlib.patches import Rectangle - plt.gca().add_patch(Rectangle((xdim_start + 50/2, ydim_start), xdim + 50/2, -ydim, linewidth=1, edgecolor='r', facecolor='r', alpha=.5)) + plt.gca().add_patch(Rectangle((xdim_start + 50, ydim_start), xdim, -ydim, linewidth=1, edgecolor='r', facecolor='r', alpha=.5)) plt.scatter(positions[:, 0], positions[:, 2]) plt.quiver(positions[:, 0], positions[:, 2], detector_elements[:, 0], detector_elements[:, 2]) plt.show() From 6ee91c446897264da9aa5ad666b1cdb414aa538c Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Fri, 16 Aug 2024 18:08:54 +0200 Subject: [PATCH 23/35] Fixed renamed imports of model based adapter --- simpa_tests/manual_tests/executables/MATLABAdditionalFlags.py | 4 ++-- simpa_tests/manual_tests/executables/MCXAdditionalFlags.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/simpa_tests/manual_tests/executables/MATLABAdditionalFlags.py b/simpa_tests/manual_tests/executables/MATLABAdditionalFlags.py index 1f114949..2c006ac5 100644 --- a/simpa_tests/manual_tests/executables/MATLABAdditionalFlags.py +++ b/simpa_tests/manual_tests/executables/MATLABAdditionalFlags.py @@ -4,7 +4,7 @@ import os import numpy as np -from simpa import MCXAdapter, ModelBasedVolumeCreationAdapter, simulate, KWaveAdapter +from simpa import MCXAdapter, ModelBasedAdapter, simulate, KWaveAdapter from simpa.core.device_digital_twins import PhotoacousticDevice, PencilBeamIlluminationGeometry, LinearArrayDetectionGeometry from simpa.utils import Settings, Tags, TISSUE_LIBRARY, PathManager from simpa_tests.manual_tests import ManualIntegrationTestClass @@ -89,7 +89,7 @@ def setup(self): def run_simulation(self): # run pipeline including volume creation and optical mcx simulation and acoustic matlab kwave simulation pipeline = [ - ModelBasedVolumeCreationAdapter(self.settings), + ModelBasedAdapter(self.settings), MCXAdapter(self.settings), KWaveAdapter(self.settings) ] diff --git a/simpa_tests/manual_tests/executables/MCXAdditionalFlags.py b/simpa_tests/manual_tests/executables/MCXAdditionalFlags.py index b381045e..b9e1768b 100644 --- a/simpa_tests/manual_tests/executables/MCXAdditionalFlags.py +++ b/simpa_tests/manual_tests/executables/MCXAdditionalFlags.py @@ -4,7 +4,7 @@ import os import numpy as np -from simpa import MCXAdapter, ModelBasedVolumeCreationAdapter, simulate +from simpa import MCXAdapter, ModelBasedAdapter, simulate from simpa.core.device_digital_twins import PhotoacousticDevice, PencilBeamIlluminationGeometry from simpa.utils import Settings, Tags, TISSUE_LIBRARY, PathManager from simpa_tests.manual_tests import ManualIntegrationTestClass @@ -68,7 +68,7 @@ def setup(self): def run_simulation(self): # run pipeline including volume creation and optical mcx simulation pipeline = [ - ModelBasedVolumeCreationAdapter(self.settings), + ModelBasedAdapter(self.settings), MCXAdapter(self.settings), ] simulate(pipeline, self.settings, self.device) From 5dcc1caa3f65f0cfb09b44e5d5346a989aa3e125 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 10:19:59 +0200 Subject: [PATCH 24/35] Changed location of test reference figures on nextcloud --- simpa_tests/manual_tests/generate_overview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpa_tests/manual_tests/generate_overview.py b/simpa_tests/manual_tests/generate_overview.py index 128b10f2..9934cd50 100644 --- a/simpa_tests/manual_tests/generate_overview.py +++ b/simpa_tests/manual_tests/generate_overview.py @@ -57,7 +57,7 @@ def download_reference_images(self): # Remove the directory shutil.rmtree(ref_imgs_path) # nextcloud url with the reference images - self.nextcloud_url = "https://hub.dkfz.de/s/Xb96SFXbmiE5Fk8" # shared "reference_figures" folder on nextcloud + self.nextcloud_url = "https://hub.dkfz.de/s/nsXKMGAaN6tPPsq" # shared "reference_figures" folder on nextcloud # Specify the local directory to save the files zip_filepath = os.path.join(self.current_dir, "downloaded.zip") # Construct the download URL based on the public share link From cb095edcd4a8c22e088f93790f4210626fa34593 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 10:45:20 +0200 Subject: [PATCH 25/35] Added installation instructions for optional dependencies for profiling --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 851adc5b..e407c7a9 100755 --- a/README.md +++ b/README.md @@ -140,9 +140,9 @@ settings.set_acoustic_settings(acoustic_settings) settings.set_reconstruction_settings(reconstruction_settings) # Set the simulation pipeline -simulation_pipeline = [sp.VolumeCreatorModule(settings), - sp.OpticalForwardModule(settings), - sp.AcousticForwardModule(settings), +simulation_pipeline = [sp.VolumeCreationModule(settings), + sp.OpticalModule(settings), + sp.AcousticModule(settings), sp.ReconstructionModule(settings)] # Choose a PA device with device position in the volume @@ -189,8 +189,8 @@ Please see the github guidelines for creating pull requests: [https://docs.githu # Performance profiling When changing the SIMPA core, e.g., by refactoring/optimizing, or if you are curious about how fast your machine runs -SIMPA, you can run the SIMPA [benchmarking scripts](simpa_examples/benchmarking/run_benchmarking.sh). It is recommended -to run: +SIMPA, you can run the SIMPA [benchmarking scripts](simpa_examples/benchmarking/run_benchmarking.sh). Make sure to install the necessary dependencies via +`pip install .[profile]` and then run: ```bash bash ./run_benchmark.sh From fa6ea6341f9b9943f88cce95fa0464feab8ec94b Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 10:48:48 +0200 Subject: [PATCH 26/35] Renamed benchmarking .md file --- simpa_examples/benchmarking/get_final_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpa_examples/benchmarking/get_final_table.py b/simpa_examples/benchmarking/get_final_table.py index fc969dfe..680c2776 100644 --- a/simpa_examples/benchmarking/get_final_table.py +++ b/simpa_examples/benchmarking/get_final_table.py @@ -57,7 +57,7 @@ def get_final_table(savefolder: str = None): mean_df.to_csv(str(df_file).replace('.csv', '_mean.csv'), index=False) # save to markdown for nice visualization - mean_df.to_markdown(str(df_file).replace('.csv', '_mean.md'), index=False) + mean_df.to_markdown(str(df_file).replace('data_frame.csv', '_mean.md'), index=False) if __name__ == "__main__": From f959b2ca5c1bbd55f0c10c37ce6b052ea5e98bb2 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 11:40:53 +0200 Subject: [PATCH 27/35] Minor refactoring and clean-up --- CONTRIBUTING.md | 4 +- .../acoustic_module/acoustic_adapter_base.py | 2 +- simpa/utils/libraries/spectrum_library.py | 1 - simpa/utils/tissue_properties.py | 1 - simpa_examples/linear_unmixing.py | 4 +- ...e_vs_two_dimensional_simulation_example.py | 10 +++-- .../device_tests/test_field_of_view.py | 10 ++--- .../automatic_tests/test_additional_flags.py | 1 - .../test_heterogeneity_generator.py | 1 - .../automatic_tests/test_processing_device.py | 1 - .../TimeReversalReconstruction.py | 1 - simpa_tests/test_utils/tissue_models.py | 43 ------------------- 12 files changed, 16 insertions(+), 63 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 405e6957..95ebd68c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,10 +31,10 @@ In general the following steps are involved during a contribution: 4. Make sure that you've installed all the optional dependencies by running `pip install .[docs,profile,testing]` in the root directory of the repository. 5. Create feature branch from develop using the naming convention T_, - where represent the number Github assigned the created issue and describes + where represents the number Github assigned the created issue and describes what is being developed in CamelCaseNotation. Examples: `T13_FixSimulatorBug`, `T27_AddNewSimulator` -6. Perform test driven development on feature branch. +6. Perform test driven development on a feature branch. A new implemented feature / a bug fix should be accompanied by a test. Additionally, all previously existing tests must still pass after the contribution. 7. Run pre-commit hooks and make sure all hooks are passing. diff --git a/simpa/core/simulation_modules/acoustic_module/acoustic_adapter_base.py b/simpa/core/simulation_modules/acoustic_module/acoustic_adapter_base.py index 1dd4855c..51d44440 100644 --- a/simpa/core/simulation_modules/acoustic_module/acoustic_adapter_base.py +++ b/simpa/core/simulation_modules/acoustic_module/acoustic_adapter_base.py @@ -24,7 +24,7 @@ class AcousticAdapterBase(SimulationModuleBase): tag in the settings dictionary. - :param settings: The settings dictionary containing key-value pairs that determine the simulation. + :param global_settings: The settings dictionary containing key-value pairs that determine the simulation. Here, it must contain the Tags.ACOUSTIC_MODEL tag and any tags that might be required by the specific acoustic model. :raises AssertionError: an assertion error is raised if the Tags.ACOUSTIC_MODEL tag is not given or diff --git a/simpa/utils/libraries/spectrum_library.py b/simpa/utils/libraries/spectrum_library.py index 8ed9d196..6a1af7ee 100644 --- a/simpa/utils/libraries/spectrum_library.py +++ b/simpa/utils/libraries/spectrum_library.py @@ -9,7 +9,6 @@ import matplotlib.pylab as plt import torch from scipy import interpolate -from simpa.utils.libraries.literature_values import OpticalTissueProperties from simpa.utils.serializer import SerializableSIMPAClass diff --git a/simpa/utils/tissue_properties.py b/simpa/utils/tissue_properties.py index 7e84fdf1..059d6730 100644 --- a/simpa/utils/tissue_properties.py +++ b/simpa/utils/tissue_properties.py @@ -4,7 +4,6 @@ from simpa.utils.constants import property_tags from simpa.utils import Settings -from simpa.utils.processing_device import get_processing_device import torch diff --git a/simpa_examples/linear_unmixing.py b/simpa_examples/linear_unmixing.py index a0ffb8d6..0a2f5015 100644 --- a/simpa_examples/linear_unmixing.py +++ b/simpa_examples/linear_unmixing.py @@ -9,7 +9,7 @@ import simpa as sp from simpa import Tags from simpa.visualisation.matplotlib_data_visualisation import visualise_data -from simpa.utils.profiling import profile +from typing import Union # FIXME temporary workaround for newest Intel architectures os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" @@ -151,7 +151,7 @@ def create_example_tissue(): # Run simulation pipeline for all wavelengths in Tag.WAVELENGTHS pipeline = [ - sp.ModelBasedVolumeCreationAdapter(settings), + sp.ModelBasedAdapter(settings), sp.MCXAdapter(settings), sp.FieldOfViewCropping(settings), ] diff --git a/simpa_examples/three_vs_two_dimensional_simulation_example.py b/simpa_examples/three_vs_two_dimensional_simulation_example.py index 49aa79b7..6df7845f 100644 --- a/simpa_examples/three_vs_two_dimensional_simulation_example.py +++ b/simpa_examples/three_vs_two_dimensional_simulation_example.py @@ -187,8 +187,10 @@ def create_example_tissue(): else: WAVELENGTH = 800 - return (sp.load_data_field(settings[Tags.SIMPA_OUTPUT_PATH], sp.Tags.DATA_FIELD_TIME_SERIES_DATA, WAVELENGTH), - sp.load_data_field(settings[Tags.SIMPA_OUTPUT_PATH], sp.Tags.DATA_FIELD_RECONSTRUCTED_DATA, WAVELENGTH)) + return (sp.load_data_field(settings[Tags.SIMPA_OUTPUT_FILE_PATH], + sp.Tags.DATA_FIELD_TIME_SERIES_DATA, WAVELENGTH), + sp.load_data_field(settings[Tags.SIMPA_OUTPUT_FILE_PATH], + sp.Tags.DATA_FIELD_RECONSTRUCTED_DATA, WAVELENGTH)) two_d_time_series, two_d_recon = run_sim(False) three_d_time_series, three_d_recon = run_sim(True) @@ -214,5 +216,5 @@ def create_example_tissue(): parser.add_argument("--visualise", default=True, type=bool, help='whether to visualise the result') config = parser.parse_args() - run_3Dvs2D_simulation_example(spacing=config.spacing, path_manager=config.path_manager, - visualise=config.visualise) + run_three_vs_two_dimensional_simulation_example(spacing=config.spacing, path_manager=config.path_manager, + visualise=config.visualise) diff --git a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py index 93894a5f..07ab6593 100644 --- a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py +++ b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py @@ -131,9 +131,9 @@ def test_with_changing_spacing(self): if __name__ == '__main__': test = TestFieldOfView() test.setUp() - test.symmetric_test() - test.symmetric_test_with_small_spacing() - test.unsymmetric_test_with_small_spacing() - test.unsymmetric_test() - test.symmetric_test_with_odd_number_of_elements() + test.test_symmetric() + test.test_symmetric_with_small_spacing() + test.test_unsymmetric_with_small_spacing() + test.test_unsymmetric() + test.test_symmetric_with_odd_number_of_elements() test.test_with_changing_spacing() diff --git a/simpa_tests/automatic_tests/test_additional_flags.py b/simpa_tests/automatic_tests/test_additional_flags.py index 95e6d078..48968956 100644 --- a/simpa_tests/automatic_tests/test_additional_flags.py +++ b/simpa_tests/automatic_tests/test_additional_flags.py @@ -3,7 +3,6 @@ # SPDX-License-Identifier: MIT import unittest -import numpy as np from simpa import MCXReflectanceAdapter, MCXAdapter, KWaveAdapter, TimeReversalAdapter, Tags, Settings from simpa.utils.matlab import generate_matlab_cmd diff --git a/simpa_tests/automatic_tests/test_heterogeneity_generator.py b/simpa_tests/automatic_tests/test_heterogeneity_generator.py index e186b24a..252e84fb 100644 --- a/simpa_tests/automatic_tests/test_heterogeneity_generator.py +++ b/simpa_tests/automatic_tests/test_heterogeneity_generator.py @@ -6,7 +6,6 @@ import numpy as np import simpa as sp from simpa.utils import Tags -import torch class TestHeterogeneityGenerator(unittest.TestCase): diff --git a/simpa_tests/automatic_tests/test_processing_device.py b/simpa_tests/automatic_tests/test_processing_device.py index fa00b049..f77c74a4 100644 --- a/simpa_tests/automatic_tests/test_processing_device.py +++ b/simpa_tests/automatic_tests/test_processing_device.py @@ -2,7 +2,6 @@ # SPDX-FileCopyrightText: 2021 Janek Groehl # SPDX-License-Identifier: MIT -from simpa.log.file_logger import Logger from simpa.utils.tags import Tags from simpa.utils.settings import Settings from simpa.utils.processing_device import get_processing_device diff --git a/simpa_tests/manual_tests/image_reconstruction/TimeReversalReconstruction.py b/simpa_tests/manual_tests/image_reconstruction/TimeReversalReconstruction.py index ba703151..3e9353f0 100644 --- a/simpa_tests/manual_tests/image_reconstruction/TimeReversalReconstruction.py +++ b/simpa_tests/manual_tests/image_reconstruction/TimeReversalReconstruction.py @@ -8,7 +8,6 @@ from simpa.core.simulation import simulate from simpa import KWaveAdapter, MCXAdapter, \ TimeReversalAdapter, ModelBasedAdapter, GaussianNoise -from simpa import reconstruct_delay_and_sum_pytorch from simpa_tests.manual_tests import ReconstructionAlgorithmTestBaseClass # FIXME temporary workaround for newest Intel architectures diff --git a/simpa_tests/test_utils/tissue_models.py b/simpa_tests/test_utils/tissue_models.py index 4a234804..77052e2e 100644 --- a/simpa_tests/test_utils/tissue_models.py +++ b/simpa_tests/test_utils/tissue_models.py @@ -47,46 +47,3 @@ def create_simple_tissue_model(transducer_dim_in_mm: float, planar_dim_in_mm: fl adhere_to_deformation=False ) return tissue_dict - - -def create_heterogeneous_tissue_model(settings): - """ - This is a very simple example script of how to create a tissue definition. - It contains a muscular background, an epidermis layer on top of the muscles - and a blood vessel. - """ - v1_oxy = 1.0 - v2_oxy = 0.0 - bg_oxy = sp.RandomHeterogeneity().get_map() - background_dictionary = sp.Settings() - background_dictionary[Tags.MOLECULE_COMPOSITION] = (sp.MolecularCompositionGenerator() - .append(sp.MOLECULE_LIBRARY.oxyhemoglobin(bg_oxy)) - .append(sp.MOLECULE_LIBRARY.deoxyhemoglobin(1 - bg_oxy)) - .get_molecular_composition(sp.SegmentationClasses.BLOOD)) - - background_dictionary[Tags.STRUCTURE_TYPE] = Tags.BACKGROUND - - tissue_dict = sp.Settings() - tissue_dict[Tags.BACKGROUND] = background_dictionary - - tissue_dict["vessel_1"] = sp.define_circular_tubular_structure_settings( - tube_start_mm=[transducer_dim_in_mm / 2 - 10, 0, 5], - tube_end_mm=[transducer_dim_in_mm / 2 - 10, planar_dim_in_mm, 5], - molecular_composition=(sp.MolecularCompositionGenerator() - .append(sp.MOLECULE_LIBRARY.oxyhemoglobin(v1_oxy)) - .append(sp.MOLECULE_LIBRARY.deoxyhemoglobin(1 - v1_oxy)) - .get_molecular_composition(sp.SegmentationClasses.BLOOD)), - radius_mm=2, priority=3, consider_partial_volume=True, - adhere_to_deformation=False - ) - tissue_dict["vessel_2"] = sp.define_circular_tubular_structure_settings( - tube_start_mm=[transducer_dim_in_mm / 2, 0, 10], - tube_end_mm=[transducer_dim_in_mm / 2, planar_dim_in_mm, 10], - molecular_composition=(sp.MolecularCompositionGenerator() - .append(sp.MOLECULE_LIBRARY.oxyhemoglobin(v2_oxy)) - .append(sp.MOLECULE_LIBRARY.deoxyhemoglobin(1 - v2_oxy)) - .get_molecular_composition(sp.SegmentationClasses.BLOOD)), - radius_mm=3, priority=3, consider_partial_volume=True, - adhere_to_deformation=False - ) - return tissue_dict From b7836978058d9d665be1f010d93233a707a978f8 Mon Sep 17 00:00:00 2001 From: cbender98 Date: Fri, 23 Aug 2024 11:42:48 +0200 Subject: [PATCH 28/35] refactored code (based on ReconstructionAlgorithmTestBaseClass) --- .../PointSourceReconstruction.py | 570 ++++++++++-------- 1 file changed, 309 insertions(+), 261 deletions(-) diff --git a/simpa_tests/manual_tests/image_reconstruction/PointSourceReconstruction.py b/simpa_tests/manual_tests/image_reconstruction/PointSourceReconstruction.py index 60e39425..dc52e03f 100644 --- a/simpa_tests/manual_tests/image_reconstruction/PointSourceReconstruction.py +++ b/simpa_tests/manual_tests/image_reconstruction/PointSourceReconstruction.py @@ -2,275 +2,323 @@ # SPDX-FileCopyrightText: 2021 Janek Groehl # SPDX-License-Identifier: MIT -from simpa.utils import Tags +# FIXME temporary workaround for newest Intel architectures +import os -from simpa.core.simulation import simulate -from simpa.utils.settings import Settings -from simpa.utils.libraries.molecule_library import Molecule, MolecularCompositionGenerator -from simpa.utils.libraries.spectrum_library import AbsorptionSpectrumLibrary, AnisotropySpectrumLibrary, \ - ScatteringSpectrumLibrary -from simpa.visualisation.matplotlib_data_visualisation import visualise_data import numpy as np -from simpa.utils.path_manager import PathManager -from simpa import DelayAndSumAdapter, MCXAdapter, KWaveAdapter, ModelBasedAdapter, FieldOfViewCropping + +from simpa import (DelayAndSumAdapter, FieldOfViewCropping, KWaveAdapter, + MCXAdapter, ModelBasedAdapter) from simpa.core.device_digital_twins import * +from simpa.core.simulation import simulate from simpa.io_handling import load_data_field +from simpa.utils import Tags +from simpa.utils.libraries.molecule_library import ( + MolecularCompositionGenerator, Molecule) +from simpa.utils.libraries.spectrum_library import (AbsorptionSpectrumLibrary, + AnisotropySpectrumLibrary, + ScatteringSpectrumLibrary) +from simpa.utils.path_manager import PathManager +from simpa.utils.settings import Settings +from simpa.visualisation.matplotlib_data_visualisation import visualise_data +from simpa_tests.manual_tests import ReconstructionAlgorithmTestBaseClass -# FIXME temporary workaround for newest Intel architectures -import os os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" -SPEED_OF_SOUND = 1470 -VOLUME_TRANSDUCER_DIM_IN_MM = 90 -VOLUME_PLANAR_DIM_IN_MM = 20 -VOLUME_HEIGHT_IN_MM = 90 -SPACING = 0.4 -RANDOM_SEED = 4711 -# TODO: Please make sure that a valid path_config.env file is located in your home directory, or that you -# point to the correct file in the PathManager(). -path_manager = PathManager() - -# If VISUALIZE is set to True, the simulation result will be plotted -VISUALIZE = True - - -def create_point_source(): +class PointSourceReconstruction(ReconstructionAlgorithmTestBaseClass): """ - This is a very simple example script of how to create a tissue definition. - It contains a muscular background, an epidermis layer on top of the muscles - and a blood vessel. + TODO + """ - background_molecule = Molecule( - volume_fraction=1.0, - absorption_spectrum=AbsorptionSpectrumLibrary.CONSTANT_ABSORBER_ARBITRARY(1e-10), - scattering_spectrum=ScatteringSpectrumLibrary.CONSTANT_SCATTERING_ARBITRARY(1e-10), - anisotropy_spectrum=AnisotropySpectrumLibrary.CONSTANT_ANISOTROPY_ARBITRARY(1.0), - speed_of_sound=SPEED_OF_SOUND - ) - background_dictionary = Settings() - background_dictionary[Tags.MOLECULE_COMPOSITION] = (MolecularCompositionGenerator(). - append(background_molecule). + def __init__(self, speed_of_sound: float = 1470, volume_transducer_dim_in_mm: float = 90, + volume_planar_dim_in_mm: float = 20, volume_height_in_mm: float = 90, + spacing: float = 0.4): + + self.reconstructed_image_pipeline = None # TODO REMOVE + + self.SPEED_OF_SOUND = speed_of_sound + self.VOLUME_TRANSDUCER_DIM_IN_MM = volume_transducer_dim_in_mm + self.VOLUME_PLANAR_DIM_IN_MM = volume_planar_dim_in_mm + self.VOLUME_HEIGHT_IN_MM = volume_height_in_mm + self.SPACING = spacing + + self.RANDOM_SEED = 4711 + + def create_point_source(self): + """ + This is a very simple example script of how to create a tissue definition. + It contains a muscular background, an epidermis layer on top of the muscles + and a blood vessel. + """ + background_molecule = Molecule( + volume_fraction=1.0, + absorption_spectrum=AbsorptionSpectrumLibrary.CONSTANT_ABSORBER_ARBITRARY(1e-10), + scattering_spectrum=ScatteringSpectrumLibrary.CONSTANT_SCATTERING_ARBITRARY(1e-10), + anisotropy_spectrum=AnisotropySpectrumLibrary.CONSTANT_ANISOTROPY_ARBITRARY(1.0), + speed_of_sound=self.SPEED_OF_SOUND + ) + background_dictionary = Settings() + background_dictionary[Tags.MOLECULE_COMPOSITION] = (MolecularCompositionGenerator(). + append(background_molecule). + get_molecular_composition(-1)) + background_dictionary[Tags.STRUCTURE_TYPE] = Tags.BACKGROUND + + vessel_molecule = Molecule( + volume_fraction=1.0, + absorption_spectrum=AbsorptionSpectrumLibrary.CONSTANT_ABSORBER_ARBITRARY(2), + scattering_spectrum=ScatteringSpectrumLibrary.CONSTANT_SCATTERING_ARBITRARY(100), + anisotropy_spectrum=AnisotropySpectrumLibrary.CONSTANT_ANISOTROPY_ARBITRARY(0.9), + speed_of_sound=self.SPEED_OF_SOUND+100, + density=1100 + ) + + vessel_1_dictionary = Settings() + vessel_1_dictionary[Tags.PRIORITY] = 3 + vessel_1_dictionary[Tags.STRUCTURE_START_MM] = [self.VOLUME_TRANSDUCER_DIM_IN_MM/2-10, 0, 35] + vessel_1_dictionary[Tags.STRUCTURE_END_MM] = [self.VOLUME_TRANSDUCER_DIM_IN_MM/2-10, self.VOLUME_PLANAR_DIM_IN_MM, 35] + vessel_1_dictionary[Tags.STRUCTURE_RADIUS_MM] = self.SPACING + vessel_1_dictionary[Tags.MOLECULE_COMPOSITION] = (MolecularCompositionGenerator(). + append(vessel_molecule). get_molecular_composition(-1)) - background_dictionary[Tags.STRUCTURE_TYPE] = Tags.BACKGROUND - - vessel_molecule = Molecule( - volume_fraction=1.0, - absorption_spectrum=AbsorptionSpectrumLibrary.CONSTANT_ABSORBER_ARBITRARY(2), - scattering_spectrum=ScatteringSpectrumLibrary.CONSTANT_SCATTERING_ARBITRARY(100), - anisotropy_spectrum=AnisotropySpectrumLibrary.CONSTANT_ANISOTROPY_ARBITRARY(0.9), - speed_of_sound=SPEED_OF_SOUND+100, - density=1100 - ) - - vessel_1_dictionary = Settings() - vessel_1_dictionary[Tags.PRIORITY] = 3 - vessel_1_dictionary[Tags.STRUCTURE_START_MM] = [VOLUME_TRANSDUCER_DIM_IN_MM/2-10, 0, 35] - vessel_1_dictionary[Tags.STRUCTURE_END_MM] = [VOLUME_TRANSDUCER_DIM_IN_MM/2-10, VOLUME_PLANAR_DIM_IN_MM, 35] - vessel_1_dictionary[Tags.STRUCTURE_RADIUS_MM] = SPACING - vessel_1_dictionary[Tags.MOLECULE_COMPOSITION] = (MolecularCompositionGenerator(). - append(vessel_molecule). - get_molecular_composition(-1)) - vessel_1_dictionary[Tags.CONSIDER_PARTIAL_VOLUME] = True - vessel_1_dictionary[Tags.STRUCTURE_TYPE] = Tags.CIRCULAR_TUBULAR_STRUCTURE - - tissue_dict = Settings() - tissue_dict[Tags.BACKGROUND] = background_dictionary - tissue_dict["vessel_1"] = vessel_1_dictionary - return tissue_dict - - -# Seed the numpy random configuration prior to creating the global_settings file in -# order to ensure that the same volume -# is generated with the same random seed every time. - -np.random.seed(RANDOM_SEED) -VOLUME_NAME = "CompletePipelineTestMSOT_"+str(RANDOM_SEED) - -general_settings = { - # These parameters set the general properties of the simulated volume - Tags.RANDOM_SEED: RANDOM_SEED, - Tags.VOLUME_NAME: VOLUME_NAME, - Tags.SIMULATION_PATH: path_manager.get_hdf5_file_save_path(), - Tags.SPACING_MM: SPACING, - Tags.DIM_VOLUME_Z_MM: VOLUME_HEIGHT_IN_MM, - Tags.DIM_VOLUME_X_MM: VOLUME_TRANSDUCER_DIM_IN_MM, - Tags.DIM_VOLUME_Y_MM: VOLUME_PLANAR_DIM_IN_MM, - Tags.VOLUME_CREATOR: Tags.VOLUME_CREATOR_VERSATILE, - Tags.GPU: True, - - # The following parameters set the optical forward model - Tags.WAVELENGTHS: [700] -} -settings = Settings(general_settings) -np.random.seed(RANDOM_SEED) - -settings.set_volume_creation_settings({ - Tags.STRUCTURES: create_point_source(), - Tags.SIMULATE_DEFORMED_LAYERS: True -}) - -settings.set_optical_settings({ - Tags.OPTICAL_MODEL_NUMBER_PHOTONS: 1e7, - Tags.OPTICAL_MODEL_BINARY_PATH: path_manager.get_mcx_binary_path(), - Tags.ILLUMINATION_TYPE: Tags.ILLUMINATION_TYPE_MSOT_ACUITY_ECHO, - Tags.LASER_PULSE_ENERGY_IN_MILLIJOULE: 50, -}) - -settings.set_acoustic_settings({ - Tags.ACOUSTIC_SIMULATION_3D: False, - Tags.ACOUSTIC_MODEL_BINARY_PATH: path_manager.get_matlab_binary_path(), - Tags.KWAVE_PROPERTY_ALPHA_POWER: 0.00, - Tags.KWAVE_PROPERTY_SENSOR_RECORD: "p", - Tags.KWAVE_PROPERTY_PMLInside: False, - Tags.KWAVE_PROPERTY_PMLSize: [31, 32], - Tags.KWAVE_PROPERTY_PMLAlpha: 1.5, - Tags.KWAVE_PROPERTY_PlotPML: False, - Tags.RECORDMOVIE: False, - Tags.MOVIENAME: "visualization_log", - Tags.ACOUSTIC_LOG_SCALE: True -}) - -settings.set_reconstruction_settings({ - Tags.RECONSTRUCTION_PERFORM_BANDPASS_FILTERING: False, - Tags.ACOUSTIC_MODEL_BINARY_PATH: path_manager.get_matlab_binary_path(), - Tags.KWAVE_PROPERTY_ALPHA_POWER: 0.00, - Tags.TUKEY_WINDOW_ALPHA: 0.5, - Tags.BANDPASS_CUTOFF_LOWPASS_IN_HZ: int(8e6), - Tags.BANDPASS_CUTOFF_HIGHPASS_IN_HZ: int(0.1e4), - Tags.RECONSTRUCTION_BMODE_AFTER_RECONSTRUCTION: False, - Tags.RECONSTRUCTION_BMODE_METHOD: Tags.RECONSTRUCTION_BMODE_METHOD_HILBERT_TRANSFORM, - Tags.RECONSTRUCTION_APODIZATION_METHOD: Tags.RECONSTRUCTION_APODIZATION_BOX, - Tags.RECONSTRUCTION_MODE: Tags.RECONSTRUCTION_MODE_PRESSURE, - Tags.KWAVE_PROPERTY_SENSOR_RECORD: "p", - Tags.KWAVE_PROPERTY_PMLInside: False, - Tags.KWAVE_PROPERTY_PMLSize: [31, 32], - Tags.KWAVE_PROPERTY_PMLAlpha: 1.5, - Tags.KWAVE_PROPERTY_PlotPML: False, - Tags.RECORDMOVIE: False, - Tags.MOVIENAME: "visualization_log", - Tags.ACOUSTIC_LOG_SCALE: True, - Tags.DATA_FIELD_SPEED_OF_SOUND: SPEED_OF_SOUND, - Tags.SPACING_MM: SPACING -}) - -SIMUATION_PIPELINE = [ - ModelBasedAdapter(settings), - MCXAdapter(settings), - KWaveAdapter(settings), - FieldOfViewCropping(settings), - DelayAndSumAdapter(settings) -] - - -def simulate_and_evaluate_with_device(_device): - print("Simulating for device:", _device) - simulate(SIMUATION_PIPELINE, settings, _device) - - if Tags.WAVELENGTH in settings: - wavelength = settings[Tags.WAVELENGTH] - else: - wavelength = 700 - - initial_pressure = load_data_field(path_manager.get_hdf5_file_save_path() + "/" + VOLUME_NAME + ".hdf5", - data_field=Tags.DATA_FIELD_INITIAL_PRESSURE, - wavelength=wavelength) - reconstruction = load_data_field(path_manager.get_hdf5_file_save_path() + "/" + VOLUME_NAME + ".hdf5", - data_field=Tags.DATA_FIELD_RECONSTRUCTED_DATA, - wavelength=wavelength) - - p0_idx = np.unravel_index(np.argmax(initial_pressure), np.shape(initial_pressure)) - re_idx = np.unravel_index(np.argmax(reconstruction), np.shape(reconstruction)) - - print("x/y in initial pressure map:", p0_idx) - print("x/y in reconstruction map:", re_idx) - distance = np.sqrt((re_idx[0] - p0_idx[0]) ** 2 + (re_idx[1] - p0_idx[1]) ** 2) - print("Distance:", distance) - - visualise_data(path_to_hdf5_file=path_manager.get_hdf5_file_save_path() + "/" + VOLUME_NAME + ".hdf5", - wavelength=wavelength, - show_time_series_data=True, - show_absorption=False, - show_reconstructed_data=True, - show_xz_only=True, - show_initial_pressure=True, - show_segmentation_map=False, - log_scale=False) - return distance - - -dist = list() - -# fov_e = np.asarray([-VOLUME_TRANSDUCER_DIM_IN_MM/2, VOLUME_TRANSDUCER_DIM_IN_MM/2, 0, 0, 0, VOLUME_HEIGHT_IN_MM]) -# device = PhotoacousticDevice(device_position_mm=np.array([VOLUME_TRANSDUCER_DIM_IN_MM/2, -# VOLUME_PLANAR_DIM_IN_MM/2, -# 0]), -# field_of_view_extent_mm=fov_e) -# device.set_detection_geometry(Random2DArrayDetectionGeometry(device_position_mm=device.device_position_mm, -# number_detector_elements=256, -# seed=1234, field_of_view_extent_mm=fov_e)) -# device.add_illumination_geometry(PencilBeamIlluminationGeometry()) -# dist.append(simulate_and_evaluate_with_device(device)) -# -# device = PhotoacousticDevice(device_position_mm=np.array([VOLUME_TRANSDUCER_DIM_IN_MM/2, -# VOLUME_PLANAR_DIM_IN_MM/2, -# 0]), -# field_of_view_extent_mm=fov_e) -# device.set_detection_geometry(Random3DArrayDetectionGeometry(device_position_mm=device.device_position_mm, -# number_detector_elements=256, -# seed=1234, field_of_view_extent_mm=fov_e)) -# device.add_illumination_geometry(PencilBeamIlluminationGeometry()) -# dist.append(simulate_and_evaluate_with_device(device)) - -dist.append(simulate_and_evaluate_with_device(MSOTAcuityEcho(device_position_mm=np.array([VOLUME_TRANSDUCER_DIM_IN_MM/2, - VOLUME_PLANAR_DIM_IN_MM/2, - 35]), - field_of_view_extent_mm=np.array([-(2 * np.sin(0.34 / 40 * 128) * 40) / 2, - (2 * np.sin(0.34 / - 40 * 128) * 40) / 2, - 0, 0, -25, 25])))) - -dist.append(simulate_and_evaluate_with_device(InVision256TF(device_position_mm=np.array([VOLUME_TRANSDUCER_DIM_IN_MM/2, - VOLUME_PLANAR_DIM_IN_MM/2, - VOLUME_HEIGHT_IN_MM/2])))) -device = PhotoacousticDevice(device_position_mm=np.array([VOLUME_TRANSDUCER_DIM_IN_MM/2, - VOLUME_PLANAR_DIM_IN_MM/2, - 30]), - field_of_view_extent_mm=np.asarray([-VOLUME_TRANSDUCER_DIM_IN_MM/2, - VOLUME_TRANSDUCER_DIM_IN_MM/2, - 0, 0, 0, VOLUME_HEIGHT_IN_MM])) -device.set_detection_geometry(LinearArrayDetectionGeometry(device_position_mm=device.device_position_mm, - pitch_mm=0.2, - number_detector_elements=256)) -device.add_illumination_geometry(PencilBeamIlluminationGeometry()) -dist.append(simulate_and_evaluate_with_device(device)) - -device = PhotoacousticDevice(device_position_mm=np.array([VOLUME_TRANSDUCER_DIM_IN_MM/2, - VOLUME_PLANAR_DIM_IN_MM/2, - 5]), - field_of_view_extent_mm=np.asarray([-VOLUME_TRANSDUCER_DIM_IN_MM / 2, - VOLUME_TRANSDUCER_DIM_IN_MM / 2, - 0, 0, 0, VOLUME_HEIGHT_IN_MM])) - -device.set_detection_geometry(LinearArrayDetectionGeometry(device_position_mm=device.device_position_mm, - pitch_mm=0.2, - number_detector_elements=256)) -device.add_illumination_geometry(PencilBeamIlluminationGeometry()) -dist.append(simulate_and_evaluate_with_device(device)) - -device = PhotoacousticDevice(device_position_mm=np.array([VOLUME_TRANSDUCER_DIM_IN_MM/2, - VOLUME_PLANAR_DIM_IN_MM/2, - 10]), - field_of_view_extent_mm=np.asarray([-VOLUME_TRANSDUCER_DIM_IN_MM / 2, - VOLUME_TRANSDUCER_DIM_IN_MM / 2, - 0, 0, 0, VOLUME_HEIGHT_IN_MM])) -device.set_detection_geometry(LinearArrayDetectionGeometry(device_position_mm=device.device_position_mm, - pitch_mm=0.2, - number_detector_elements=256)) -device.add_illumination_geometry(PencilBeamIlluminationGeometry()) -dist.append(simulate_and_evaluate_with_device(device)) -print("") -print("Results:") -print("______________") -for dis in dist: - print("Distance", dis) + vessel_1_dictionary[Tags.CONSIDER_PARTIAL_VOLUME] = True + vessel_1_dictionary[Tags.STRUCTURE_TYPE] = Tags.CIRCULAR_TUBULAR_STRUCTURE + + tissue_dict = Settings() + tissue_dict[Tags.BACKGROUND] = background_dictionary + tissue_dict["vessel_1"] = vessel_1_dictionary + return tissue_dict + + def setup(self): + + # Note: Please make sure that a valid path_config.env file is located in your home directory, or that you + # point to the correct file in the PathManager(). + self.path_manager = PathManager() + + + # Seed the numpy random configuration prior to creating the global_settings file in + # order to ensure that the same volume + # is generated with the same random seed every time. + + np.random.seed(self.RANDOM_SEED) + self.VOLUME_NAME = "CompletePipelineTestMSOT_"+str(self.RANDOM_SEED) + + general_settings = { + # These parameters set the general properties of the simulated volume + Tags.RANDOM_SEED: self.RANDOM_SEED, + Tags.VOLUME_NAME: self.VOLUME_NAME, + Tags.SIMULATION_PATH: self.path_manager.get_hdf5_file_save_path(), + Tags.SPACING_MM: self.SPACING, + Tags.DIM_VOLUME_Z_MM: self.VOLUME_HEIGHT_IN_MM, + Tags.DIM_VOLUME_X_MM: self.VOLUME_TRANSDUCER_DIM_IN_MM, + Tags.DIM_VOLUME_Y_MM: self.VOLUME_PLANAR_DIM_IN_MM, + Tags.VOLUME_CREATOR: Tags.VOLUME_CREATOR_VERSATILE, + Tags.GPU: True, + + # The following parameters set the optical forward model + Tags.WAVELENGTHS: [700] + } + settings = Settings(general_settings) + np.random.seed(self.RANDOM_SEED) + + settings.set_volume_creation_settings({ + Tags.STRUCTURES: self.create_point_source(), + Tags.SIMULATE_DEFORMED_LAYERS: True + }) + + settings.set_optical_settings({ + Tags.OPTICAL_MODEL_NUMBER_PHOTONS: 1e7, + Tags.OPTICAL_MODEL_BINARY_PATH: self.path_manager.get_mcx_binary_path(), + Tags.ILLUMINATION_TYPE: Tags.ILLUMINATION_TYPE_MSOT_ACUITY_ECHO, + Tags.LASER_PULSE_ENERGY_IN_MILLIJOULE: 50, + }) + + settings.set_acoustic_settings({ + Tags.ACOUSTIC_SIMULATION_3D: False, + Tags.ACOUSTIC_MODEL_BINARY_PATH: self.path_manager.get_matlab_binary_path(), + Tags.KWAVE_PROPERTY_ALPHA_POWER: 0.00, + Tags.KWAVE_PROPERTY_SENSOR_RECORD: "p", + Tags.KWAVE_PROPERTY_PMLInside: False, + Tags.KWAVE_PROPERTY_PMLSize: [31, 32], + Tags.KWAVE_PROPERTY_PMLAlpha: 1.5, + Tags.KWAVE_PROPERTY_PlotPML: False, + Tags.RECORDMOVIE: False, + Tags.MOVIENAME: "visualization_log", + Tags.ACOUSTIC_LOG_SCALE: True + }) + + settings.set_reconstruction_settings({ + Tags.RECONSTRUCTION_PERFORM_BANDPASS_FILTERING: False, + Tags.ACOUSTIC_MODEL_BINARY_PATH: self.path_manager.get_matlab_binary_path(), + Tags.KWAVE_PROPERTY_ALPHA_POWER: 0.00, + Tags.TUKEY_WINDOW_ALPHA: 0.5, + Tags.BANDPASS_CUTOFF_LOWPASS_IN_HZ: int(8e6), + Tags.BANDPASS_CUTOFF_HIGHPASS_IN_HZ: int(0.1e4), + Tags.RECONSTRUCTION_BMODE_AFTER_RECONSTRUCTION: False, + Tags.RECONSTRUCTION_BMODE_METHOD: Tags.RECONSTRUCTION_BMODE_METHOD_HILBERT_TRANSFORM, + Tags.RECONSTRUCTION_APODIZATION_METHOD: Tags.RECONSTRUCTION_APODIZATION_BOX, + Tags.RECONSTRUCTION_MODE: Tags.RECONSTRUCTION_MODE_PRESSURE, + Tags.KWAVE_PROPERTY_SENSOR_RECORD: "p", + Tags.KWAVE_PROPERTY_PMLInside: False, + Tags.KWAVE_PROPERTY_PMLSize: [31, 32], + Tags.KWAVE_PROPERTY_PMLAlpha: 1.5, + Tags.KWAVE_PROPERTY_PlotPML: False, + Tags.RECORDMOVIE: False, + Tags.MOVIENAME: "visualization_log", + Tags.ACOUSTIC_LOG_SCALE: True, + Tags.DATA_FIELD_SPEED_OF_SOUND: self.SPEED_OF_SOUND, + Tags.SPACING_MM: self.SPACING + }) + + self.settings = settings + + def simulate_and_evaluate_with_device(self, _device): + SIMULATION_PIPELINE = [ + ModelBasedAdapter(self.settings), + MCXAdapter(self.settings), + KWaveAdapter(self.settings), + FieldOfViewCropping(self.settings), + DelayAndSumAdapter(self.settings) + ] + + print("Simulating for device:", _device) + simulate(SIMULATION_PIPELINE, self.settings, _device) + + if Tags.WAVELENGTH in self.settings: + wavelength = self.settings[Tags.WAVELENGTH] + else: + wavelength = 700 + + initial_pressure = load_data_field(self.path_manager.get_hdf5_file_save_path() + "/" + self.VOLUME_NAME + ".hdf5", + data_field=Tags.DATA_FIELD_INITIAL_PRESSURE, + wavelength=wavelength) + reconstruction = load_data_field(self.path_manager.get_hdf5_file_save_path() + "/" + self.VOLUME_NAME + ".hdf5", + data_field=Tags.DATA_FIELD_RECONSTRUCTED_DATA, + wavelength=wavelength) + + p0_idx = np.unravel_index(np.argmax(initial_pressure), np.shape(initial_pressure)) + re_idx = np.unravel_index(np.argmax(reconstruction), np.shape(reconstruction)) + + print("x/y in initial pressure map:", p0_idx) + print("x/y in reconstruction map:", re_idx) + distance = np.sqrt((re_idx[0] - p0_idx[0]) ** 2 + (re_idx[1] - p0_idx[1]) ** 2) + print("Distance:", distance) + + if self.save_path is not None: + save_path = self.save_path + f"PointSourceReconstruction_{self.figure_number}_{distance=}.png" + else: + save_path = self.save_path + + visualise_data(path_to_hdf5_file=self.path_manager.get_hdf5_file_save_path() + "/" + self.VOLUME_NAME + ".hdf5", + wavelength=wavelength, + show_time_series_data=True, + show_absorption=False, + show_reconstructed_data=True, + show_xz_only=True, + show_initial_pressure=True, + show_segmentation_map=False, + log_scale=False, + save_path=save_path) + self.figure_number += 1 + return distance + + + def test_reconstruction_of_simulation(self): + + + dist = list() + + # fov_e = np.asarray([-self.VOLUME_TRANSDUCER_DIM_IN_MM/2, self.VOLUME_TRANSDUCER_DIM_IN_MM/2, 0, 0, 0, self.VOLUME_HEIGHT_IN_MM]) + # device = PhotoacousticDevice(device_position_mm=np.array([self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + # self.VOLUME_PLANAR_DIM_IN_MM/2, + # 0]), + # field_of_view_extent_mm=fov_e) + # device.set_detection_geometry(Random2DArrayDetectionGeometry(device_position_mm=device.device_position_mm, + # number_detector_elements=256, + # seed=1234, field_of_view_extent_mm=fov_e)) + # device.add_illumination_geometry(PencilBeamIlluminationGeometry()) + # dist.append(self.simulate_and_evaluate_with_device(device)) + # + # device = PhotoacousticDevice(device_position_mm=np.array([self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + # self.VOLUME_PLANAR_DIM_IN_MM/2, + # 0]), + # field_of_view_extent_mm=fov_e) + # device.set_detection_geometry(Random3DArrayDetectionGeometry(device_position_mm=device.device_position_mm, + # number_detector_elements=256, + # seed=1234, field_of_view_extent_mm=fov_e)) + # device.add_illumination_geometry(PencilBeamIlluminationGeometry()) + # dist.append(self.simulate_and_evaluate_with_device(device)) + + dist.append(self.simulate_and_evaluate_with_device(MSOTAcuityEcho(device_position_mm=np.array([self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + self.VOLUME_PLANAR_DIM_IN_MM/2, + 35]), + field_of_view_extent_mm=np.array([-(2 * np.sin(0.34 / 40 * 128) * 40) / 2, + (2 * np.sin(0.34 / + 40 * 128) * 40) / 2, + 0, 0, -25, 25])))) + + dist.append(self.simulate_and_evaluate_with_device(InVision256TF(device_position_mm=np.array([self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + self.VOLUME_PLANAR_DIM_IN_MM/2, + self.VOLUME_HEIGHT_IN_MM/2])))) + device = PhotoacousticDevice(device_position_mm=np.array([self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + self.VOLUME_PLANAR_DIM_IN_MM/2, + 30]), + field_of_view_extent_mm=np.asarray([-self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + 0, 0, 0, self.VOLUME_HEIGHT_IN_MM])) + device.set_detection_geometry(LinearArrayDetectionGeometry(device_position_mm=device.device_position_mm, + pitch_mm=0.2, + number_detector_elements=256)) + device.add_illumination_geometry(PencilBeamIlluminationGeometry(device_position_mm=device.device_position_mm)) + dist.append(self.simulate_and_evaluate_with_device(device)) + + device = PhotoacousticDevice(device_position_mm=np.array([self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + self.VOLUME_PLANAR_DIM_IN_MM/2, + 5]), + field_of_view_extent_mm=np.asarray([-self.VOLUME_TRANSDUCER_DIM_IN_MM / 2, + self.VOLUME_TRANSDUCER_DIM_IN_MM / 2, + 0, 0, 0, self.VOLUME_HEIGHT_IN_MM])) + + device.set_detection_geometry(LinearArrayDetectionGeometry(device_position_mm=device.device_position_mm, + pitch_mm=0.2, + number_detector_elements=256)) + device.add_illumination_geometry(PencilBeamIlluminationGeometry()) + dist.append(self.simulate_and_evaluate_with_device(device)) + + device = PhotoacousticDevice(device_position_mm=np.array([self.VOLUME_TRANSDUCER_DIM_IN_MM/2, + self.VOLUME_PLANAR_DIM_IN_MM/2, + 10]), + field_of_view_extent_mm=np.asarray([-self.VOLUME_TRANSDUCER_DIM_IN_MM / 2, + self.VOLUME_TRANSDUCER_DIM_IN_MM / 2, + 0, 0, 0, self.VOLUME_HEIGHT_IN_MM])) + device.set_detection_geometry(LinearArrayDetectionGeometry(device_position_mm=device.device_position_mm, + pitch_mm=0.2, + number_detector_elements=256)) + device.add_illumination_geometry(PencilBeamIlluminationGeometry()) + dist.append(self.simulate_and_evaluate_with_device(device)) + print("") + print("Results:") + print("______________") + for dis in dist: + print("Distance", dis) + + def run_test(self, show_figure_on_screen=True, save_path=None): + + if save_path is None or not os.path.isdir(save_path): + save_path = "figures/" + if not os.path.exists(save_path): + os.mkdir(save_path) + + if show_figure_on_screen: + save_path = None + + self.save_path = save_path + self.figure_number = 0 + + self.setup() + self.perform_test() + self.tear_down() + +if __name__ == '__main__': + test = PointSourceReconstruction() + test.run_test(show_figure_on_screen=False) From 8d5bb69d2099e8c033f6ffd2ac8926d7321606d0 Mon Sep 17 00:00:00 2001 From: cbender98 Date: Fri, 23 Aug 2024 13:24:06 +0200 Subject: [PATCH 29/35] - do not eglect PointSourceReconstruction.py script anymore in the manual test overview - removed distance in save_path (figure names of reference and generated need to be the same) --- simpa_tests/manual_tests/generate_overview.py | 4 ++-- .../image_reconstruction/PointSourceReconstruction.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/simpa_tests/manual_tests/generate_overview.py b/simpa_tests/manual_tests/generate_overview.py index 128b10f2..c1a2f3aa 100644 --- a/simpa_tests/manual_tests/generate_overview.py +++ b/simpa_tests/manual_tests/generate_overview.py @@ -43,8 +43,8 @@ def __init__(self, verbose: bool = False, save_path: str = None): self.mdFile = MdUtils(file_name=self.md_name, title='Overview of Manual Test Results') self.set_style() - # Note: Open issue in PointSourceReconstruction.py file (make it consistent with the other manual tests) - self.scripts_to_neglect = ["PointSourceReconstruction.py"] + # If you manually want to neglect a specific manual test enter the python script name here + self.scripts_to_neglect = [] def download_reference_images(self): """ diff --git a/simpa_tests/manual_tests/image_reconstruction/PointSourceReconstruction.py b/simpa_tests/manual_tests/image_reconstruction/PointSourceReconstruction.py index dc52e03f..dc17dea2 100644 --- a/simpa_tests/manual_tests/image_reconstruction/PointSourceReconstruction.py +++ b/simpa_tests/manual_tests/image_reconstruction/PointSourceReconstruction.py @@ -205,7 +205,7 @@ def simulate_and_evaluate_with_device(self, _device): print("Distance:", distance) if self.save_path is not None: - save_path = self.save_path + f"PointSourceReconstruction_{self.figure_number}_{distance=}.png" + save_path = self.save_path + f"PointSourceReconstruction_{self.figure_number}.png" else: save_path = self.save_path @@ -321,4 +321,4 @@ def run_test(self, show_figure_on_screen=True, save_path=None): if __name__ == '__main__': test = PointSourceReconstruction() - test.run_test(show_figure_on_screen=False) + test.run_test(show_figure_on_screen=True) From 18e53752f58c6b6d812ccdbd17289257377467bc Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 13:38:46 +0200 Subject: [PATCH 30/35] Added some information about reproducibility in the README --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e407c7a9..74873400 100755 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ The paper that introduces SIMPA including visualisations and explanations can be * [Getting started](#getting-started) * [Simulation examples](#simulation-examples) * [Documentation](#documentation) +* [Reproducibility](#reproducibility) * [Contributing](#how-to-contribute) * [Performance profiling](#performance-profiling) * [Troubleshooting](#troubleshooting) @@ -56,7 +57,7 @@ The recommended way to install SIMPA is a manual installation from the GitHub re Now open a python instance in the 'simpa' folder that you have just downloaded. Make sure that you have your preferred virtual environment activated (we also recommend python 3.10) -1. `pip install .` +1. `pip install .` or `pip install -e .` for an editable mode. 2. Test if the installation worked by using `python` followed by `import simpa` then `exit()` If no error messages arise, you are now setup to use SIMPA in your project. @@ -152,6 +153,12 @@ device = sp.CustomDevice() sp.simulate(simulation_pipeline, settings, device) ``` +# Reproducibility + +For reproducibility, we provide the exact version number including the commit hash in the simpa output file. +This can be accessed via `simpa.__version__` or by checking the tag `Tags.SIMPA_VERSION` in the output file. +This way, you can always trace back the exact version of the code that was used to generate the simulation results. + # Documentation The updated version of the SIMPA documentation can be found at [https://simpa.readthedocs.io/en/develop](https://simpa.readthedocs.io/en/develop). From 487ba6744e38ca139d11851e9a29b221258f6c99 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 13:41:44 +0200 Subject: [PATCH 31/35] Reformatting and ran pre-commit hooks --- README.md | 6 +++--- docs/source/introduction.md | 16 +++++++++++----- .../monospectral/field_of_view_cropping.py | 7 ++++--- .../reconstruction_utils.py | 3 ++- .../time_reversal_adapter.py | 9 +++++---- .../create_a_custom_digital_device_twin.py | 3 ++- simpa_examples/segmentation_loader.py | 2 +- .../device_tests/test_field_of_view.py | 3 ++- .../volume_creation/SegmentationLoader.py | 2 +- 9 files changed, 31 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 74873400..0693fe7e 100755 --- a/README.md +++ b/README.md @@ -142,9 +142,9 @@ settings.set_reconstruction_settings(reconstruction_settings) # Set the simulation pipeline simulation_pipeline = [sp.VolumeCreationModule(settings), - sp.OpticalModule(settings), - sp.AcousticModule(settings), - sp.ReconstructionModule(settings)] + sp.OpticalModule(settings), + sp.AcousticModule(settings), + sp.ReconstructionModule(settings)] # Choose a PA device with device position in the volume device = sp.CustomDevice() diff --git a/docs/source/introduction.md b/docs/source/introduction.md index e0aa7018..51999ae9 100644 --- a/docs/source/introduction.md +++ b/docs/source/introduction.md @@ -20,7 +20,7 @@ The recommended way to install SIMPA is a manual installation from the GitHub re Now open a python instance in the 'simpa' folder that you have just downloaded. Make sure that you have your preferred virtual environment activated (we also recommend python 3.10) -1. `pip install .` +1. `pip install .` or `pip install -e .` for an editable mode. 2. Test if the installation worked by using `python` followed by `import simpa` then `exit()` If no error messages arise, you are now setup to use SIMPA in your project. @@ -104,10 +104,10 @@ settings.set_acoustic_settings(acoustic_settings) settings.set_reconstruction_settings(reconstruction_settings) # Set the simulation pipeline -simulation_pipeline = [sp.VolumeCreatorModule(settings), - sp.OpticalForwardModule(settings), - sp.AcousticForwardModule(settings), - sp.ReconstructionModule(settings)] +simulation_pipeline = [sp.VolumeCreationModule(settings), + sp.OpticalModule(settings), + sp.AcousticModule(settings), + sp.ReconstructionModule(settings)] # Choose a PA device with device position in the volume device = sp.CustomDevice() @@ -116,3 +116,9 @@ device = sp.CustomDevice() sp.simulate(simulation_pipeline, settings, device) ``` +# Reproducibility + +For reproducibility, we provide the exact version number including the commit hash in the simpa output file. +This can be accessed via `simpa.__version__` or by checking the tag `Tags.SIMPA_VERSION` in the output file. +This way, you can always trace back the exact version of the code that was used to generate the simulation results. + diff --git a/simpa/core/processing_components/monospectral/field_of_view_cropping.py b/simpa/core/processing_components/monospectral/field_of_view_cropping.py index 9ffb1321..004a0eba 100644 --- a/simpa/core/processing_components/monospectral/field_of_view_cropping.py +++ b/simpa/core/processing_components/monospectral/field_of_view_cropping.py @@ -46,9 +46,10 @@ def run(self, device: DigitalDeviceTwinBase): else: field_of_view_mm = device.get_field_of_view_mm() self.logger.debug(f"FOV (mm): {field_of_view_mm}") - _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, self.global_settings[Tags.SPACING_MM], self.logger) - field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering - field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int + _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions( + field_of_view_mm, self.global_settings[Tags.SPACING_MM], self.logger) + field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering + field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 diff --git a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py index 111b4a43..1cdb1ae4 100644 --- a/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py +++ b/simpa/core/simulation_modules/reconstruction_module/reconstruction_utils.py @@ -445,6 +445,7 @@ def preparing_reconstruction_and_obtaining_reconstruction_settings( return (time_series_sensor_data, sensor_positions, speed_of_sound_in_m_per_s, spacing_in_mm, time_spacing_in_ms, torch_device) + def compute_image_dimensions(field_of_view_in_mm: np.ndarray, spacing_in_mm: float, logger: Logger) -> Tuple[int, int, int, np.float64, np.float64, np.float64, np.float64, np.float64, np.float64]: @@ -485,7 +486,7 @@ def compute_for_one_dimension(start_in_mm: float, end_in_mm: float) -> Tuple[int end_temp = end_in_mm / spacing_in_mm dim_temp = np.abs(end_temp - start_temp) dim = round_x5_away_from_zero(dim_temp) - diff = dim_temp - dim # the sign is important here + diff = dim_temp - dim # the sign is important here start = start_temp - np.sign(start_temp) * diff/2 end = end_temp - np.sign(end_temp) * diff/2 return dim, start, end diff --git a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py index c500a368..5c61ff44 100644 --- a/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py +++ b/simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py @@ -183,10 +183,11 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry): reconstructed_data = reconstructed_data.T field_of_view_mm = detection_geometry.get_field_of_view_mm() - _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(field_of_view_mm, spacing_in_mm, self.logger) - field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering - field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int - + _, _, _, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions( + field_of_view_mm, spacing_in_mm, self.logger) + field_of_view_voxels = [xdim_start, xdim_end, zdim_start, zdim_end, ydim_start, ydim_end] # change ordering + field_of_view_voxels = [int(dim) for dim in field_of_view_voxels] # cast to int + self.logger.debug(f"FOV (voxels): {field_of_view_voxels}") # In case it should be cropped from A to A, then crop from A to A+1 x_offset_correct = 1 if (field_of_view_voxels[1] - field_of_view_voxels[0]) < 1 else 0 diff --git a/simpa_examples/create_a_custom_digital_device_twin.py b/simpa_examples/create_a_custom_digital_device_twin.py index 73dc7e0c..a1adac68 100644 --- a/simpa_examples/create_a_custom_digital_device_twin.py +++ b/simpa_examples/create_a_custom_digital_device_twin.py @@ -42,7 +42,8 @@ def __init__(self, device_position_mm): positions = device.get_detection_geometry().get_detector_element_positions_accounting_for_device_position_mm() detector_elements = device.get_detection_geometry().get_detector_element_orientations() positions = sp.round_x5_away_from_zero(positions/settings[Tags.SPACING_MM]) - xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions(device.get_detection_geometry().field_of_view_extent_mm, settings[Tags.SPACING_MM], Logger()) + xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions( + device.get_detection_geometry().field_of_view_extent_mm, settings[Tags.SPACING_MM], Logger()) import matplotlib.pyplot as plt from matplotlib.patches import Rectangle diff --git a/simpa_examples/segmentation_loader.py b/simpa_examples/segmentation_loader.py index bc0ab2d8..a4d9cd39 100644 --- a/simpa_examples/segmentation_loader.py +++ b/simpa_examples/segmentation_loader.py @@ -41,7 +41,7 @@ def run_segmentation_loader(spacing: float | int = 1.0, input_spacing: float | i segmentation_volume_tiled = np.tile(label_mask, (1, 128, 1)) segmentation_volume_mask = sp.round_x5_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/spacing, - order=0)).astype(int) + order=0)).astype(int) def segmentation_class_mapping(): ret_dict = dict() diff --git a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py index 07ab6593..bbdbc99a 100644 --- a/simpa_tests/automatic_tests/device_tests/test_field_of_view.py +++ b/simpa_tests/automatic_tests/device_tests/test_field_of_view.py @@ -23,7 +23,7 @@ def _test(self, field_of_view_extent_mm: list, spacing_in_mm: float, detection_g detection_geometry.field_of_view_extent_mm = field_of_view_extent_mm xdim, zdim, ydim, xdim_start, xdim_end, ydim_start, ydim_end, zdim_start, zdim_end = compute_image_dimensions( detection_geometry.field_of_view_extent_mm, spacing_in_mm, self.logger) - + assert type(xdim) == int and type(ydim) == int and type(zdim) == int, "dimensions should be integers" assert xdim >= 1 and ydim >= 1 and zdim >= 1, "dimensions should be positive" @@ -128,6 +128,7 @@ def test_with_changing_spacing(self): self.assertAlmostEqual(zdim_start, 0) self.assertAlmostEqual(zdim_end, 0) + if __name__ == '__main__': test = TestFieldOfView() test.setUp() diff --git a/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py b/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py index add659eb..559b9436 100644 --- a/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py +++ b/simpa_tests/manual_tests/volume_creation/SegmentationLoader.py @@ -25,7 +25,7 @@ def setup(self): input_spacing = 0.2 segmentation_volume_tiled = np.tile(label_mask, (1, 128, 1)) segmentation_volume_mask = sp.round_x5_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/target_spacing, - order=0)).astype(int) + order=0)).astype(int) def segmentation_class_mapping(): ret_dict = dict() From 65a0615884c7d1491f2b3817ddd2426f1af9713e Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 13:52:39 +0200 Subject: [PATCH 32/35] New directory for reference testing images Co-authored-by: Christoph Bender --- simpa_tests/manual_tests/generate_overview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpa_tests/manual_tests/generate_overview.py b/simpa_tests/manual_tests/generate_overview.py index c1a2f3aa..e99cf978 100644 --- a/simpa_tests/manual_tests/generate_overview.py +++ b/simpa_tests/manual_tests/generate_overview.py @@ -57,7 +57,7 @@ def download_reference_images(self): # Remove the directory shutil.rmtree(ref_imgs_path) # nextcloud url with the reference images - self.nextcloud_url = "https://hub.dkfz.de/s/Xb96SFXbmiE5Fk8" # shared "reference_figures" folder on nextcloud + self.nextcloud_url = "https://hub.dkfz.de/s/nsXKMGAaN6tPPsq" # shared "reference_figures" folder on nextcloud # Specify the local directory to save the files zip_filepath = os.path.join(self.current_dir, "downloaded.zip") # Construct the download URL based on the public share link From c0531a18b4813d0b5afe2fe60c903ceed87e18be Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 14:08:21 +0200 Subject: [PATCH 33/35] Added release drafter --- .github/release-drafter.yml | 32 +++++++++++++++++++++ .github/workflows/release-drafter.yml | 41 +++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/release-drafter.yml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..bea11f1e --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,32 @@ +name-template: 'v$RESOLVED_VERSION ๐ŸŒˆ' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: '๐Ÿš€ Features' + labels: + - 'feature' + - 'enhancement' + - 'documentation' + - title: '๐Ÿ› Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '๐Ÿงฐ Maintenance' + label: 'chore' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +template: | + ## Changes + + $CHANGES \ No newline at end of file diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..93fc6347 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,41 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - main + # pull_request event is required only for autolabeler +# pull_request: + # Only following types are handled by the action, but one can default to all as well +# types: [ opened, reopened, synchronize ] + # pull_request_target event is required for autolabeler to support PRs from forks + # pull_request_target: + # types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: read + runs-on: ubuntu-latest + steps: + # (Optional) GitHub Enterprise requires GHE_HOST variable set + #- name: Set GHE_HOST + # run: | + # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV + + # Drafts your next Release notes as Pull Requests are merged into "main" + - uses: release-drafter/release-drafter@v6 + # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml + # with: + # config-name: my-config.yml + # disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 06398877671aa1bb2db5eebbc226a67e53a5ab84 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 14:53:40 +0200 Subject: [PATCH 34/35] Changed LC_NUMERIC to C/POSIX/en_US.utf8 to acccount for different languages in benchmarking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan-Hinrich Nรถlke Co-authored-by: Christoph Bender --- simpa_examples/benchmarking/get_final_table.py | 2 +- simpa_examples/benchmarking/run_benchmarking.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/simpa_examples/benchmarking/get_final_table.py b/simpa_examples/benchmarking/get_final_table.py index 680c2776..ab90576f 100644 --- a/simpa_examples/benchmarking/get_final_table.py +++ b/simpa_examples/benchmarking/get_final_table.py @@ -57,7 +57,7 @@ def get_final_table(savefolder: str = None): mean_df.to_csv(str(df_file).replace('.csv', '_mean.csv'), index=False) # save to markdown for nice visualization - mean_df.to_markdown(str(df_file).replace('data_frame.csv', '_mean.md'), index=False) + mean_df.to_markdown(str(df_file).replace('data_frame.csv', 'mean.md'), index=False) if __name__ == "__main__": diff --git a/simpa_examples/benchmarking/run_benchmarking.sh b/simpa_examples/benchmarking/run_benchmarking.sh index 0659d932..7776ffcb 100644 --- a/simpa_examples/benchmarking/run_benchmarking.sh +++ b/simpa_examples/benchmarking/run_benchmarking.sh @@ -86,7 +86,7 @@ done for ((i=0; i < number; i++)) do - for spacing in $(seq $start $step $stop) + for spacing in $(LC_NUMERIC=C seq $start $step $stop) do for profile in "${profiles[@]}" do From a3e52bf3069594981cf1855d2417a1b1e5f27112 Mon Sep 17 00:00:00 2001 From: Kris Dreher Date: Fri, 23 Aug 2024 15:30:11 +0200 Subject: [PATCH 35/35] Renamed ModelBasedVolumeCreator --- .../minimal_optical_simulation_heterogeneous_tissue.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simpa_examples/minimal_optical_simulation_heterogeneous_tissue.py b/simpa_examples/minimal_optical_simulation_heterogeneous_tissue.py index 874ee093..12e240c2 100644 --- a/simpa_examples/minimal_optical_simulation_heterogeneous_tissue.py +++ b/simpa_examples/minimal_optical_simulation_heterogeneous_tissue.py @@ -133,14 +133,14 @@ def create_example_tissue(settings): if not SAVE_REFLECTANCE and not SAVE_PHOTON_DIRECTION: pipeline = [ - sp.ModelBasedVolumeCreationAdapter(settings), + sp.ModelBasedAdapter(settings), sp.MCXAdapter(settings), sp.GaussianNoise(settings, "noise_model_1") ] else: pipeline = [ - sp.ModelBasedVolumeCreationAdapter(settings), - sp.MCXAdapterReflectance(settings), + sp.ModelBasedAdapter(settings), + sp.MCXReflectanceAdapter(settings), ]