Skip to content

Commit

Permalink
Merge branch 'develop' into T105_OverviewManualTest
Browse files Browse the repository at this point in the history
  • Loading branch information
kdreher authored Jul 29, 2024
2 parents 84cfb25 + 0374433 commit c1d67ea
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 5 deletions.
14 changes: 13 additions & 1 deletion simpa/core/simulation_modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from abc import abstractmethod

from simpa.core import PipelineModule
from simpa.utils import Settings
from simpa.utils import Settings, Tags
from typing import List


class SimulationModule(PipelineModule):
Expand All @@ -30,3 +31,14 @@ def load_component_settings(self) -> Settings:
:return: Loads component settings corresponding to this simulation component
"""
pass

def get_additional_flags(self) -> List[str]:
"""Reads the list of additional flags from the corresponding component settings Tags.ADDITIONAL_FLAGS
:return: List[str]: list of additional flags
"""
cmd = []
if Tags.ADDITIONAL_FLAGS in self.component_settings:
for flag in self.component_settings[Tags.ADDITIONAL_FLAGS]:
cmd.append(str(flag))
return cmd
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import gc
import os
import subprocess
from typing import List

import numpy as np
import scipy.io as sio
Expand Down Expand Up @@ -238,7 +239,7 @@ def k_wave_acoustic_forward_model(self, detection_geometry: DetectionGeometryBas
simulation_script_path = "simulate_2D"

matlab_binary_path = self.component_settings[Tags.ACOUSTIC_MODEL_BINARY_PATH]
cmd = generate_matlab_cmd(matlab_binary_path, simulation_script_path, optical_path)
cmd = generate_matlab_cmd(matlab_binary_path, simulation_script_path, optical_path, self.get_additional_flags())

cur_dir = os.getcwd()
self.logger.info(cmd)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def forward_model(self,
self.generate_mcx_json_input(settings_dict=settings_dict)
# run the simulation
cmd = self.get_command()
self.logger.info(cmd)
self.run_mcx(cmd)

# Read output
Expand Down Expand Up @@ -184,6 +185,7 @@ def get_command(self) -> List:
cmd.append("1")
cmd.append("-F")
cmd.append("jnii")
cmd += self.get_additional_flags()
return cmd

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def forward_model(self,
self.generate_mcx_json_input(settings_dict=settings_dict)
# run the simulation
cmd = self.get_command()
self.logger.info(cmd)
self.run_mcx(cmd)

# Read output
Expand Down Expand Up @@ -117,6 +118,7 @@ def get_command(self) -> List:
if Tags.COMPUTE_DIFFUSE_REFLECTANCE in self.component_settings and \
self.component_settings[Tags.COMPUTE_DIFFUSE_REFLECTANCE]:
cmd.append("--saveref") # save diffuse reflectance at 0 filled voxels outside of domain
cmd += self.get_additional_flags()
return cmd

def read_mcx_output(self, **kwargs) -> Dict:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ def reconstruction_algorithm(self, time_series_sensor_data, detection_geometry):
axes = (0, 1)

matlab_binary_path = self.component_settings[Tags.ACOUSTIC_MODEL_BINARY_PATH]
cmd = generate_matlab_cmd(matlab_binary_path, time_reversal_script, acoustic_path)

cmd = generate_matlab_cmd(matlab_binary_path, time_reversal_script, acoustic_path, self.get_additional_flags())
cur_dir = os.getcwd()
os.chdir(self.global_settings[Tags.SIMULATION_PATH])
self.logger.info(cmd)
Expand Down
17 changes: 16 additions & 1 deletion simpa/utils/matlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,21 @@
from typing import List


def generate_matlab_cmd(matlab_binary_path: str, simulation_script_path: str, data_path: str) -> List[str]:
def generate_matlab_cmd(matlab_binary_path: str, simulation_script_path: str, data_path: str, additional_flags: List[str] = []) -> List[str]:
"""Generates the MATLAB execution command from the given paths
:param matlab_binary_path: path to the MATLAB binary file as defined by PathManager
:type matlab_binary_path: str
:param simulation_script_path: path to the MATLAB script that should be run (either simulate_2D.m or simulate_3D.m)
:type simulation_script_path: str
:param data_path: path to the .mat file used for simulating
:type data_path: str
:param additional_flags: list of optional additional flags for MATLAB
:type additional_flags: List[str]
:return: list of command parts
:rtype: List[str]
"""

# get path of calling script to add to matlab path
base_script_path = os.path.dirname(os.path.abspath(inspect.stack()[1].filename))
# ensure data path is an absolute path
Expand All @@ -19,6 +33,7 @@ def generate_matlab_cmd(matlab_binary_path: str, simulation_script_path: str, da
cmd.append("-nosplash")
cmd.append("-automation")
cmd.append("-wait")
cmd += additional_flags
cmd.append("-r")
cmd.append(f"addpath('{base_script_path}');{simulation_script_path}('{data_path}');exit;")
return cmd
9 changes: 9 additions & 0 deletions simpa/utils/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: MIT

from numbers import Number
from typing import Iterable

import numpy as np

Expand Down Expand Up @@ -1496,3 +1497,11 @@ class Tags:
"""
Identifier for the environment varibale that defines the path the the matlab executable.
"""

ADDITIONAL_FLAGS = ("additional_flags", Iterable)
"""
Defines a sequence of extra flags to be parsed to executables for simulation modules.
Caution: The user is responsible for checking if these flags exist and don't break the predefined flags' behaviour.
It is assumed that if flags are specified multiple times the flag provided last is considered.
This can for example be used to override predefined flags.
"""
1 change: 1 addition & 0 deletions simpa_examples/optical_and_acoustic_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def create_example_tissue():
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({
Expand Down
53 changes: 53 additions & 0 deletions simpa_tests/automatic_tests/test_additional_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import unittest
import numpy as np

from simpa import MCXAdapterReflectance, MCXAdapter, KWaveAdapter, TimeReversalAdapter, Tags, Settings
from simpa.utils.matlab import generate_matlab_cmd

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 = MCXAdapterReflectance(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")

def test_get_cmd_mcx_adapter(self):
self.settings.set_optical_settings({
Tags.OPTICAL_MODEL_BINARY_PATH: '.',
Tags.ADDITIONAL_FLAGS: self.additional_flags
})
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")

def test_get_cmd_kwave_adapter(self):
self.settings.set_acoustic_settings({
Tags.ADDITIONAL_FLAGS: self.additional_flags
})
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")

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())
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")



if __name__ == '__main__':
unittest.main()
146 changes: 146 additions & 0 deletions simpa_tests/manual_tests/executables/MATLABAdditionalFlags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# SPDX-FileCopyrightText: 2021 Division of Intelligent Medical Systems, DKFZ
# SPDX-FileCopyrightText: 2021 Janek Groehl
# SPDX-License-Identifier: MIT

import os
import numpy as np
from simpa import MCXAdapter, ModelBasedVolumeCreationAdapter, 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


class MATLABAdditionalFlags(ManualIntegrationTestClass):
"""
Tests if using Tags.ADDITIONAL_FLAGS to set additional flags for MATLAB works in the KWaveAdapter.
"""

def create_example_tissue(self):
"""
Creates a very simple example tissue with only background tissue.
"""

background_dictionary = Settings()
background_dictionary[Tags.MOLECULE_COMPOSITION] = TISSUE_LIBRARY.constant(0.1, 100, 0.9)
background_dictionary[Tags.STRUCTURE_TYPE] = Tags.BACKGROUND
tissue_dict = Settings()
tissue_dict[Tags.BACKGROUND] = background_dictionary

return tissue_dict

def setup(self):
"""
Creates basic simulation settings and a simulation device.
"""

path_manager = PathManager()

self.settings = Settings({
Tags.WAVELENGTHS: [800],
Tags.WAVELENGTH: 800,
Tags.VOLUME_NAME: "AdditionalFlagsTest",
Tags.SIMULATION_PATH: path_manager.get_hdf5_file_save_path(),
Tags.SPACING_MM: 1,
Tags.DIM_VOLUME_X_MM: 100,
Tags.DIM_VOLUME_Y_MM: 100,
Tags.DIM_VOLUME_Z_MM: 100,
Tags.RANDOM_SEED: 4711,
Tags.GPU: True
})

self.settings.set_volume_creation_settings({
Tags.SIMULATE_DEFORMED_LAYERS: True,
Tags.STRUCTURES: self.create_example_tissue()
})
self.settings.set_optical_settings({
Tags.OPTICAL_MODEL_NUMBER_PHOTONS: 1e7,
Tags.OPTICAL_MODEL_BINARY_PATH: path_manager.get_mcx_binary_path(),
Tags.OPTICAL_MODEL: Tags.OPTICAL_MODEL_MCX,
Tags.ILLUMINATION_TYPE: Tags.ILLUMINATION_TYPE_PENCIL,
Tags.LASER_PULSE_ENERGY_IN_MILLIJOULE: 50,
Tags.MCX_ASSUMED_ANISOTROPY: 0.9
})
self.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
})

self.device = PhotoacousticDevice(device_position_mm=np.asarray([self.settings[Tags.DIM_VOLUME_X_MM] / 2 - 0.5,
self.settings[Tags.DIM_VOLUME_Y_MM] / 2 - 0.5,
0]))
self.device.add_illumination_geometry(PencilBeamIlluminationGeometry())
self.device.set_detection_geometry(LinearArrayDetectionGeometry(device_position_mm=self.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])))

output_name = f'{os.path.join(self.settings[Tags.SIMULATION_PATH], self.settings[Tags.VOLUME_NAME])}'
self.output_file_name = f'{output_name}.log'

def run_simulation(self):
# run pipeline including volume creation and optical mcx simulation and acoustic matlab kwave simulation
pipeline = [
ModelBasedVolumeCreationAdapter(self.settings),
MCXAdapter(self.settings),
KWaveAdapter(self.settings)
]
simulate(pipeline, self.settings, self.device)

def test_execution_of_additional_flag(self):
"""Tests if log file is created by setting additional parameters
:raises FileNotFoundError: if log file does not exist at expected location
"""

# perform cleaning before test
if os.path.exists(self.output_file_name):
os.remove(self.output_file_name)

# run simulation
self.settings.get_acoustic_settings()[Tags.ADDITIONAL_FLAGS] = ['-logfile', self.output_file_name]
self.run_simulation()

# checking if file exists afterwards
if not os.path.exists(self.output_file_name):
raise FileNotFoundError(f"Log file wasn't created at expected path {self.output_file_name}")

def test_if_last_flag_is_used(self):
"""Tests if log file is created with correct last given name by setting multiple additional parameters
:raises FileNotFoundError: if correct log file does not exist at expected location
"""

# perform cleaning before test
if os.path.exists(self.output_file_name):
os.remove(self.output_file_name)

# run simulation
self.settings.get_acoustic_settings()[Tags.ADDITIONAL_FLAGS] = ['-logfile', 'temp_name', '-logfile', self.output_file_name]
self.run_simulation()

# checking if file exists afterwards
if not os.path.exists(self.output_file_name):
raise FileNotFoundError(f"Log file wasn't created with correct last given name at expected path {self.output_file_name}")

def perform_test(self):
"""
Calls all individual tests of this class
"""
self.test_execution_of_additional_flag()
self.test_if_last_flag_is_used()

def visualise_result(self, show_figure_on_screen=True, save_path=None):
pass # no figures are created that could be visualized

if __name__ == '__main__':
test = MATLABAdditionalFlags()
test.run_test(show_figure_on_screen=False)
Loading

0 comments on commit c1d67ea

Please sign in to comment.