Skip to content

Commit

Permalink
Merge branch 'refs/heads/develop' into T154_heterogeneous_tissue
Browse files Browse the repository at this point in the history
# Conflicts:
#	simpa/utils/tags.py
  • Loading branch information
frisograce committed Jul 29, 2024
2 parents 723faec + 0374433 commit 0f53cf2
Show file tree
Hide file tree
Showing 45 changed files with 2,135 additions and 1,083 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/automatic_testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest, windows-latest]

steps:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ simpa_tests/figures/*
simpa_tests/**/figures
simpa_tests/**/figures/*

simpa_examples/benchmarking/*.csv
simpa_examples/benchmarking/*.txt

path_config.env

*.orig
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ repos:
types: [python]
args:
- --license-filepath
- pre_commit_configs/license_header.txt # defaults to: LICENSE.txt
- .pre_commit_configs/license_header.txt # defaults to: LICENSE.txt

- repo: https://github.com/jorisroovers/gitlint # Uses MIT License (MIT compatible)
rev: v0.19.1
Expand All @@ -38,7 +38,7 @@ repos:
types: [markdown]
args:
- -c
- pre_commit_configs/link-config.json
- .pre_commit_configs/link-config.json
always_run: true

- repo: local
Expand Down
File renamed without changes.
File renamed without changes.
9 changes: 5 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ In general the following steps are involved during a contribution:
5. 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. Once development is finished, create a pull request including your changes.
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.
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).
8. 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".
9. A member of the core development team will review your pull request and potentially require further changes
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
(see [Contribution review and integration](#contribution-review-and-integration)).
Once all remarks have been resolved, your changes will be merged into the develop branch.

Expand Down
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The recommended way to install SIMPA is a manual installation from the GitHub re
4. `git pull`

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.8)
virtual environment activated (we also recommend python 3.10)
1. `pip install .`
2. Test if the installation worked by using `python` followed by `import simpa` then `exit()`

Expand Down Expand Up @@ -183,13 +183,17 @@ Please see the github guidelines for creating pull requests: [https://docs.githu

# Performance profiling

Do you wish to know which parts of the simulation pipeline cost the most amount of time?
If that is the case then you can use the following commands to profile the execution of your simulation script.
You simply need to replace the `myscript` name with your script name.
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:

`python -m cProfile -o myscript.cprof myscript.py`
```bash
bash ./run_benchmark.sh
```

once for checking if it works and then parse [--number 100] to run it at eg 100 times for actual benchmarking.
Please see [benchmarking.md](docs/source/benchmarking.md) for a complete explanation.

`pyprof2calltree -k -i myscript.cprof`

# Troubleshooting

Expand Down
2 changes: 2 additions & 0 deletions docs/source/bench_link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
```{include} benchmarking.md
```
132 changes: 132 additions & 0 deletions docs/source/benchmarking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Benchmarking SIMPA

## Overview
The [run_benchmarking.sh](../../simpa_examples/benchmarking/run_benchmarking.sh) bash script helps you benchmark
SIMPA simulations with various profiling options such as time, GPU memory, and memory usage.
It allows customization of initial spacing, final spacing, step size, output file location, and number of simulations.
To check and amend which simulations are run, see the [performance check script](../../simpa_examples/benchmarking/performance_check.py).

## Usage
To use this script, run it from the command line with the desired options. Please ensure you check two things before
running the script: First, ensure that the device will not be in use for the duration - ideally restart before
benchmarking - of the benchmarking process,
as this will create large uncertainties within the outcomes. Second, ensure that you don't accidentally write over any
existing file by saving the files created by this script after runtime to different location.

The script will create multiple text files (eg. benchmarking_data_TIME_0.2.txt), showing the line by line profiling of
the most recent runs, as well as two csv's with the data from all the runs (benchmarking_data_frame.csv) and the means
and standard deviations of all the runs (benchmarking_data_frame_mean.csv).

Below is a description of the available options and how to use them.

### Benchmarking for contributions
When contributing, you may be asked by the development team to benchmarking the changes you've made to help them
understand how your changes have effected the performance of SIMPA. Therefore, we ask that you run this script with
**`-n, --number`** as 100 before AND after your changes, on a clean setup with no browser or other applications running.
Please put this in the conversation of the pull request, and not add it to the files of the pull request itself.


## Options
- **`-i, --init`**: First spacing to benchmark (default = 0.2mm).
- **`-c, --cease`**: Final spacing to benchmark (default = 0.4mm).
- **`-s, --step`**: Step between spacings (default = 0.1mm).
- **`-f, --file`**: Where to store the output files (default = save in current directory; 'print' prints it in console).
- **`-t, --time`**: Profile times taken (if no profile is specified, all are set).
- **`-g, --gpu`**: Profile GPU usage (if no profile is specified, all are set).
- **`-m, --memory`**: Profile memory usage (if no profile is specified, all are set).
- **`-n, --number`**: Number of simulations (default = 1).
- **`-h, --help`**: Display this help message.

## Default Values
If no options are provided for initial spacing, final spacing, or step size, the script uses the following default
values:
- **Initial Spacing**: 0.2mm
- **Final Spacing**: 0.4mm
- **Step Size**: 0.1mm

If no profiling options are specified, all three profilers (time, GPU memory, and memory) are used by default.

## Examples
Here are some examples of how to use the script:

1. **Default Usage**:
```bash
bash ./run_benchmark.sh
```

2. **Custom Spacing and File Output**:
```bash
bash ./run_benchmark.sh -i 0.1 -c 0.5 -s 0.05 -f results
```

3. **Profile Time and GPU Memory for 3 Simulations**:
```bash
bash ./run_benchmark.sh -t -g -n 3
```

To read the csv you can use the following code:
```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)
```

The expected outcome should look something similar to the below:

![img.png](images/benchmarking_table.png)

# Line Profiler (more advanced - for specific function profiling)

Within SIMPA we have an [inbuilt python script](../../simpa/utils/profiling.py) to help benchmark specific functions and
understand the holdups in our code. This script is designed to set up a profiling environment based on an environment
variable named `SIMPA_PROFILE`. The `@profile` decorator can then be added to functions to see line-by-line statistics
of code performance.

Here is a breakdown of the script's functionality:

1. **Determine Profile Type and Stream:**
- `profile_type` is fetched from the environment variable `SIMPA_PROFILE`.
- `stream` is set to an open file object if the environment variable `SIMPA_PROFILE_SAVE_FILE` is set; otherwise, it is `None`.

2. **No Profiling:**
- If `profile_type` is `None`

3. **Time Profiling:**
- If `profile_type` is `"TIME"`

4. **Memory Profiling:**
- If `profile_type` is `"MEMORY"`

5. **GPU Memory Profiling:**
- If `profile_type` is `"GPU_MEMORY"`

6. **Profile Decorator**
- The `profile` decorator is defined to register functions to be profiled and to print statistics upon program exit.

7. **Invalid Profile Type:**
- If `profile_type` does not match any of the expected values (`"TIME"`, `"MEMORY"`, or `"GPU_MEMORY"`), a `RuntimeError` is raised.

### Example Usage

To use this script, you need to set the `SIMPA_PROFILE` environment variable to one of the supported values
(`TIME`, `MEMORY`, or `GPU_MEMORY`) and optionally set the `SIMPA_PROFILE_SAVE_FILE` to specify where to save the
profiling results.

The environment variables must be set before importing simpa in your python script like below.
```python
import os
os.environ("SIMPA_PROFILE")="TIME"
os.environ("SIMPA_PROFILE_SAVE_FILE")=profile_results.txt
```

To use the `@profile` decorator, simply apply it to the functions you want to profile within your script:

```python
@profile
def some_function():
# function implementation
```

Make sure the necessary profiling modules (`line_profiler`, `memory_profiler`, `pytorch_memlab`) are installed in your
environment.
Binary file added docs/source/images/benchmarking_table.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pipeline according to their specific use cases and tool requirements.

intro_link
contributing_link
bench_link.md

===================================

Expand Down
2 changes: 1 addition & 1 deletion docs/source/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The recommended way to install SIMPA is a manual installation from the GitHub re
4. `git pull`

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.8)
virtual environment activated (we also recommend python 3.10)
1. `pip install .`
2. Test if the installation worked by using `python` followed by `import simpa` then `exit()`

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ description = "Simulation and Image Processing for Photonics and Acoustics"
license = {text = "MIT"}
readme = "README.md"
keywords = ["simulation", "photonics", "acoustics"]
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = [
"matplotlib>=3.5.0", # Uses PSF-License (MIT compatible)
"numpy>=1.21.4", # Uses BSD-License (MIT compatible)
"scipy>=1.7.2,<1.14.0", # Uses BSD-like-License (MIT compatible)
"scipy>=1.13.0", # Uses BSD-like-License (MIT compatible)
"pynrrd>=0.4.2", # Uses MIT-License (MIT compatible)
"scikit-image>=0.18.3", # Uses BSD-License (MIT compatible)
"xmltodict>=0.12.0", # Uses MIT-License (MIT compatible)
Expand Down Expand Up @@ -54,7 +54,7 @@ Documentation = "https://simpa.readthedocs.io/en/main/"
Repository = "https://github.com/IMSY-DKFZ/simpa"

[tool.setuptools.packages.find]
include = ["simpa", "simpa_tests"]
include = ["simpa", "simpa_tests", "simpa_examples"]

[tool.setuptools_scm]

Expand Down
6 changes: 6 additions & 0 deletions simpa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@

from .utils import *
from .log import Logger
from importlib.metadata import version, PackageNotFoundError

try:
__version__ = version("simpa")
except PackageNotFoundError:
__version__ = "unknown version"

from .core.simulation_modules.volume_creation_module.volume_creation_module_model_based_adapter import \
ModelBasedVolumeCreationAdapter
from .core.simulation_modules.volume_creation_module.volume_creation_module_segmentation_based_adapter import \
Expand Down
5 changes: 4 additions & 1 deletion simpa/core/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# SPDX-License-Identifier: MIT

from simpa.utils import Tags
from simpa import __version__

from simpa.io_handling.io_hdf5 import save_hdf5, load_hdf5, save_data_field, load_data_field
from simpa.io_handling.ipasc import export_to_ipasc
from simpa.utils.settings import Settings
Expand Down Expand Up @@ -54,7 +56,8 @@ def simulate(simulation_pipeline: list, settings: Settings, digital_device_twin:
simpa_output_path = path + settings[Tags.VOLUME_NAME]

settings[Tags.SIMPA_OUTPUT_PATH] = simpa_output_path + ".hdf5"


simpa_output[Tags.SIMPA_VERSION] = __version__
simpa_output[Tags.SETTINGS] = settings
simpa_output[Tags.DIGITAL_DEVICE] = digital_device_twin
simpa_output[Tags.SIMULATION_PIPELINE] = [type(x).__name__ for x in simulation_pipeline]
Expand Down
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
2 changes: 2 additions & 0 deletions simpa/io_handling/io_hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ def data_grabber(file, path):
"""

if isinstance(h5file[path], h5py._hl.dataset.Dataset):
if isinstance(h5file[path][()], bytes):
return h5file[path][()].decode("utf-8")
return h5file[path][()]

dictionary = {}
Expand Down
2 changes: 1 addition & 1 deletion simpa/utils/dict_path_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def generate_dict_path(data_field, wavelength: (int, float) = None) -> str:
:return: String which defines the path to the data_field.
"""

if data_field in [Tags.SIMULATIONS, Tags.SETTINGS, Tags.DIGITAL_DEVICE, Tags.SIMULATION_PIPELINE]:
if data_field in [Tags.SIMPA_VERSION, Tags.SIMULATIONS, Tags.SETTINGS, Tags.DIGITAL_DEVICE, Tags.SIMULATION_PIPELINE]:
return "/" + data_field + "/"

all_wl_independent_properties = wavelength_independent_properties + toolkit_tags
Expand Down
Loading

0 comments on commit 0f53cf2

Please sign in to comment.