diff --git a/.github/actions/test-matlab/action.yml b/.github/actions/test-matlab/action.yml
new file mode 100644
index 000000000..4f38361b8
--- /dev/null
+++ b/.github/actions/test-matlab/action.yml
@@ -0,0 +1,42 @@
+name: 'Matlab Test'
+author: 'e0404'
+description: 'Runs a Matlab Test for a given version'
+
+inputs:
+ matlab-version:
+ description: Matlab Version, e.g. R2022b
+ required: true
+
+runs:
+ using: 'composite'
+ steps:
+ # Install MATLAB
+ - name: Install MATLAB
+ uses: matlab-actions/setup-matlab@v2
+ with:
+ release: ${{ inputs.matlab-version }}
+ products: Image_Processing_Toolbox Parallel_Computing_Toolbox Optimization_Toolbox
+
+ # Runs test command
+ - name: Run Tests
+ uses: matlab-actions/run-command@v2
+ with:
+ command: matRad_runTests('test',true);
+
+ - name: Upload Test Results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results-matlab-${{ inputs.matlab-version }}
+ path: |
+ testresults.xml
+ coverage.xml
+ coverage.json
+
+
+ - name: Publish Test Results
+ uses: test-summary/action@v2
+ if: success() || failure()
+ with:
+ paths: testresults.xml
+ # check_name: "${{ github.job }}"
\ No newline at end of file
diff --git a/.github/actions/test-octave/action.yml b/.github/actions/test-octave/action.yml
new file mode 100644
index 000000000..fd78be330
--- /dev/null
+++ b/.github/actions/test-octave/action.yml
@@ -0,0 +1,39 @@
+name: 'Octave Test'
+author: 'e0404'
+description: 'Runs an Octave test. Uses the version in the respective Ubuntu-OS'
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Install OCTAVE
+ shell: bash
+ run: |
+ sudo apt update
+ sudo apt-get install -y gdb gfortran fonts-freefont-otf gnuplot-x11 libgdcm-dev octave liboctave-dev
+ - name: Prepare Test Environment
+ shell: bash
+ run: |
+ sudo chmod +x .github/before_install_linux.sh
+ sudo .github/before_install_linux.sh
+ - name: Run Tests
+ shell: bash
+ run: xvfb-run -a .github/runtests.sh octave-cli
+ # uses: GabrielBB/xvfb-action@v1 #For Headless tests
+ # with:
+ # run: .github/runtests.sh octave-cli
+ - name: Upload Test Results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results-octave
+ path: testresults.xml
+
+ - name: Publish Test Results
+ # uses: EnricoMi/publish-unit-test-result-action@v2
+ uses: test-summary/action@v2
+ if: success() || failure()
+ with:
+ # files: |
+ # testresults.xml
+ paths: testresults.xml
+ # check_name: "${{ github.job }}"
diff --git a/.github/before_install_linux.sh b/.github/before_install_linux.sh
index 12beb1bde..853cd16c6 100644
--- a/.github/before_install_linux.sh
+++ b/.github/before_install_linux.sh
@@ -1,9 +1,10 @@
#!/usr/bin/env bash
sudo chmod +x .github/runtests.sh
-sudo chmod +x MCsquare/bin/MCsquare_linux
+sudo chmod +x thirdParty/MCsquare/bin/MCsquare_linux
-mv optimization/optimizer/ipopt.m optimization/optimizer/ipopt.m.bak
+mv matRad/optimization/optimizer/ipopt.m optimization/optimizer/ipopt.m.bak
octave --no-gui --eval "pkg install -forge dicom"
octave --no-gui --eval "pkg install -forge nan"
+octave --no-gui --eval "pkg install -forge image"
diff --git a/.github/runtests.bat b/.github/runtests.bat
index b74e0d62f..2f6478a2a 100644
--- a/.github/runtests.bat
+++ b/.github/runtests.bat
@@ -33,7 +33,7 @@ set CONTINUOUS_INTEGRATION=true
set CI=true
REM Actually run the test suite
-cd unitTest
+cd test
set TESTDIR="%cd%"
REM also CD in MATLAB/Octave to make sure that startup files
REM cannot play any role in setting the path
diff --git a/.github/runtests.sh b/.github/runtests.sh
index 33afcc80f..913bfadae 100644
--- a/.github/runtests.sh
+++ b/.github/runtests.sh
@@ -39,11 +39,8 @@ export CONTINUOUS_INTEGRATION=true
export CI=true
## Actually run the test suite
-cd unitTest
-TESTDIR=`pwd`
# also CD in MATLAB/Octave to make sure that startup files
# cannot play any role in setting the path
-${Runner} ${Switches} "cd('${TESTDIR}'); matRad_runTests" > ../runtests.log #2> ../runtests.err put stdout to log, but let it print error messages
+${Runner} ${Switches} "matRad_runTests('test');" > ../runtests.log #2> ../runtests.err put stdout to log, but let it print error messages
exitIfError $?
-cd ..
diff --git a/.github/workflows/coverage-report.yml b/.github/workflows/coverage-report.yml
new file mode 100644
index 000000000..3de3817b4
--- /dev/null
+++ b/.github/workflows/coverage-report.yml
@@ -0,0 +1,59 @@
+name: Coverage Report
+
+permissions:
+ checks: write
+ pull-requests: write
+ actions: read
+ contents: read
+
+
+on:
+ workflow_call:
+
+jobs:
+ coverage_report:
+ runs-on: ubuntu-latest
+
+ steps:
+
+ - name: Download Coverage Artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: test-results-matlab-R2022b
+ path: coverage
+
+ - name: Upload to Coveralls
+ uses: coverallsapp/github-action@v2
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ file: coverage/coverage.json
+ format: coveralls
+
+ - name: Publish Coverage Report
+ uses: irongut/CodeCoverageSummary@v1.3.0
+ with:
+ filename: coverage/coverage.xml
+ badge: true
+ fail_below_min: false
+ format: markdown
+ hide_branch_rate: true
+ hide_complexity: true
+ indicators: true
+ output: both
+ thresholds: '40 70'
+
+ - name: Attach Job Summary
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const markdownContent = fs.readFileSync('code-coverage-results.md', 'utf8');
+ core.summary.addRaw(markdownContent);
+ core.summary.write();
+
+ - name: Add Coverage PR Comment
+ uses: marocchino/sticky-pull-request-comment@v2
+ if: github.event_name == 'pull_request'
+ with:
+ recreate: true
+ path: code-coverage-results.md
diff --git a/.github/workflows/test-results.yml b/.github/workflows/test-results.yml
new file mode 100644
index 000000000..8322c983a
--- /dev/null
+++ b/.github/workflows/test-results.yml
@@ -0,0 +1,40 @@
+name: Test Report
+
+permissions:
+ checks: write
+ pull-requests: write
+
+on:
+ workflow_call:
+
+jobs:
+ test_report:
+ runs-on: ubuntu-latest
+
+ steps:
+
+ - name: Download Results for Matlab R2022b
+ uses: actions/download-artifact@v4
+ with:
+ name: test-results-matlab-R2022b
+ path: test-results/matlab-R2022b
+
+ - name: Download Results for Matlab Latest
+ uses: actions/download-artifact@v4
+ with:
+ name: test-results-matlab-latest
+ path: test-results/matlab-latest
+
+ - name: Download Results for Octave
+ uses: actions/download-artifact@v4
+ with:
+ name: test-results-octave
+ path: test-results/octave
+
+ - name: Publish Test Results
+ uses: EnricoMi/publish-unit-test-result-action@v2
+ with:
+ files: |
+ test-results/*/testresults.xml
+
+
\ No newline at end of file
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 9143c1735..6d21ea359 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,57 +1,52 @@
# This is a basic workflow to help you get started with Actions
name: Tests
+
+permissions:
+ checks: write
+ pull-requests: write
+ actions: read
+ contents: read
+
# Controls when the action will run.
on: [push, pull_request, workflow_dispatch]
jobs:
test-matlab-stable: #Matlab test Job for supported Release
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3 # Checks-out repository under $GITHUB_WORKSPACE
- # Install MATLAB
- - name: Install MATLAB
- uses: matlab-actions/setup-matlab@v1
+ - uses: actions/checkout@v4 # Checks-out repository under $GITHUB_WORKSPACE
with:
- release: R2022b
- # Runs test command
- - name: Run Tests
- uses: matlab-actions/run-command@v1
+ submodules: 'true'
+ - name: Run Test
+ uses: ./.github/actions/test-matlab
with:
- command: cd unitTest; matRad_runTests
+ matlab-version: R2022b
+
test-matlab-latest: #Matlab test Job for latest Matlab release
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3 # Checks-out repository under $GITHUB_WORKSPACE
- # Install MATLAB
- - name: Install MATLAB
- uses: matlab-actions/setup-matlab@v1
+ - uses: actions/checkout@v4 # Checks-out repository under $GITHUB_WORKSPACE
with:
- release: latest
- # Runs test command
- - name: Run Tests
- uses: matlab-actions/run-command@v1
+ submodules: 'true'
+ - uses: ./.github/actions/test-matlab
with:
- command: cd unitTest; matRad_runTests
+ matlab-version: latest
+
test-octave-6: #Octave test Job
runs-on: ubuntu-22.04 # We use Ubuntu-22.04 because it has Octave 6.4
steps:
- - uses: actions/checkout@v3 # Checks-out repository under $GITHUB_WORKSPACE
- - name: Install OCTAVE
- run: |
- sudo apt update
- sudo apt-get install -y gdb gfortran fonts-freefont-otf gnuplot-x11 libgdcm-dev octave liboctave-dev
- - name: Prepare Test Environment
- run: |
- sudo chmod +x .github/before_install_linux.sh
- sudo .github/before_install_linux.sh
- - name: Run Tests
- run: xvfb-run -a .github/runtests.sh octave-cli
- # uses: GabrielBB/xvfb-action@v1 #For Headless tests
- # with:
- # run: .github/runtests.sh octave-cli
- - name: Upload logs if test fails
- uses: actions/upload-artifact@v3
- if: failure()
+ - uses: actions/checkout@v4 # Checks-out repository under $GITHUB_WORKSPACE
with:
- name: Test Log
- path: runtests.log
-
+ submodules: 'true'
+ - uses: ./.github/actions/test-octave
+
+ test-report:
+ name: Collect Test Results
+ needs: [test-matlab-stable, test-matlab-latest, test-octave-6]
+ uses: ./.github/workflows/test-results.yml
+ secrets: inherit
+
+ coverage-report:
+ name: Collect Coverage Report
+ needs: [test-matlab-stable]
+ uses: ./.github/workflows/coverage-report.yml
+ secrets: inherit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0aa682893..cd0e481c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,7 @@
-MCsquare/bin/Materials/
+thirdParty/MCsquare/bin/Materials/
+userdata/*
+testresults.xml
+coverage.xml
+coverage.json
+*.asv
+build/
diff --git a/.gitmodules b/.gitmodules
index 89add3106..b73e2e19a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,11 @@
+[submodule "submodules/matlab2tikz"]
+ path = submodules/matlab2tikz
+ url = https://github.com/e0404/matlab2tikz.git
+ branch = dev
+[submodule "submodules/latexTable"]
+ path = submodules/latexTable
+ url = https://github.com/e0404/latexTable.git
+ branch = dev
[submodule "submodules/MCsquare"]
path = submodules/MCsquare
url = https://github.com/e0404/MCsquare.git
@@ -5,3 +13,11 @@
path = submodules/ompMC
url = https://github.com/e0404/ompMC
branch = dev_parallel
+[submodule "submodules/MOxUnit"]
+ path = submodules/MOxUnit
+ url = https://github.com/e0404/MOxUnit
+ branch = matRad
+[submodule "submodules/MOcov"]
+ path = submodules/MOcov
+ url = https://github.com/e0404/MOcov
+ branch = matRad
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 7dccd4bf9..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,102 +0,0 @@
-jobs:
- include:
- # works on Precise and Trusty
- - name: "Ubuntu Octave"
- os: linux
- language: cpp
- dist: focal # 20.04 -> Octave 5.2
- services:
- - xvfb
- addons:
- apt:
- update: true
- packages:
- - gdb
- - gfortran
- - fonts-freefont-otf
- - gnuplot-x11
- - libgdcm-dev
- - octave
- - liboctave-dev
- - name: "Ubuntu Matlab"
- os: linux
- language: matlab
- dist: focal
- - name: "OSX Octave"
- os: osx
- language: cpp
- addons:
- homebrew:
- update: true
- packages:
- - llvm
- - libomp
- - gdcm
- - octave
- cache:
- directories:
- - $HOME/Library/Caches/Homebrew
- - name: "Windows Octave"
- os: windows
- language: cpp
- cache:
- directories:
- - $HOME/AppData/Local/Temp/chocolatey
- - /C/ProgramData/chocolatey/
- allow_failures:
- - os: windows
- - os: osx
-
-before_install:
- # Linux setup
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Octave" ]]; then echo "Testing matRad on linux with Octave..." ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Octave" ]]; then sudo chmod +x .travis/before_install_linux.sh ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Octave" ]]; then sudo .travis/before_install_linux.sh; fi
-
- # Linux Matlab Setup
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Matlab" ]]; then echo "Testing matRad on linux with Matlab..." ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Matlab" ]]; then sudo chmod +x .travis/runtests.sh ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Matlab" ]]; then sudo chmod +x MCsquare/bin/MCsquare_linux ; fi
-
- # OSX setup
- - if [[ "$TRAVIS_JOB_NAME" == "OSX Octave" ]]; then echo "Testing matRad on Mac OSX..." ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "OSX Octave" ]]; then sudo chmod +x .travis/before_install_osx.sh ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "OSX Octave" ]]; then sudo .travis/before_install_osx.sh; fi
-
- # Windows Setup
- - if [[ "$TRAVIS_JOB_NAME" == "Windows Octave" ]]; then echo "Testing matRad on Windows..." ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "Windows Octave" ]]; then choco install octave.portable ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "Windows Octave" ]]; then bash .travis/before_install_win_gitbash.sh ; fi
-
-
-before_script:
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Octave" ]]; then ulimit -c unlimited -S ; fi
-
-
-after_failure:
- ## Linux stack trace
- # find core file
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Octave" ]]; then COREFILE=$(find . -maxdepth 1 -name "core*" | head -n 1) ; fi
- # print stack trace
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Octave" ]]; then gdb -c "$COREFILE" -ex "thread apply all bt" -ex "set pagination 0" -batch /usr/bin/octave-cli ; fi
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Octave" ]]; then tail runtests.log ; fi
-
-script:
- # Linux Octave script
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Octave" ]]; then travis_wait 45 .travis/runtests.sh octave-cli ; fi
- # Linux Matlab script
- - if [[ "$TRAVIS_JOB_NAME" == "Ubuntu Matlab" ]]; then travis_wait 45 .travis/runtests.sh matlab; fi
- # OSX script
- - if [[ "$TRAVIS_JOB_NAME" == "OSX Octave" ]]; then travis_wait 45 .travis/runtests.sh octave-cli ; fi
- # Windows script
- - if [[ "$TRAVIS_JOB_NAME" == "Windows Octave" ]]; then travis_wait 45 "cmd.exe //C RefreshEnv.cmd & .travis/runtests.sh octave-cli" ; fi
-
-before_cache:
- - if [[ "$TRAVIS_JOB_NAME" == "OSX Octave" ]]; then brew cleanup; fi
-
-notifications:
- slack:
- if: repo = e0404/matRad
- rooms:
- - e0404:u5tBXbO6D1mEwzJuFZV0MmqJ
-
\ No newline at end of file
diff --git a/AUTHORS.txt b/AUTHORS.txt
index e96a490dd..8d2dc3189 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -1,29 +1,43 @@
-List of all matRad developers that contributed code (alphabetical)
+List of all matRad developers that contributed code (alphabetical)
+* Nelly Abbani
+* Nabe Al-Hasnawi
* Mark Bangert
+* Tobias Becher
* Amit Ben Antony Bennan
* Lucas Burigo
* Gonzalo Cabal
* Eduadro Cisternas
-* Edgardo Doerner
* Louis Charton
* Eric Christiansen
+* Remo Cristoforetti
+* Marios Dallas
+* Edgardo Doerner
+* Simona Facchiano
* Hubert Gabrys
* Josefine Handrack
+* Jennifer Hardt
* Emily Heath
* Cindy Hermann
* Noa Homolka
+* Raed Ibragim
+* Fabian Jäger
* Fernando Hueso-González
* Navid Khaledi
* Thomas Klinge
+* Jeremias Kunz
+* Paul Anton Meder
* Henning Mescher
* Lucas-Raphael Müller
* Ahmad Neishabouri
* Martina Palkowitsch
* Giuseppe Pezzano
* Daniel Ramirez
+* Claus Sarnighausen
* Carsten Scholz
+* Camilo Sevilla
* Alexander Stadler
+* Uwe Titt
* Niklas Wahl
* Jona Welsch
* Hans-Peter Wieser
diff --git a/ChangeLog.txt b/ChangeLog.txt
index cd58e42ec..ae60edf09 100644
--- a/ChangeLog.txt
+++ b/ChangeLog.txt
@@ -1,3 +1,41 @@
+Monte-Carlo Update
+ Workflow of the Monte Carlo pipeline including MCsquare and TOPAS have been completely overhauled
+ - Restructured the MCEmittanceBaseData class, fit and calculation pipeline
+ - Added example 13 for generating analytical data file by fitting to given machine emittance
+ - added function to plot particleBaseDataEntry
+ - edited function to fit base data
+ - added function to generate a single pencil beam
+ - MCemmittanceBaseData can calculate meanEnergy and spread also for carbon and helium ions
+ - Added support for 4D calculation
+ - added 4D accumulation in calcDoseDirectMC
+ - Fixed number of errors in 4D calculation workflow
+ - TOPAS Updates:
+ - Added comments to whole pipeline
+ - Implemented dij calculation
+ - Restructured resampling in calcParticleDoseMCtopas in separate function
+ - calcParticleDoseMCtopas now generates dij in matRad format for calcDoseDirect
+ - Merged support functions for TOPAS into topasConfig class
+ - modular Schneider Converter: Converter is generated on demand rather than read from file which allows a variety of different options
+ - modular TOPAS scorer which can be individually turned on and off
+ - Export feature for TOPAS to run externally (includes functions to read from external folders)
+ - MCsquare Updates:
+ - Merged support functions for MCsquare into MCsquareConfig class
+ - added variable in pln to contain an existing BDL file if available
+ - renamed MCsquare property "Num_Primaries" -> "numHistories" to be in line with other Monte Carlo (this is written to the BDL file in original format)
+ - Implemented calculation of std of physicalDose
+
+ Changes to matRad workflow:
+ - Added a class constructor for pln in MatRad_config, which loads requested classes in pln and writes default values that were not set
+ - Changed matRad_calcCubes to accept a variety of different fields for Monte Carlo, without changing the current usage
+ - Enabled helium in calcdoseDirect
+ - Bugfix for coordinate system in resizeCstToGrid function
+ - Added optional initial weights to fluence Optimization
+ - Added flag in stf to catch specific error where no energies could be found
+
+Development Changes
+ - Bugfixes:
+ - Fix SFUD wrapper for new pln & cst format
+
Version 2.10.1 - Patch release for "Blaise"
Release with small updates, clean-ups and bugfixes
- Bugfix in 3D view due to inconsistent angles in pln & stf
diff --git a/IO/matRad_exportGUI.fig b/IO/matRad_exportGUI.fig
deleted file mode 100644
index 27769a09b..000000000
Binary files a/IO/matRad_exportGUI.fig and /dev/null differ
diff --git a/IO/matRad_exportGUI.m b/IO/matRad_exportGUI.m
deleted file mode 100644
index ac0ef2723..000000000
--- a/IO/matRad_exportGUI.m
+++ /dev/null
@@ -1,490 +0,0 @@
-function varargout = matRad_exportGUI(varargin)
-% MATRAD_EXPORTGUI MATLAB code for matRad_exportGUI.fig
-% MATRAD_EXPORTGUI, by itself, creates a new MATRAD_EXPORTGUI or raises the existing
-% singleton*.
-%
-% H = MATRAD_EXPORTGUI returns the handle to a new MATRAD_EXPORTGUI or the handle to
-% the existing singleton*.
-%
-% MATRAD_EXPORTGUI('CALLBACK',hObject,eventData,handles,...) calls the local
-% function named CALLBACK in MATRAD_EXPORTGUI.M with the given input arguments.
-%
-% MATRAD_EXPORTGUI('Property','Value',...) creates a new MATRAD_EXPORTGUI or raises
-% the existing singleton*. Starting from the left, property value pairs are
-% applied to the GUI before matRad_exportGUI_OpeningFcn gets called. An
-% unrecognized property name or invalid value makes property application
-% stop. All inputs are passed to matRad_exportGUI_OpeningFcn via varargin.
-%
-% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
-% instance to run (singleton)".
-%
-% See also: GUIDE, GUIDATA, GUIHANDLES
-
-% Edit the above text to modify the response to help matRad_exportGUI
-
-% Last Modified by GUIDE v2.5 07-Jul-2016 14:50:05
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% Begin initialization code - DO NOT EDIT
-gui_Singleton = 1;
-gui_State = struct('gui_Name', mfilename, ...
- 'gui_Singleton', gui_Singleton, ...
- 'gui_OpeningFcn', @matRad_exportGUI_OpeningFcn, ...
- 'gui_OutputFcn', @matRad_exportGUI_OutputFcn, ...
- 'gui_LayoutFcn', [] , ...
- 'gui_Callback', []);
-if nargin && ischar(varargin{1})
- gui_State.gui_Callback = str2func(varargin{1});
-end
-
-if nargout
- [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
-else
- gui_mainfcn(gui_State, varargin{:});
-end
-% End initialization code - DO NOT EDIT
-
-% --- Executes just before matRad_exportGUI is made visible.
-function matRad_exportGUI_OpeningFcn(hObject, eventdata, handles, varargin)
-% This function has no output args, see OutputFcn.
-% hObject handle to figure
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-% varargin command line arguments to matRad_exportGUI (see VARARGIN)
-
-% Choose default command line output for matRad_exportGUI
-handles.output = hObject;
-
-%Fills structure export table
-if evalin('base','exist(''cst'',''var'')') == 1
- cst = evalin( 'base', 'cst' );
- tableData = cell(numel(cst(:,2)),2);
- tableData(:,2) = cst(:,2);
- tableData(:,1) = {true};
-else
- tableData = cell(0);
- set(handles.checkbox_CT,'Enable','off');
-end
-set(handles.uitable_vois,'data',tableData);
-
-%Fills result cubes export table
-if evalin('base','exist(''resultGUI'',''var'')')
- result = evalin( 'base', 'resultGUI' );
- cubeNames = fieldnames(result);
- cubeIx = 1;
- for f = 1:numel(cubeNames)
- if ndims(result.(cubeNames{f})) < 3
- continue;
- end
- cubes{cubeIx} = cubeNames{f};
- cubeIx = cubeIx + 1;
- end
- numCubes = cubeIx - 1;
- tableData = cell(numCubes,2);
- tableData(:,2) = cubes;
- tableData(:,1) = {true};
-else
- tableData = cell(0);
- set(handles.checkbox_dose,'Enable','off');
-end
-set(handles.uitable_doseCubes,'data',tableData);
-
-
-% Update handles structure
-guidata(hObject, handles);
-
-initialize_gui(hObject, handles, false);
-
-% UIWAIT makes matRad_exportGUI wait for user response (see UIRESUME)
-% uiwait(handles.figure1);
-
-
-% --- Outputs from this function are returned to the command line.
-function varargout = matRad_exportGUI_OutputFcn(hObject, eventdata, handles)
-% varargout cell array for returning output args (see VARARGOUT);
-% hObject handle to figure
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Get default command line output from handles structure
-varargout{1} = handles.output;
-
-% --------------------------------------------------------------------
-function initialize_gui(fig_handle, handles, isreset)
-% If the metricdata field is present and the btn_cancel flag is false, it means
-% we are we are just re-initializing a GUI by calling it from the cmd line
-% while it is up. So, bail out as we dont want to btn_cancel the data.
-if isfield(handles, 'metricdata') && ~isreset
- return;
-end
-
-%{
-handles.metricdata.density = 0;
-handles.metricdata.volume = 0;
-
-set(handles.density, 'String', handles.metricdata.density);
-set(handles.volume, 'String', handles.metricdata.volume);
-set(handles.mass, 'String', 0);
-
-set(handles.unitgroup, 'SelectedObject', handles.english);
-
-set(handles.text4, 'String', 'lb/cu.in');
-set(handles.text5, 'String', 'cu.in');
-set(handles.text6, 'String', 'lb');
-
-% Update handles structure
-guidata(handles.figure1, handles);
-%}
-
-% --- Executes on button press in checkbox_CT.
-function checkbox_CT_Callback(hObject, eventdata, handles)
-% hObject handle to checkbox_CT (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-saveCT = get(hObject,'Value');
-
-%Show the VOI-table only if we want to save a CT
-if (saveCT)
- set(handles.uitable_vois,'Visible', 'on', 'Enable','on');
-else
- set(handles.uitable_vois,'Visible', 'off', 'Enable','off');
-end
-
-
-% --- Executes on selection change in listbox_vois.
-function uitable_vois_Callback(hObject, eventdata, handles)
-% hObject handle to listbox_vois (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns listbox_vois contents as cell array
-% contents{get(hObject,'Value')} returns selected item from listbox_vois
-
-
-% --- Executes during object creation, after setting all properties.
-function uitable_vois_CreateFcn(hObject, eventdata, handles)
-% hObject handle to listbox_vois (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: listbox controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on button press in checkbox_dose.
-function checkbox_dose_Callback(hObject, eventdata, handles)
-% hObject handle to checkbox_dose (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-
-%Show the Result-table only if we want to save dose cubes
-saveDose = get(hObject,'Value');
-if (saveDose)
- set(handles.uitable_doseCubes,'Visible', 'on', 'Enable','on');
-
-else
- set(handles.uitable_doseCubes,'Visible', 'off', 'Enable','off');
- %set(handles.uitable_vois,'data',cell(0));
-end
-
-
-% --- Executes on selection change in listbox_dose.
-function uitable_doseCubes_Callback(hObject, eventdata, handles)
-% hObject handle to listbox_dose (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns listbox_dose contents as cell array
-% contents{get(hObject,'Value')} returns selected item from listbox_dose
-
-
-% --- Executes during object creation, after setting all properties.
-function listbox_dose_CreateFcn(hObject, eventdata, handles)
-% hObject handle to listbox_dose (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: listbox controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on button press in pushbutton_dir_export_browse.
-function exportDir = pushbutton_dir_export_browse_Callback(hObject, eventdata, handles)
-% hObject handle to pushbutton_dir_export_browse (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-exportDir = uigetdir('', 'Choose the export directory...');
-if exportDir ~= 0
- exportDir = [exportDir filesep];
- set(handles.edit_dir_export,'String',exportDir);
- % Update handles structure
- guidata(hObject, handles);
-end
-
-
-function exportDir = edit_dir_export_Callback(hObject, eventdata, handles)
-% hObject handle to edit_dir_export (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of edit_dir_export as text
-% str2double(get(hObject,'String')) returns contents of edit_dir_export as a double
-
-exportDir = get(handles.edit_dir_export,'String');
-
-%Add filesperator
-if exportDir(end) ~= filesep;
- exportDir = [exportDir filesep];
-end
-
-%Check if the user specified an existing directory
-if ~exist(exportDir,'dir')
- warndlg(['Folder ' exportDir ' does not exist!']);
- exportDir = '';
-end
-set(handles.edit_dir_export,'String',exportDir);
-guidata(hObject, handles);
-
-% --- Executes during object creation, after setting all properties.
-function edit_dir_export_CreateFcn(hObject, eventdata, handles)
-% hObject handle to edit_dir_export (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-
-
-
-end
-
-
-% --- Executes on selection change in popupmenu_extension.
-function popupmenu_extension_Callback(hObject, eventdata, handles)
-% hObject handle to popupmenu_extension (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_extension contents as cell array
-% contents{get(hObject,'Value')} returns selected item from popupmenu_extension
-
-
-% --- Executes during object creation, after setting all properties.
-function popupmenu_extension_CreateFcn(hObject, eventdata, handles)
-% hObject handle to popupmenu_extension (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-%These sets up the available extensions
-extensions{1} = '*.nrrd';
-extensions{2} = '*.vtk';
-extensions{3} = '*.mha';
-set(hObject,'String',extensions);
-
-% Hint: popupmenu controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on button press in btn_export.
-function btn_export_Callback(hObject, eventdata, handles)
-% hObject handle to btn_export (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-%Get the export dir
-exportDir = get(handles.edit_dir_export,'String');
-
-%Sanity check
-if numel(exportDir) == 0
- errordlg('No Export folder selected!');
- return;
-elseif ~exist(exportDir,'dir')
- errordlg(['Folder ' exportDir ' does not exist!']);
- return;
-else
- %Add file separator if necessary
- if exportDir(end) ~= filesep;
- exportDir = [exportDir filesep];
- end
-end
-
-%Get the file extension
-extensionIndex = get(handles.popupmenu_extension,'Value');
-extensions = get(handles.popupmenu_extension,'String');
-extension = extensions{extensionIndex};
-extension = extension(2:end);
-
-saveCT = get(handles.checkbox_CT,'Value');
-saveResults = get(handles.checkbox_dose,'Value');
-
-%%Prepare for export
-%If we export CT, try to create a subdirectory for VOIs
-if (saveCT)
- voiDir = [exportDir '/vois/'];
- if ~exist(voiDir,'dir')
- if ~mkdir(voiDir)
- warndlg('Could not create subfolder for VOI masks. Masks will be stored in base folder.');
- voiDir = exportDir;
- end
- end
-end
-%If we export results, try to create a subdirectory for VOIs
-if (saveResults)
- resultDir = [exportDir '/results/'];
- if ~exist(resultDir,'dir')
- if ~mkdir(resultDir)
- warndlg('Could not create subfolder for resulting dose cubes. Cubes will be stored in base folder.');
- resultDir = exportDir;
- end
- end
-end
-
-%prepare metadata
-ct = evalin('base','ct');
-
-metadata.resolution = [ct.resolution.x ct.resolution.y ct.resolution.z];
-metadata.compress = get(handles.checkbox_compress,'Value');
-
-%Check if we have position information
-if isfield(ct,'dicomInfo')
- if isfield(ct.dicomInfo,'ImagePositionPatient')
- metadata.imageOrigin = ct.dicomInfo.ImagePositionPatient;
- if ~isrow(metadata.imageOrigin)
- metadata.imageOrigin = transpose(metadata.imageOrigin);
- end
- end
-end
-
-%This is only for the waitbar to get the number of cubes you wanna save
-numExportCubes = 0;
-if (saveCT)
- if isfield(ct,'cubeHU')
- numExportCubes = numExportCubes + 1;
- end
-
- if isfield(ct,'cube')
- numExportCubes = numExportCubes + 1;
- end
- voiNames = get(handles.uitable_vois,'Data');
- voiIndices = find([voiNames{:,1}] == true);
- numExportCubes = numExportCubes + numel(voiIndices);
-
-else
- numExportCubes = 0;
-end
-
-if saveResults
- cubeNames = get(handles.uitable_doseCubes,'data');
- cubeIndices = find([cubeNames{:,1}] == true);
- numExportCubes = numExportCubes + numel(cubeIndices);
-end
-
-%Give an error if nothing was selected
-if numExportCubes == 0
- errordlg('No data was selected for export!');
- return;
-end
-
-currentCube = 0;
-
-hWaitbar = waitbar(0,'Exporting...','WindowStyle', 'modal');
-cleanUp = onCleanup(@() close(hWaitbar));
-
-%CT and Mask export
-if saveCT
-
- if isfield(ct,'cube')
- %Export the CT (ED suffix to clarify it is not in HU)
- currentCube = currentCube + 1;
- waitbar(currentCube/numExportCubes,hWaitbar,['Exporting CT Intensity values (' num2str(currentCube) '/' num2str(numExportCubes) ') ...']);
- matRad_writeCube(fullfile(exportDir,['CT_ED' extension]),ct.cube{1},'double',metadata);
- end
-
- if isfield(ct,'cubeHU')
- currentCube = currentCube + 1;
- waitbar(currentCube/numExportCubes,hWaitbar,['Exporting CT in HU (' num2str(currentCube) '/' num2str(numExportCubes) ') ...']);
- matRad_writeCube(fullfile(exportDir,['CT_HU' extension]),ct.cubeHU{1},'double',metadata);
- end
-
- %Export VOI masks
- cst = evalin('base','cst');
-
- for voiIx = voiIndices
- %Waitbar
- currentCube = currentCube + 1;
- waitbar(currentCube/numExportCubes,hWaitbar,['Exporting Segmentation Mask (' num2str(currentCube) '/' num2str(numExportCubes) ') ...']);
-
- %Get the index list
- voiRow = find(strcmp(voiNames{voiIx,2},cst(:,2)));
- voiIndexList = cst{voiRow,4}{1};
- %Set up the full mask
- voiMask = zeros(ct.cubeDim);
- voiMask(voiIndexList) = 1;
- %Export...
- matRad_writeCube(fullfile(voiDir,[voiNames{voiIx,2} extension]),voiMask,'uint8',metadata);
- end
-
-end
-
-%Results Export
-if saveResults
- results = evalin('base','resultGUI');
- cubeNames = get(handles.uitable_doseCubes,'data');
-
- for cubeIx = cubeIndices
- %Export
- currentCube = currentCube + 1;
- waitbar(currentCube/numExportCubes,hWaitbar,['Exporting Results (' num2str(currentCube) '/' num2str(numExportCubes) ') ...']);
- matRad_writeCube(fullfile(resultDir,[cubeNames{cubeIx,2} extension]),results.(cubeNames{cubeIx,2}),'double',metadata);
- end
-end
-
-close(handles.figure1);
-
-
-% --- Executes on button press in btn_cancel.
-function btn_cancel_Callback(hObject, eventdata, handles)
-% hObject handle to btn_cancel (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-close(handles.figure1);
-
-% --- Executes during object creation, after setting all properties.
-function btn_cancel_CreateFcn(hObject, eventdata, handles)
-% hObject handle to btn_cancel (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% --- Executes on button press in checkbox_compress.
-function checkbox_compress_Callback(hObject, eventdata, handles)
-% hObject handle to checkbox_compress (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of checkbox_compress
diff --git a/IO/matRad_importGUI.fig b/IO/matRad_importGUI.fig
deleted file mode 100644
index 3181e7886..000000000
Binary files a/IO/matRad_importGUI.fig and /dev/null differ
diff --git a/IO/matRad_importGUI.m b/IO/matRad_importGUI.m
deleted file mode 100644
index c9f9e4c51..000000000
--- a/IO/matRad_importGUI.m
+++ /dev/null
@@ -1,325 +0,0 @@
-function varargout = matRad_importGUI(varargin)
-% MATRAD_IMPORTGUI MATLAB code for matRad_importGUI.fig
-% MATRAD_IMPORTGUI, by itself, creates a new MATRAD_IMPORTGUI or raises the existing
-% singleton*.
-%
-% H = MATRAD_IMPORTGUI returns the handle to a new MATRAD_IMPORTGUI or the handle to
-% the existing singleton*.
-%
-% MATRAD_IMPORTGUI('CALLBACK',hObject,eventData,handles,...) calls the local
-% function named CALLBACK in MATRAD_IMPORTGUI.M with the given input arguments.
-%
-% MATRAD_IMPORTGUI('Property','Value',...) creates a new MATRAD_IMPORTGUI or raises the
-% existing singleton*. Starting from the left, property value pairs are
-% applied to the GUI before matRad_importGUI_OpeningFcn gets called. An
-% unrecognized property name or invalid value makes property application
-% stop. All inputs are passed to matRad_importGUI_OpeningFcn via varargin.
-%
-% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
-% instance to run (singleton)".
-%
-% See also: GUIDE, GUIDATA, GUIHANDLES
-
-% Edit the above text to modify the response to help matRad_importGUI
-
-% Last Modified by GUIDE v2.5 09-Aug-2018 15:18:30
-
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% Begin initialization code - DO NOT EDIT
-gui_Singleton = 1;
-gui_State = struct('gui_Name', mfilename, ...
- 'gui_Singleton', gui_Singleton, ...
- 'gui_OpeningFcn', @matRad_importGUI_OpeningFcn, ...
- 'gui_OutputFcn', @matRad_importGUI_OutputFcn, ...
- 'gui_LayoutFcn', [] , ...
- 'gui_Callback', []);
-if nargin && ischar(varargin{1})
- gui_State.gui_Callback = str2func(varargin{1});
-end
-
-if nargout
- [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
-else
- gui_mainfcn(gui_State, varargin{:});
-end
-% End initialization code - DO NOT EDIT
-
-
-% --- Executes just before matRad_importGUI is made visible.
-function matRad_importGUI_OpeningFcn(hObject, eventdata, handles, varargin)
-% This function has no output args, see OutputFcn.
-% hObject handle to figure
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-% varargin command line arguments to matRad_importGUI (see VARARGIN)
-
-% Choose default command line output for matRad_importGUI
-handles.output = hObject;
-
-% Update handles structure
-guidata(hObject, handles);
-
-% UIWAIT makes matRad_importGUI wait for user response (see UIRESUME)
-% uiwait(handles.figure_importDialog);
-
-
-% --- Outputs from this function are returned to the command line.
-function varargout = matRad_importGUI_OutputFcn(hObject, eventdata, handles)
-% varargout cell array for returning output args (see VARARGOUT);
-% hObject handle to figure
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Get default command line output from handles structure
-varargout{1} = handles.output;
-
-
-
-function edit_ctPath_Callback(hObject, eventdata, handles)
-% hObject handle to edit_ctPath (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of edit_ctPath as text
-% str2double(get(hObject,'String')) returns contents of edit_ctPath as a double
-
-
-% --- Executes during object creation, after setting all properties.
-function edit_ctPath_CreateFcn(hObject, eventdata, handles)
-% hObject handle to edit_ctPath (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on button press in pushbutton_ctPath.
-function pushbutton_ctPath_Callback(hObject, eventdata, handles)
-% hObject handle to pushbutton_ctPath (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-[importCTFile,importCTPath,~] = uigetfile({'*.nrrd', 'NRRD-Files'}, 'Choose the CT file...');
-if importCTFile ~= 0
- set(handles.edit_ctPath,'String',fullfile(importCTPath,importCTFile));
- % Update handles structure
- guidata(hObject, handles);
-end
-
-
-
-function listbox_maskPaths_Callback(hObject, eventdata, handles)
-% hObject handle to listbox_maskPaths (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of listbox_maskPaths as text
-% str2double(get(hObject,'String')) returns contents of listbox_maskPaths as a double
-
-
-% --- Executes during object creation, after setting all properties.
-function listbox_maskPaths_CreateFcn(hObject, eventdata, handles)
-% hObject handle to listbox_maskPaths (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-set(hObject,'value',[],'max',2,'min',0,'String',cell(0));
-
-
-% --- Executes on button press in pushbutton_addMaskPaths.
-function pushbutton_addMaskPaths_Callback(hObject, eventdata, handles)
-% hObject handle to pushbutton_addMaskPaths (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-[importMaskFile,importMaskPath,~] = uigetfile({'*.nrrd', 'NRRD-Files'}, 'Choose the binary mask files...','MultiSelect','on');
-if ~isempty(importMaskFile)
- if ~iscell(importMaskFile)
- tmpName = importMaskFile;
- importMaskFile = cell(1);
- importMaskFile{1} = tmpName;
- end
- importMaskFile = cellfun(@(filename) fullfile(importMaskPath,filename),importMaskFile,'UniformOutput',false);
- entries = get(handles.listbox_maskPaths,'String');
- newEntries = [entries importMaskFile];
- set(handles.listbox_maskPaths,'String',newEntries);
- % Update handles structure
- guidata(hObject, handles);
-end
-
-
-% --- Executes on button press in pushbutton_import.
-function pushbutton_import_Callback(hObject, eventdata, handles)
-% hObject handle to pushbutton_import (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-ctFile = get(handles.edit_ctPath,'String');
-maskFiles = get(handles.listbox_maskPaths,'String');
-
-if isempty(ctFile) || isempty(maskFiles)
- errordlg('Please sepecify a CT and at least one mask!');
-end
-
-convertHU = get(handles.checkbox_huConvert,'Value');
-
-if convertHU
- [ct,cst] = matRad_importPatient(ctFile,maskFiles,get(handles.edit_hlut,'String'));
-else
- [ct,cst] = matRad_importPatient(ctFile,maskFiles);
-end
-
-cst = showCheckDialog(cst);
-
-assignin('base', 'ct', ct);
-assignin('base', 'cst', cst);
-
-delete(handles.figure_importDialog);
-
-
-% --- Executes on button press in pushbutton_cancel.
-function pushbutton_cancel_Callback(hObject, eventdata, handles)
-% hObject handle to pushbutton_cancel (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-delete(handles.figure_importDialog);
-
-
-% --- Executes on button press in pushbutton_addMaskFolders.
-function pushbutton_addMaskFolders_Callback(hObject, eventdata, handles)
-% hObject handle to pushbutton_addMaskFolders (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-importMaskPath = uigetdir('./', 'Choose the folder containing binary mask files...');
-importMaskPath = [importMaskPath filesep];
-if ~isempty(importMaskPath)
- entries = get(handles.listbox_maskPaths,'String');
- newEntries = [entries cellstr(importMaskPath)];
- set(handles.listbox_maskPaths,'String',newEntries);
- % Update handles structure
- guidata(hObject, handles);
-end
-
-% --- Executes on key press with focus on listbox_maskPaths and none of its controls.
-function listbox_maskPaths_KeyPressFcn(hObject, eventdata, handles)
-% hObject handle to listbox_maskPaths (see GCBO)
-% eventdata structure with the following fields (see MATLAB.UI.CONTROL.UICONTROL)
-% Key: name of the key that was pressed, in lower case
-% Character: character interpretation of the key(s) that was pressed
-% Modifier: name(s) of the modifier key(s) (i.e., control, shift) pressed
-% handles structure with handles and user data (see GUIDATA)
-if isequal(eventdata.Key,'delete') || isequal(eventdata.Key,'backspace')
- selectIndex = get(hObject,'value');
- entries = get(hObject,'String');
- if numel(entries) == 0
- return;
- end
- entries(selectIndex) = [];
- if selectIndex > numel(entries) && selectIndex > 1
- selectIndex = selectIndex - 1;
- end
- set(hObject,'String',entries,'value',selectIndex);
-end
-
-% --- Creates a Dialog for the final adaptations to VOIs and CT conversion
-function cst = showCheckDialog(cst)
-
-handle = dialog('Position', [100 100 400 250],'WindowStyle','modal','Name','Confirm Segmentations');
-
-%Create Table
-hTable = uitable('Parent',handle,'Units','normal','Position',[0.1 0.2 0.8 0.8]);
-hTable.Data = cst(:,2:3);
-hTable.ColumnName = {'Name','Type'};
-hTable.ColumnWidth = {150,'auto'};
-hTable.RowName = [];
-hTable.ColumnEditable = [true true];
-hTable.ColumnFormat = {'char',{'TARGET', 'OAR', 'IGNORED'}};
-
-%Create Button
-hButton = uicontrol(handle,'Style','pushbutton','String','Confirm','Units','normal','Position',[0.7 0.05 0.2 0.1],'Callback','uiresume(gcbf)');%{@pushbutton_confirm_vois_callback});
-try
- uiwait(handle);
- cst(:,2:3) = hTable.Data(:,:);
-catch
- warning('Closed checkdialog without confirmation! Using default cst information!');
-end
-delete(handle);
-
-
-% --- Executes on button press in checkbox_huConvert.
-function checkbox_huConvert_Callback(hObject, eventdata, handles)
-% hObject handle to checkbox_huConvert (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of checkbox_huConvert
-
-checked = get(hObject,'Value');
-
-if checked
- fieldState = 'on';
-else
- fieldState = 'off';
-end
-
-
-set(handles.edit_hlut,'Enable',fieldState);
-set(handles.pushbutton_hlutFile,'Enable',fieldState);
-
-
-function edit_hlut_Callback(hObject, eventdata, handles)
-% hObject handle to edit_hlut (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of edit_hlut as text
-% str2double(get(hObject,'String')) returns contents of edit_hlut as a double
-
-
-% --- Executes during object creation, after setting all properties.
-function edit_hlut_CreateFcn(hObject, eventdata, handles)
-% hObject handle to edit_hlut (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on button press in pushbutton_hlutFile.
-function pushbutton_hlutFile_Callback(hObject, eventdata, handles)
-% hObject handle to pushbutton_hlutFile (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-[importHLUTFile,importHLUTPath,~] = uigetfile({'*.hlut', 'matRad HLUT-Files'}, 'Choose the HLUT file...');
-if importHLUTFile ~= 0
- set(handles.edit_hlut,'String',fullfile(importHLUTPath,importHLUTFile));
- % Update handles structure
- guidata(hObject, handles);
-end
diff --git a/MCsquare/bin/Scanners/matRad_water/HU_Density_Conversion.txt b/MCsquare/bin/Scanners/matRad_water/HU_Density_Conversion.txt
deleted file mode 100644
index 5314d3ad9..000000000
--- a/MCsquare/bin/Scanners/matRad_water/HU_Density_Conversion.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-# ===================
-# HU density g/cm3
-# ===================
--1050 0.0001
--999 0.001
-0 1.000
-1000 2.000
-1500 4.000
diff --git a/MCsquare/matRad_readMhd.m b/MCsquare/matRad_readMhd.m
deleted file mode 100644
index 93dd60c40..000000000
--- a/MCsquare/matRad_readMhd.m
+++ /dev/null
@@ -1,56 +0,0 @@
-function cube = matRad_readMhd(folder,filename)
-% matRad mhd file reader
-%
-% call
-% cube = matRad_readMhd(folder,filename)
-%
-% input
-% folder: folder where the *raw and *mhd file are located
-% filename: filename
-%
-% output
-% cube: 3D array
-%
-% References
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-%% read header
-headerFileHandle = fopen([folder filesep filename],'r');
-
-s = textscan(headerFileHandle, '%s', 'delimiter', '\n');
-
-% read dimensions
-idx = find(~cellfun(@isempty,strfind(s{1}, 'DimSize')),1,'first');
-dimensions = cell2mat(textscan(s{1}{idx},'DimSize = %f %f %f'));
-
-% read filename of data
-idx = find(~cellfun(@isempty,strfind(s{1}, 'ElementDataFile')),1,'first');
-tmp = textscan(s{1}{idx},'ElementDataFile = %s');
-dataFilename = cell2mat(tmp{1});
-
-% get data type
-idx = find(~cellfun(@isempty,strfind(s{1}, 'ElementType')),1,'first');
-tmp = textscan(s{1}{idx},'ElementType = MET_%s');
-type = lower(cell2mat(tmp{1}));
-
-fclose(headerFileHandle);
-
-%% read data
-dataFileHandle = fopen([folder filesep dataFilename],'r');
-cube = reshape(fread(dataFileHandle,inf,type),dimensions);
-cube = permute(cube,[2 1 3]);
-cube = flip(cube,2);
-fclose(dataFileHandle);
diff --git a/MCsquare/matRad_writeMCsquareinputAllFiles.m b/MCsquare/matRad_writeMCsquareinputAllFiles.m
deleted file mode 100644
index 19b828c1a..000000000
--- a/MCsquare/matRad_writeMCsquareinputAllFiles.m
+++ /dev/null
@@ -1,150 +0,0 @@
-function matRad_writeMCsquareinputAllFiles(filename,MCsquareConfig,stf)
-% generate input files for MCsquare dose calcualtion from matRad
-%
-% call
-% matRad_writeMCsquareinputAllFiles(filename,MCsquareConfig,stf)
-%
-% input
-% filename: filename
-% MCsquareConfig: matRad MCsquare configuration
-% stf: matRad steering information struct
-%
-% output
-% -
-%
-% References
-% [1] https://openreggui.org/git/open/REGGUI/blob/master/functions/io/convert_Plan_PBS.m
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-%% write overall configuration file
-fileHandle = fopen(filename,'w');
-MCsquareConfig.write(fileHandle);
-fclose(fileHandle);
-
-%% prepare steering file writing
-numOfFields = length(stf);
-if MCsquareConfig.Beamlet_Mode
- totalMetersetWeightOfAllFields = 1;
-else
- totalMetersetWeightOfFields = NaN*ones(numOfFields,1);
- for i = 1:numOfFields
- totalMetersetWeightOfFields(i) = sum([stf(i).energyLayer.numOfPrimaries]);
- end
- totalMetersetWeightOfAllFields = sum(totalMetersetWeightOfFields);
-end
-
-%% write steering file
-
-fileHandle = fopen(MCsquareConfig.BDL_Plan_File,'w');
-
-fprintf(fileHandle,'#TREATMENT-PLAN-DESCRIPTION\n');
-fprintf(fileHandle,'#PlanName\n');
-fprintf(fileHandle,'matRad_bixel\n');
-fprintf(fileHandle,'#NumberOfFractions\n');
-fprintf(fileHandle,'1\n');
-fprintf(fileHandle,'##FractionID\n');
-fprintf(fileHandle,'1\n');
-fprintf(fileHandle,'##NumberOfFields\n');
-fprintf(fileHandle,[num2str(numOfFields) '\n']);
-for i = 1:numOfFields
- fprintf(fileHandle,'###FieldsID\n');
- fprintf(fileHandle,[num2str(i) '\n']);
-end
-fprintf(fileHandle,'\n#TotalMetersetWeightOfAllFields\n');
-fprintf(fileHandle,[num2str(totalMetersetWeightOfAllFields) '\n']);
-
-for i = 1:numOfFields
- fprintf(fileHandle,'\n#FIELD-DESCRIPTION\n');
- fprintf(fileHandle,'###FieldID\n');
- fprintf(fileHandle,[num2str(i) '\n']);
- fprintf(fileHandle,'###FinalCumulativeMeterSetWeight\n');
- if MCsquareConfig.Beamlet_Mode
- finalCumulativeMeterSetWeight = 1/numOfFields;
- else
- finalCumulativeMeterSetWeight = totalMetersetWeightOfFields(i);
- end
- fprintf(fileHandle,[num2str(finalCumulativeMeterSetWeight) '\n']);
- fprintf(fileHandle,'###GantryAngle\n');
- fprintf(fileHandle,[num2str(stf(i).gantryAngle) '\n']);
- fprintf(fileHandle,'###PatientSupportAngle\n');
- fprintf(fileHandle,[num2str(stf(i).couchAngle) '\n']);
- fprintf(fileHandle,'###IsocenterPosition\n');
- fprintf(fileHandle,[num2str(stf(i).isoCenter) '\n']);
- fprintf(fileHandle,'###NumberOfControlPoints\n');
- numOfEnergies = numel(stf(i).energies);
- fprintf(fileHandle,[num2str(numOfEnergies) '\n']);
-
- metersetOffset = 0;
- fprintf(fileHandle,'\n#SPOTS-DESCRIPTION\n');
- for j = 1:numOfEnergies
- fprintf(fileHandle,'####ControlPointIndex\n');
- fprintf(fileHandle,[num2str(j) '\n']);
- fprintf(fileHandle,'####SpotTunnedID\n');
- fprintf(fileHandle,['1\n']);
- fprintf(fileHandle,'####CumulativeMetersetWeight\n');
- if MCsquareConfig.Beamlet_Mode
- cumulativeMetersetWeight = j/numOfEnergies * 1/numOfFields;
- else
- cumulativeMetersetWeight = metersetOffset + sum([stf(i).energyLayer(j).numOfPrimaries]);
- metersetOffset = cumulativeMetersetWeight;
- end
- fprintf(fileHandle,[num2str(cumulativeMetersetWeight) '\n']);
- fprintf(fileHandle,'####Energy (MeV)\n');
- fprintf(fileHandle,[num2str(stf(i).energies(j)) '\n']);
- fprintf(fileHandle,'####NbOfScannedSpots\n');
- numOfSpots = size(stf(i).energyLayer(j).targetPoints,1);
- fprintf(fileHandle,[num2str(numOfSpots) '\n']);
- fprintf(fileHandle,'####X Y Weight\n');
- for k = 1:numOfSpots
- if MCsquareConfig.Beamlet_Mode
- n = stf(i).energyLayer(j).numOfPrimaries(k);
- else
- n = stf(i).energyLayer(j).numOfPrimaries(k) / mcSquare_magicFudge(stf(i).energies(j));
- end
- fprintf(fileHandle,[num2str(stf(i).energyLayer(j).targetPoints(k,:)) ' ' num2str(n) '\n']);
- end
- end
-end
-
-fclose(fileHandle);
-
-end
-
-function gain = mcSquare_magicFudge(energy)
-% mcSquare will scale the spot intensities in
-% https://gitlab.com/openmcsquare/MCsquare/blob/master/src/data_beam_model.c#L906
-% by this factor so we need to divide up front to make things work. The
-% original code can be found at https://gitlab.com/openmcsquare/MCsquare/blob/master/src/compute_beam_model.c#L16
-
-K = 35.87; % in eV (other value 34.23 ?)
-
-% // Air stopping power (fit ICRU) multiplied by air density
-SP = (9.6139e-9*energy^4 - 7.0508e-6*energy^3 + 2.0028e-3*energy^2 - 2.7615e-1*energy + 2.0082e1) * 1.20479E-3 * 1E6; % // in eV / cm
-
-% // Temp & Pressure correction
-PTP = 1.0;
-
-% // MU calibration (1 MU = 3 nC/cm)
-% // 1cm de gap effectif
-C = 3.0E-9; % // in C / cm
-
-% // Gain: 1eV = 1.602176E-19 J
-gain = (C*K) / (SP*PTP*1.602176E-19);
-
-% divide by 1e7 to not get tiny numbers...
-gain = gain/1e7;
-
-end
\ No newline at end of file
diff --git a/MCsquare/matRad_writeMhd.m b/MCsquare/matRad_writeMhd.m
deleted file mode 100644
index 69cb121a4..000000000
--- a/MCsquare/matRad_writeMhd.m
+++ /dev/null
@@ -1,47 +0,0 @@
-function matRad_writeMhd(cube,resolution,filename)
-
-
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2020 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% write header file
-fileHandle = fopen(filename,'w');
-
-fprintf(fileHandle,'ObjectType = Image\n');
-fprintf(fileHandle,'NDims = 3\n');
-fprintf(fileHandle,'BinaryData = True\n');
-fprintf(fileHandle,'BinaryDataByteOrderMSB = False\n');
-fprintf(fileHandle,'CompressedData = False\n');
-fprintf(fileHandle,'TransformMatrix = 1 0 0 0 1 0 0 0 1\n');
-fprintf(fileHandle,'Offset = 0 0 0\n');
-fprintf(fileHandle,'CenterOfRotation = 0 0 0\n');
-fprintf(fileHandle,'AnatomicalOrientation = RAI\n');
-fprintf(fileHandle,'ElementSpacing = %f %f %f\n',resolution);
-fprintf(fileHandle,'DimSize = %d %d %d\n',size(cube,2),size(cube,1),size(cube,3));
-fprintf(fileHandle,'ElementType = MET_DOUBLE\n');
-filenameRaw = [filename(1:end-4) '.raw'];
-fprintf(fileHandle,'ElementDataFile = %s\n',filenameRaw);
-
-fclose(fileHandle);
-
-%% write data file
-dataFileHandle = fopen(filenameRaw,'w');
-
-cube = flip(cube,2);
-cube = permute(cube,[2 1 3]);
-
-fwrite(dataFileHandle,cube(:),'double');
-fclose(dataFileHandle);
diff --git a/MatRad_Config.m b/MatRad_Config.m
deleted file mode 100644
index e2020cb5e..000000000
--- a/MatRad_Config.m
+++ /dev/null
@@ -1,312 +0,0 @@
-classdef MatRad_Config < handle
-% MatRad_Config MatRad Configuration class
-% This class is used globally through Matlab to handle default values and
-% logging and is declared as global matRad_cfg.
-% Usage:
-% matRad_cfg = MatRad_Config.instance();
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
- properties
-
- %Logging
- logLevel = 3; %1 = only Errors, 2 = with Warnings, 3 = Info output, 4 = deprecation warnings, 5 = debug information
- keepLog = false; %Stores the full log in memory
- writeLog = false; %Writes the log to a file on-the-fly
-
- %Default Properties
- propDoseCalc;
- propOpt;
- propMC;
- propStf;
-
- %Disable GUI
- disableGUI = false;
- end
-
- properties (SetAccess = private)
- messageLog = {};
- logFileHandle;
-
- %For storing the Environment & its version
- env;
- envVersion;
- isOctave; %Helper bool to check for Octave
- isMatlab; %Helper bool to check for Matlab
- end
-
- properties (SetAccess = private)
- matRadRoot;
- end
-
- methods (Access = private)
- function obj = MatRad_Config()
- %MatRad_Config Constructs an instance of this class.
- % The configuration is implemented as a singleton and used globally
- % Therefore its constructor is private
- % For instantiation, use the static MatRad_Config.instance();
-
- %Set Path
- obj.matRadRoot = fileparts(mfilename('fullpath'));
- addpath(genpath(pwd));
-
- %Set Version
- obj.getEnvironment();
-
- %Just to catch people messing with the properties in the file
- if ~isempty(obj.writeLog) && obj.writeLog
- logFile = [matRadRoot filesep 'matRad.log'];
- obj.logFileHandle = fopen(logFile,'a');
- end
-
- %Call the reset function for remaining inatialization
- obj.reset();
- end
-
- function displayToConsole(obj,type,formatSpec,varargin)
- %displayToConsole lowest-level logging function for matRad.
- % Display to console will be called from the public wrapper
- % functions dispError, dispWarning, dispInfo, dispDebug
- %
- % input
- % type: type of the log information.
- % Needs to be one of 'error', 'warning', 'info' or 'debug'.
- % formatSpec: string to print using format specifications similar to fprintf
- % varargin: variables according to formatSpec
-
- if nargin < 4
- forwardArgs = {formatSpec};
- else
- forwardArgs = [{formatSpec},varargin(:)'];
- end
-
- if obj.keepLog
- obj.messageLog{end+1,1} = upper(type);
- obj.messageLog{end,2} = sprintf(forwardArgs{:});
- end
-
- switch type
- case{'info'}
- if obj.logLevel >= 3
- fprintf(forwardArgs{:});
- end
- case{'debug'}
- if obj.logLevel >= 5
- forwardArgs{1} = ['DEBUG: ' forwardArgs{1}];
- fprintf(forwardArgs{:});
- end
- case{'dep'}
- if obj.logLevel >= 4
- forwardArgs{1} = ['DEPRECATION WARNING: ' forwardArgs{1}];
- warning(forwardArgs{:});
- end
- case{'warning'}
- if obj.logLevel >= 2
- warning(forwardArgs{:});
- end
- case {'error'}
- if obj.logLevel >= 1
- %We create an error structure to later clean the
- %stack trace from the last two files/lines (i.e.,
- %this function / file)
-
- err.message = sprintf(forwardArgs{:});
- err.identifier = 'matRad:Error';
- err.stack = dbstack(2);
- error(err);
-
- end
- otherwise
- error('Log type %s not defined!',type);
- end
-
- if obj.writeLog
- fprintf(obj.logFileHandle,forwardArgs{:});
- end
- end
-
- end
-
- methods
- function reset(obj)
- %Set all default properties for matRad's computations
- obj.setDefaultProperties();
- end
-
- function setDefaultProperties(obj)
- %setDefaultProperties set matRad's default computation
- % properties
- % input
-
- obj.propStf.defaultLongitudinalSpotSpacing = 2;
- obj.propStf.defaultAddMargin = true; %expand target for beamlet finding
-
- obj.propDoseCalc.defaultResolution = struct('x',3,'y',3,'z',3); %[mm]
- obj.propDoseCalc.defaultLateralCutOff = 0.995; %[rel.]
- obj.propDoseCalc.defaultGeometricCutOff = 50; %[mm]
- obj.propDoseCalc.defaultKernelCutOff = Inf; %[mm]
- obj.propDoseCalc.defaultSsdDensityThreshold = 0.05; %[rel.]
- obj.propDoseCalc.defaultUseGivenEqDensityCube = false; %Use the given density cube ct.cube and omit conversion from cubeHU.
- obj.propDoseCalc.defaultIgnoreOutsideDensities = true; %Ignore densities outside of cst contours
- obj.propDoseCalc.defaultUseCustomPrimaryPhotonFluence = false; %Use a custom primary photon fluence
-
- obj.propOpt.defaultMaxIter = 500;
-
- obj.propMC.ompMC_defaultHistories = 1e6;
- obj.propMC.ompMC_defaultOutputVariance = false;
- obj.propMC.MCsquare_defaultHistories = 1e6;
- obj.propMC.direct_defaultHistories = 2e4;
-
- obj.disableGUI = false;
- end
-
- %%For testing
- function setDefaultPropertiesForTesting(obj)
- %setDefaultPropertiesForTesting sets matRad's default
- %properties during testing to reduce computational load
-
- obj.logLevel = 1; %Omit output except errors
-
- obj.propStf.defaultLongitudinalSpotSpacing = 20;
- obj.propStf.defaultAddMargin = true; %expand target for beamlet finding
-
- obj.propDoseCalc.defaultResolution = struct('x',5,'y',6,'z',7); %[mm]
- obj.propDoseCalc.defaultGeometricCutOff = 20;
- obj.propDoseCalc.defaultLateralCutOff = 0.8;
- obj.propDoseCalc.defaultKernelCutOff = 20; %[mm]
- obj.propDoseCalc.defaultSsdDensityThreshold = 0.05;
- obj.propDoseCalc.defaultUseGivenEqDensityCube = false; %Use the given density cube ct.cube and omit conversion from cubeHU.
- obj.propDoseCalc.defaultIgnoreOutsideDensities = true;
- obj.propDoseCalc.defaultUseCustomPrimaryPhotonFluence = false; %Use a custom primary photon fluence
-
- obj.propOpt.defaultMaxIter = 10;
-
- obj.propMC.ompMC_defaultHistories = 100;
- obj.propMC.ompMC_defaultOutputVariance = true;
- obj.propMC.MCsquare_defaultHistories = 100;
- obj.propMC.direct_defaultHistories = 100;
-
- obj.disableGUI = true;
- end
-
- function dispDebug(obj,formatSpec,varargin)
- %dispDebug print debug messages (log level >= 4)
- % input
- % formatSpec: string to print using format specifications similar to fprintf
- % varargin: variables according to formatSpec
-
- obj.displayToConsole('debug',formatSpec,varargin{:});
- end
-
- function dispInfo(obj,formatSpec,varargin)
- %dispInfo print information console output (log level >= 3)
- % input
- % formatSpec: string to print using format specifications similar to fprintf
- % varargin: variables according to formatSpec
- obj.displayToConsole('info',formatSpec,varargin{:});
- end
-
- function dispError(obj,formatSpec,varargin)
- %dispError print errors (forwarded to "error" that will stop the program) (log level >= 1)
- % input
- % formatSpec: string to print using format specifications
- % similar to 'error'
- % varargin: variables according to formatSpec
- obj.displayToConsole('error',formatSpec,varargin{:});
- end
-
- function dispWarning(obj,formatSpec,varargin)
- %dispError print warning (forwarded to 'warning') (log level >= 2)
- % input
- % formatSpec: string to print using format specifications
- % similar to 'warning'
- % varargin: variables according to formatSpec
- obj.displayToConsole('warning',formatSpec,varargin{:});
- end
-
- function dispDeprecationWarning(obj,formatSpec,varargin)
- %dispDeprecationWarning wrapper for deprecation warnings forwarded to displayToConsole
- obj.displayToConsole('dep',formatSpec,varargin{:});
- end
-
- function obj = writeLogToFile(obj,filename)
- %writeLogToFile writes the log kept in MatRad_Config to file.
- % Note that the switch keepLog must be enabled for MatRad_Config to store all logging output.
-
- singleString = '%s: %s\n';
- fID = fopen(filename,'w');
- fprintf(fID,repmat(singleString,1,size(obj.messageLog,1)),obj.messageLog{:});
- fclose(fID);
- end
-
- function set.logLevel(obj,newLogLevel)
- %%Property set methods for logLevel
- minLevel = 1;
- maxLevel = 5;
- if newLogLevel >= minLevel && newLogLevel <= maxLevel
- obj.logLevel = newLogLevel;
- else
- obj.dispError('Invalid log level. Value must be between %d and %d',minLevel,maxLevel);
- end
- end
-
- function set.writeLog(obj,writeLog)
- if writeLog
- logFile = [obj.matRadRoot filesep 'matRad.log'];
- obj.logFileHandle = fopen(logFile,'a');
- obj.writeLog = true;
- else
- fclose(obj.logFileHandle);
- obj.writeLog = false;
- end
- end
-
- function getEnvironment(obj)
- % getEnvironment function to get the software environment
- % matRad is running on
-
- obj.isOctave = exist('OCTAVE_VERSION', 'builtin') ~= 0;
- obj.isMatlab = ~obj.isOctave;
-
- if obj.isOctave
- obj.env = 'OCTAVE';
- obj.envVersion = OCTAVE_VERSION;
- else
- obj.env = 'MATLAB';
- vData = ver(obj.env);
- obj.envVersion = vData.Version;
-
- end
- end
- end
-
- methods(Static)
-
- function obj = instance()
- %instance creates a singleton instance of MatRad_Config
- % In MatRad_Config, the constructor is private to make sure only on global instance exists.
- % Call this static functino to get or create an instance of the matRad configuration class
- persistent uniqueInstance;
-
- if isempty(uniqueInstance)
- obj = MatRad_Config();
- uniqueInstance = obj;
- else
- obj = uniqueInstance;
- end
- end
- end
-end
-
diff --git a/PITCHME.md b/PITCHME.md
deleted file mode 100644
index 7cf6ee4a2..000000000
--- a/PITCHME.md
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-### A radiation treatment planning software for intensity-modulated photon, proton and carbon ion therapy.
----
-# Free Software for reasearch and education
----
-## matRad provides functionalites for
-- DICOM import
-- ray tracing
-- photon dose calculation
-- proton & carbon dose calculation
-- inverse planning
-- multileaf collimator sequencing
-- treatment plan visualization and evaluation
----
-### Graphical User Interface
-
----
-## Code Example
-```matlab
-load 'LIVER.mat'
-
-pln.bixelWidth = 5; % [mm]
-pln.gantryAngles = [300]; % [�]
-pln.couchAngles = [0]; % [�]
-pln.radiationMode = 'carbon';
-pln.bioOptimization = 'LEMIV_RBExD';
-
-stf = matRad_generateStf(ct,cst,pln);
-
-dij = matRad_calcParticleDose(ct,stf,pln,cst);
-
-resultGUI = matRad_fluenceOptimization(dij,cst,pln);
-
-matRadGUI
-```
-@[1](import a open source liver patient)
-@[3-7](define your treatment plan)
-@[9](generate beam and ray geometry)
-@[11](dose calculation - obtain dose influence matrix)
-@[13](inverse planning for IMPT)
-@[15](start GUI for visualization of result)
----
-
----
-## Performance
-
----
-## matRad webinar
-
----
-## Get in touch
-### https://matRad.org/
-### matRad@dkfz.de
-
----
-## Code Example
-
-```matlab
-beamIx = 2;
-stf(beamIx).isoCenter(1) = stf(beamIx).isoCenter(1) + 3;
-
-resultGUI_isoShift = matRad_calcDoseDirect(ct,stf,pln,cst,w);
-
-slice = round(stf.isoCenter(3)./ct.resolution.z);
-
-DoseDiff = resultGUI(:,:,slice) - resultGUI_isoShift(:,:,slice);
-
-figure, imagesc(DoseDiff);colorbar
-```
-@[1-2](Lets simulate a lateral displacement in x of the second beam)
-@[4](recalculate dose using previously optimized beamlet weights)
-@[6](determine axial iso center slice)
-@[8](calculate dose difference)
-@[10](plot dose difference slice)
----
-
-## More Code Examples on
-## https://github.com/e0404/matRad/tree/master/examples
----
\ No newline at end of file
diff --git a/PITCHME.yaml b/PITCHME.yaml
deleted file mode 100644
index 14235c8fe..000000000
--- a/PITCHME.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-theme : white
-logo : https://github.com/e0404/matRad/wiki/images/DKFZ_Logo1.png
-footnote : German Cancer Research Center - Department of Medical Physics in Radiation Oncology - Optimization algorithms
diff --git a/README.md b/README.md
index 8246e863a..b59b94f3a 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,8 @@
[](https://github.com/e0404/matRad/releases)
[](https://github.com/e0404/matRad/releases)
[](https://github.com/e0404/matRad/graphs/contributors)
-[](https://gitpitch.com/e0404/matRad/master)
[](https://github.com/e0404/matRad/actions/workflows/tests.yml)
-[](https://dev.azure.com/e0404/matRad)
DOIs:
- General DOI: [](https://doi.org/10.5281/zenodo.3879615)
@@ -19,7 +17,9 @@ matRad is an open source treatment planning system for radiation therapy written
More information can be found on the project page at ; a wiki documentation is under constant development at .
# Getting Started
+If you want to quickly run matRad, start with the Quick Start below. Some information on the structure of matRad for more sustainable use is given afterwards.
+## Quick Start
It’s the first time you want to use matRad?
First, get a local copy of matRad by download or git cloning. Having done that, we recommend you navigate into the folder in Matlab and execute
@@ -58,8 +58,24 @@ The most time consuming but also most educational approach to matRad.
When in the main matRad folder, navigate to the folder *examples*. Open one of the examples given there. Execute it section by section. Move on to the next example afterwards.
-# Need help?
+## Advanced information for new users
+### Folder Structure
+#### Core Source Code
+Most of the source code of matRad is located in the "matRad" subfolder. Within the first level of matRad, you find the functions handling the basic workflow steps. These functions have simple interfaces relying on matRad's main data structures ct, cst, stf, dij, resultGUI, and pln.
+Additionally, it contains MatRad_Config.m which is a singleton class implementation to handle global configuration of matRad. Check out the infos further below.
+
+We try to keep the main workflow functions as consistent as possible, while the fine-grained implementation in the subfolders within matRad/* may undergo larger changes.
+#### User Directory
+By default, matRad adds the "userdata" folder to the path. It is the place to put your custom scripts, machine data, imported patients etc. Just follow the README files in the folders. Contents of this folder are added to the .gitignore and will thus be ignored during your development efforts, keeping your repository clean.
+#### Third-Party & Submodules
+Our ThirdParty-Tools used in matRad are stored in the thirdParty folder including licenses. Submodules contains references to used git repositories, and you might recognize that some dependencies appear both in submodules and thirdParty. This is mainly to maintain operation if the code is downloaded (and not cloned), and also helps us to maintain the build process of mex files built from source in the submodules (and then added to ThirdParty).
+#### Tests
+The "test" folder contains xUnit-Style tests based on the MOxUnit framework. You can run those tests by running matRad_runTests from the root directory. Check the README file within the test folder for more information.
+### MatRad_Config / matRad_cfg
+matRad maintains its global configuration, including some default parameters, as well as a logging mechanism with different levels, in the MatRad_Config.m class serving as a "singleton" throughout matRad. You will see many functions using a call like `matRad_cfg = MatRad_Config.instance();`, which will get you the global configuration anywhere in the code or in the command window. Alternatively, `matRad_rc` will return matRad_cfg as well.
+
+# Need help?
If you encounter problems with matRad, please consider the following guidelines **before** submitting issues on our github page.
* Check you are using the newest version of matRad.
@@ -126,6 +142,6 @@ matRad is distributed in the hope that it will be useful, but WITHOUT ANY WARRAN
Please note that we treat the compilation of matRad and Ipopt as separate and independent works (or modules, components, programs). Therefore, to the best of our understanding, the compilation of matRad and Ipopt is subject to the "Mere Aggregation" exception in section 5 of the GNU v3 and the exemption from "Contributions" in section 1. b) ii) of the EPL v1.0. Should this interpretation turn out to be not in compliance with the applicable laws in force, we have provided you with an additional permission under GNU GPL version 3 section 7 to allow you to use the work resulting from combining matRad with Ipopt.
-You will receive a copy of the GPL v3 and a copy of the EPL v1.0 in the file LICENSES.txt along with the compilation. If not, see http://www.gnu.org/licenses/ and/or http://opensource.org/licenses/EPL-1.0/.
+You will receive a copy of the GPL v3 and a copy of the EPL v1.0 in the file LICENSE.md along with the compilation. If not, see http://www.gnu.org/licenses/ and/or http://opensource.org/licenses/EPL-1.0/.
---
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 25dad3302..000000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-pool:
- vmImage: 'ubuntu-latest'
-
-steps:
-- task: CmdLine@2
- inputs:
- script: 'sudo chmod +x MCsquare/bin/MCsquare_linux'
-- task: InstallMATLAB@0
-- task: RunMATLABCommand@0
- inputs:
- command: 'cd unitTest; matRad_runTests;'
\ No newline at end of file
diff --git a/basedata/carbon_Generic.mat b/basedata/carbon_Generic.mat
deleted file mode 100644
index 4f5f93f3e..000000000
Binary files a/basedata/carbon_Generic.mat and /dev/null differ
diff --git a/dicom/@matRad_DicomExporter/matRad_exportDicomRTPlan.m b/dicom/@matRad_DicomExporter/matRad_exportDicomRTPlan.m
deleted file mode 100644
index 561712ac8..000000000
--- a/dicom/@matRad_DicomExporter/matRad_exportDicomRTPlan.m
+++ /dev/null
@@ -1,37 +0,0 @@
-function obj = matRad_exportDicomRTPlan(obj)
-% matRad function to export resultGUI to dicom.
-%
-% call
-% matRad_exportDicomRTDoses(resultGUI,ct,pln,fieldnames)
-%
-% input
-% resultGUI: matRad resultGUI struct with different beams. Note that
-% the summation (called plan) of the beams is named
-% without subscripts, e.g. physical_Dose.
-% ct: matRad ct struct
-%
-% output
-% resultGUI: matRad resultGUI struct with different beams. Note that
-% the summation (called plan) of the beams is named
-% without subscripts, e.g. physical_Dose.
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-matRad_cfg = MatRad_Config.instance();
-matRad_cfg.dispWarning('RTPlan export is not yet implemented...\n');
-
-end
diff --git a/dicom/DKFZ_Logo.png b/dicom/DKFZ_Logo.png
deleted file mode 100644
index 05a4ba5ea..000000000
Binary files a/dicom/DKFZ_Logo.png and /dev/null differ
diff --git a/dicom/matRad_createCst.m b/dicom/matRad_createCst.m
deleted file mode 100644
index a961e6643..000000000
--- a/dicom/matRad_createCst.m
+++ /dev/null
@@ -1,83 +0,0 @@
-function cst = matRad_createCst(structures)
-% matRad function to create a cst struct upon dicom import
-%
-% call
-% cst = matRad_createCst(structures)
-%
-% input
-% structures: matlab struct containing information about rt structure
-% set (generated with matRad_importDicomRtss and
-% matRad_convRtssContours2Indices)
-%
-% output
-% cst: matRad cst struct
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-matRad_cfg = MatRad_Config.instance();
-
-nStructures = size(structures,2);
-cst = cell(nStructures,6);
-
-%Create set of default colors
-defaultColors = colorcube(nStructures);
-
-for i = 1:size(structures,2)
- cst{i,1} = i - 1; % first organ has number 0
- cst{i,2} = structures(i).structName;
-
- if ~isempty(regexpi(cst{i,2},'tv')) || ...
- ~isempty(regexpi(cst{i,2},'target')) || ...
- ~isempty(regexpi(cst{i,2},'gtv')) || ...
- ~isempty(regexpi(cst{i,2},'ctv')) || ...
- ~isempty(regexpi(cst{i,2},'ptv')) || ...
- ~isempty(regexpi(cst{i,2},'boost')) || ...
- ~isempty(regexpi(cst{i,2},'tumor'))
-
- cst{i,3} = 'TARGET';
-
- cst{i,5}.Priority = 1;
-
- % default objectives for targets
- objective = DoseObjectives.matRad_SquaredDeviation;
- objective.penalty = 800;
- objective.parameters = {30}; %Default reference Dose
- cst{i,6}{1} = struct(objective);
-
- else
-
- cst{i,3} = 'OAR';
-
- cst{i,5}.Priority = 2;
-
- cst{i,6} = []; % define no OAR dummy objcetives
-
- end
-
- cst{i,4}{1} = structures(i).indices;
-
- % set default parameter for biological planning
- cst{i,5}.alphaX = 0.1;
- cst{i,5}.betaX = 0.05;
- cst{i,5}.Visible = 1;
- if isfield(structures(i),'structColor') && ~isempty(structures(i).structColor)
- cst{i,5}.visibleColor = structures(i).structColor' ./ 255;
- else
- cst{i,5}.visibleColor = defaultColors(i,:);
- matRad_cfg.dispInfo('No color information for structure %d "%s". Assigned default color [%f %f %f]\n',i,cst{i,2},defaultColors(i,1),defaultColors(i,2),defaultColors(i,3));
- end
-end
diff --git a/dicom/matRad_importDicom.m b/dicom/matRad_importDicom.m
deleted file mode 100644
index ce64bd477..000000000
--- a/dicom/matRad_importDicom.m
+++ /dev/null
@@ -1,165 +0,0 @@
-function [ct, cst, pln, resultGUI] = matRad_importDicom( files, dicomMetaBool )
-% matRad wrapper function to import a predefined set of dicom files
-% into matRad's native data formats
-%
-% call
-% [ct, cst, pln, resultGUI] = matRad_importDicom( files )
-% [ct, cst, pln, resultGUI] = matRad_importDicom( files, dicomMetaBool )
-%
-% input
-% files: list of files to be imported (will contain cts and rt
-% structure set)
-% dicomMetaBool: (boolean, optional) import complete dicomInfo and
-% patientName
-%
-% output
-% ct: matRad ct struct
-% cst: matRad cst struct
-% pln: matRad plan struct
-% resultGUI: matRad result struct holding data for visualization in GUI
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-[env, ~] = matRad_getEnvironment();
-
-%%
-if ~exist('dicomMetaBool','var')
- dicomMetaBool = true;
-end
-
-%%
-h = waitbar(0,'Please wait...');
-%h.WindowStyle = 'Modal';
-steps = 2;
-
-%% import ct-cube
-waitbar(1 / steps)
-resolution.x = files.resx;
-resolution.y = files.resy;
-resolution.z = files.resz; % [mm] / lps coordinate system
-if files.useDoseGrid && isfield(files,'rtdose')
- % get grid from dose cube
- if verLessThan('matlab','9')
- doseInfo = dicominfo(files.rtdose{1,1});
- else
- doseInfo = dicominfo(files.rtdose{1,1},'UseDictionaryVR',true);
- end
- doseGrid{1} = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ...
- doseInfo.PixelSpacing(1) * double(0:doseInfo.Columns - 1);
- doseGrid{2} = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ...
- doseInfo.PixelSpacing(2) * double(0:doseInfo.Rows - 1);
- doseGrid{3} = doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector(:)';
-
- % get ct on grid
- ct = matRad_importDicomCt(files.ct, resolution, dicomMetaBool,doseGrid);
-
-else
- ct = matRad_importDicomCt(files.ct, resolution, dicomMetaBool);
-end
-
-if ~isempty(files.rtss)
-
- %% import structure data
- waitbar(2 / steps)
- structures = matRad_importDicomRtss(files.rtss{1},ct.dicomInfo);
- close(h)
-
- %% creating structure cube
- h = waitbar(0,'Please wait...');
- %h.WindowStyle = 'Modal';
- steps = numel(structures);
- for i = 1:numel(structures)
- % computations take place here
- waitbar(i / steps)
- fprintf('creating cube for %s volume...\n', structures(i).structName);
- structures(i).indices = matRad_convRtssContours2Indices(structures(i),ct);
- end
- fprintf('finished!\n');
- close(h)
-
- %% creating cst
- cst = matRad_createCst(structures);
-
-else
-
- cst = matRad_dummyCst(ct);
-
-end
-
-%% determine pln parameters
-if isfield(files,'rtplan')
- if ~(cellfun(@isempty,files.rtplan(1,:)))
- pln = matRad_importDicomRTPlan(ct, files.rtplan, dicomMetaBool);
- end
-end
-
-%% import stf
-if isfield(files,'rtplan')
- if ~(cellfun(@isempty,files.rtplan(1,:)))
- if (strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon'))
- %% import steering file
- % pln output because bixelWidth is determined via the stf
- [stf, pln] = matRad_importDicomSteeringParticles(ct, pln, files.rtplan);
- elseif strcmp(pln.radiationMode, 'photons') && isfield(pln.propStf,'collimation')
- % return correct angles in pln
- [stf, pln] = matRad_importDicomSteeringPhotons(pln);
- else
- warning('No support for DICOM import of steering information for this modality.');
- end
- end
-end
-
-%% import dose cube
-if isfield(files,'rtdose')
- % check if files.rtdose contains a path and is labeld as RTDose
- % only the first two elements are relevant for loading the rt dose
- if ~(cellfun(@isempty,files.rtdose(1,1:2)))
- fprintf('loading Dose files \n', structures(i).structName);
- % parse plan in order to scale dose cubes to a fraction based dose
- if exist('pln','var') && ~isempty(pln) && isfield(pln,'numOfFractions')
- resultGUI = matRad_importDicomRTDose(ct, files.rtdose, pln);
- else
- resultGUI = matRad_importDicomRTDose(ct, files.rtdose);
- end
- if size(resultGUI) == 0
- clear resultGUI;
- end
- end
-end
-
-%% put weight also into resultGUI
-if exist('stf','var') && exist('resultGUI','var')
- resultGUI.w = [];
- for i = 1:size(stf,2)
- resultGUI.w = [resultGUI.w; [stf(i).ray.weight]'];
- end
-end
-
-%% save ct, cst, pln, dose
-matRadFileName = [files.ct{1,3} '.mat']; % use default from dicom
-[FileName,PathName] = uiputfile('*','Save as...',matRadFileName);
-if ischar(FileName)
- % delete unnecessary variables
- switch env
- case 'MATLAB'
- clearvars -except ct cst pln stf resultGUI FileName PathName;
- case 'OCTAVE'
- clear -x ct cst pln stf resultGUI FileName PathName;
- end
- % save all except FileName and PathName
- save([PathName, FileName], '-regexp', '^(?!(FileName|PathName)$).','-v7.3');
-end
diff --git a/dicom/matRad_importDicomCt.m b/dicom/matRad_importDicomCt.m
deleted file mode 100644
index 5499452d6..000000000
--- a/dicom/matRad_importDicomCt.m
+++ /dev/null
@@ -1,253 +0,0 @@
-function ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool, grid, visBool)
-% matRad function to import dicom ct data
-%
-% call
-% ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool)
-% ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool, grid)
-% ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool, visBool)
-% ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool, grid, visBool)
-%
-% input
-% ctList: list of dicom ct files
-% resolution: resolution of the imported ct cube, i.e. this function
-% will interpolate to a different resolution if desired
-% dicomMetaBool: store complete dicom information if true
-% grid: optional: a priori grid specified for interpolation
-% visBool: optional: turn on/off visualization
-%
-% output
-% ct: matRad ct struct. Note that this 3D matlab array
-% contains water euqivalent electron denisities.
-% Hounsfield units are converted using a standard lookup
-% table in matRad_calcWaterEqD
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-matRad_cfg = MatRad_Config.instance();
-
-matRad_cfg.dispInfo('\nimporting ct-cube...');
-
-%% processing input variables
-if ~exist('visBool','var')
- visBool = 0;
-end
-
-% creation of ctInfo list
-numOfSlices = size(ctList,1);
-matRad_cfg.dispInfo('\ncreating info...')
-
-sliceThicknessStandard = true;
-for i = 1:numOfSlices
-
- if verLessThan('matlab','9')
- tmpDicomInfo = dicominfo(ctList{i,1});
- else
- tmpDicomInfo = dicominfo(ctList{i,1},'UseDictionaryVR',true);
- end
-
- % remember relevant dicom info - do not record everything as some tags
- % might not been defined for individual files
- ctInfo(i).PixelSpacing = tmpDicomInfo.PixelSpacing;
- ctInfo(i).ImagePositionPatient = tmpDicomInfo.ImagePositionPatient;
- ctInfo(i).SliceThickness = tmpDicomInfo.SliceThickness;
- ctInfo(i).ImageOrientationPatient = tmpDicomInfo.ImageOrientationPatient;
- ctInfo(i).PatientPosition = tmpDicomInfo.PatientPosition;
- ctInfo(i).Rows = tmpDicomInfo.Rows;
- ctInfo(i).Columns = tmpDicomInfo.Columns;
- ctInfo(i).Width = tmpDicomInfo.Width;
- ctInfo(i).Height = tmpDicomInfo.Height;
- ctInfo(i).RescaleSlope = tmpDicomInfo.RescaleSlope;
- ctInfo(i).RescaleIntercept = tmpDicomInfo.RescaleIntercept;
-
- %Problem due to some CT files using non-standard SpacingBetweenSlices
-
- if isempty(ctInfo(i).SliceThickness)
- %Print warning ocne
- if sliceThicknessStandard
- matRad_cfg.dispWarning('Non-standard use of SliceThickness Attribute (empty), trying to overwrite with SpacingBetweenSlices');
- sliceThicknessStandard = false;
- end
- ctInfo(i).SliceThickness = tmpDicomInfo.SpacingBetweenSlices;
- end
-
- if i == 1
- completeDicom = tmpDicomInfo;
- end
-
- matRad_progress(i,numOfSlices);
-end
-
-% adjusting sequence of slices (filenames may not be ordered propperly....
-% e.g. CT1.dcm, CT10.dcm, CT100zCoordList = [ctInfo.ImagePositionPatient(1,3)]';.dcm, CT101.dcm,...
-CoordList = [ctInfo.ImagePositionPatient]';
-[~, indexing] = sort(CoordList(:,3)); % get sortation from z-coordinates
-
-ctList = ctList(indexing);
-ctInfo = ctInfo(indexing);
-
-%% check data set for consistency
-if size(unique([ctInfo.PixelSpacing]','rows'),1) > 1
- matRad_cfg.dispError('Different pixel size in different CT slices');
-end
-
-coordsOfFirstPixel = [ctInfo.ImagePositionPatient];
-if numel(unique(coordsOfFirstPixel(1,:))) > 1 || numel(unique(coordsOfFirstPixel(2,:))) > 1
- matRad_cfg.dispError('Ct slices are not aligned');
-end
-if sum(diff(coordsOfFirstPixel(3,:))<=0) > 0
- matRad_cfg.dispError('Ct slices not monotonically increasing');
-end
-if numel(unique([ctInfo.Rows])) > 1 || numel(unique([ctInfo.Columns])) > 1
- matRad_cfg.dispError('Ct slice sizes inconsistent');
-end
-
-
-%% checking the patient position
-% As of now, the matRad treatment planning system is only valid for
-% patients in a supine position. Other orientations (e.g. prone, decubitus
-% left/right) are not supported.
-% Defined Terms:
-% HFP Head First-Prone (not supported)
-% HFS Head First-Supine (supported)
-% HFDR Head First-Decubitus Right (not supported)
-% HFDL Head First-Decubitus Left (not supported)
-% FFDR Feet First-Decubitus Right (not supported)
-% FFDL Feet First-Decubitus Left (not supported)
-% FFP Feet First-Prone (not supported)
-% FFS Feet First-Supine (supported)
-
-if isempty(regexp(ctInfo(1).PatientPosition,{'S','P'}, 'once'))
- matRad_cfg.dispError(['This Patient Position is not supported by matRad.'...
- ' As of now only ''HFS'' (Head First-Supine), ''FFS'''...
- ' (Feet First-Supine), '...
- '''HFP'' (Head First-Prone), and ''FFP'''...
- ' (Feet First-Prone) can be processed.'])
-end
-
-%% creation of ct-cube
-matRad_cfg.dispInfo('reading slices...')
-origCt = zeros(ctInfo(1).Height, ctInfo(1).Width, numOfSlices);
-for i = 1:numOfSlices
- currentFilename = ctList{i};
- [currentImage, map] = dicomread(currentFilename);
- origCt(:,:,i) = currentImage(:,:); % creation of the ct cube
-
- % draw current ct-slice
- if visBool
- if ~isempty(map)
- image(ind2rgb(uint8(63*currentImage/max(currentImage(:))),map));
- xlabel('x [voxelnumber]')
- ylabel('y [voxelnumber]')
- title(['Slice # ' int2str(i) ' of ' int2str(numOfSlices)])
- else
- image(ind2rgb(uint8(63*currentImage/max(currentImage(:))),bone));
- xlabel('x [voxelnumber]')
- ylabel('y [voxelnumber]')
- title(['Slice # ' int2str(i) ' of ' int2str(numOfSlices)])
- end
- axis equal tight;
- pause(0.1);
- end
- matRad_progress(i,numOfSlices);
-end
-
-%% correction if not lps-coordinate-system
-% when using the physical coordinates (ctInfo.ImagePositionPatient) to
-% arrange the slices in z-direction, there is no more need for mirroring
-% in the z-direction
-matRad_cfg.dispInfo('\nz-coordinates taken from ImagePositionPatient\n')
-
-% The x- & y-direction in lps-coordinates are specified in:
-% ImageOrientationPatient
-xDir = ctInfo(1).ImageOrientationPatient(1:3); % lps: [1;0;0]
-yDir = ctInfo(1).ImageOrientationPatient(4:6); % lps: [0;1;0]
-nonStandardDirection = false;
-
-% correct x- & y-direction
-%
-% if xDir(1) == 1 && xDir(2) == 0 && xDir(3) == 0
-% matRad_cfg.dispInfo('x-direction OK\n')
-% elseif xDir(1) == -1 && xDir(2) == 0 && xDir(3) == 0
-% matRad_cfg.dispInfo('\nMirroring x-direction...')
-% origCt = flip(origCt,1);
-% matRad_cfg.dispInfo('finished!\n')
-% else
-% nonStandardDirection = true;
-% end
-%
-% if yDir(1) == 0 && yDir(2) == 1 && yDir(3) == 0
-% matRad_cfg.dispInfo('y-direction OK\n')
-% elseif yDir(1) == 0 && yDir(2) == -1 && yDir(3) == 0
-% matRad_cfg.dispInfo('\nMirroring y-direction...')
-% origCt = flip(origCt,2);
-% matRad_cfg.dispInfo('finished!\n')
-% else
-% nonStandardDirection = true;
-% end
-
-if nonStandardDirection
- matRad_cfg.dispInfo(['Non-standard patient orientation.\n'...
- 'CT might not fit to contoured structures\n'])
-end
-
-%% interpolate cube
-matRad_cfg.dispInfo('\nInterpolating CT cube...');
-if exist('grid','var')
- ct = matRad_interpDicomCtCube(origCt, ctInfo, resolution, grid);
-else
- ct = matRad_interpDicomCtCube(origCt, ctInfo, resolution);
-end
-matRad_cfg.dispInfo('finished!\n');
-
-%% remember some parameters of original dicom
-ct.dicomInfo.PixelSpacing = ctInfo(1).PixelSpacing;
- tmp = [ctInfo.ImagePositionPatient];
-ct.dicomInfo.SlicePositions = tmp(3,:);
-ct.dicomInfo.SliceThickness = [ctInfo.SliceThickness];
-ct.dicomInfo.ImagePositionPatient = ctInfo(1).ImagePositionPatient;
-ct.dicomInfo.ImageOrientationPatient = ctInfo(1).ImageOrientationPatient;
-ct.dicomInfo.PatientPosition = ctInfo(1).PatientPosition;
-ct.dicomInfo.Width = ctInfo(1).Width;
-ct.dicomInfo.Height = ctInfo(1).Height;
-ct.dicomInfo.RescaleSlope = ctInfo(1).RescaleSlope;
-ct.dicomInfo.RescaleIntercept = ctInfo(1).RescaleIntercept;
-if isfield(completeDicom, 'Manufacturer')
-ct.dicomInfo.Manufacturer = completeDicom.Manufacturer;
-end
-if isfield(completeDicom, 'ManufacturerModelName')
-ct.dicomInfo.ManufacturerModelName = completeDicom.ManufacturerModelName;
-end
-if isfield(completeDicom, 'ConvolutionKernel')
-ct.dicomInfo.ConvolutionKernel = completeDicom.ConvolutionKernel;
-end
-
-% store patientName only if user wants to
-if isfield(completeDicom,'PatientName') && dicomMetaBool == true
- ct.dicomInfo.PatientName = completeDicom.PatientName;
-end
-if dicomMetaBool == true
- ct.dicomMeta = completeDicom;
-end
-
-ct.timeStamp = datestr(clock);
-
-% convert to Hounsfield units
-matRad_cfg.dispInfo('\nconversion of ct-Cube to Hounsfield units...');
-ct = matRad_calcHU(ct);
-matRad_cfg.dispInfo('finished!\n');
-
-end
diff --git a/dicom/matRad_importDicomGUI.fig b/dicom/matRad_importDicomGUI.fig
deleted file mode 100644
index 6d5cf0418..000000000
Binary files a/dicom/matRad_importDicomGUI.fig and /dev/null differ
diff --git a/dicom/matRad_importDicomGUI.m b/dicom/matRad_importDicomGUI.m
deleted file mode 100644
index 078ed32b6..000000000
--- a/dicom/matRad_importDicomGUI.m
+++ /dev/null
@@ -1,738 +0,0 @@
-function varargout = matRad_importDicomGUI(varargin)
-% MATRAD_IMPORTDICOMGUI MATLAB code for matRad_importDicomGUI.fig
-% MATRAD_IMPORTDICOMGUI, by itself, creates a new MATRAD_IMPORTDICOMGUI or raises the existing
-% singleton*.
-%
-% H = MATRAD_IMPORTDICOMGUI returns the handle to a new MATRAD_IMPORTDICOMGUI or the handle to
-% the existing singleton*.
-%
-% MATRAD_IMPORTDICOMGUI('CALLBACK',hObject,eventData,handles,...) calls the local
-% function named CALLBACK in MATRAD_IMPORTDICOMGUI.M with the given input arguments.
-%
-% MATRAD_IMPORTDICOMGUI('Property','Value',...) creates a new MATRAD_IMPORTDICOMGUI or raises the
-% existing singleton*. Starting from the left, property value pairs are
-% applied to the GUI before matRad_importDicomGUI_OpeningFcn gets called. An
-% unrecognized property name or invalid value makes property application
-% stop. All inputs are passed to matRad_importDicomGUI_OpeningFcn via varargin.
-%
-% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
-% instance to run (singleton)".
-%
-% See also: GUIDE, GUIDATA, GUIHANDLES
-
-% Edit the above text to modify the response to help matRad_importDicomGUI
-
-% Last Modified by GUIDE v2.5 02-Jun-2017 00:45:04
-
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% Begin initialization code - DO NOT EDIT
-gui_Singleton = 1;
-gui_State = struct('gui_Name', mfilename, ...
- 'gui_Singleton', gui_Singleton, ...
- 'gui_OpeningFcn', @matRad_importDicomGUI_OpeningFcn, ...
- 'gui_OutputFcn', @matRad_importDicomGUI_OutputFcn, ...
- 'gui_LayoutFcn', [] , ...
- 'gui_Callback', []);
-if nargin && ischar(varargin{1})
- gui_State.gui_Callback = str2func(varargin{1});
-end
-
-% if nargout
- [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
-% else
-% gui_mainfcn(gui_State, varargin{:});
-% end
-% End initialization code - DO NOT EDIT
-
-
-% --- Executes just before matRad_importDicomGUI is made visible.
-function matRad_importDicomGUI_OpeningFcn(hObject, eventdata, handles, varargin)
-% This function has no output args, see OutputFcn.
-% hObject handle to figure
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-% varargin command line arguments to matRad_importDicomGUI (see VARARGIN)
-
-% Choose default command line output for matRad_importDicomGUI
-handles.output = hObject;
-
-axes(handles.axesMatRadLogo)
-[im, ~, alpha] = imread('matrad_logo.png');
-q = image(im);
-axis equal off
-set(q, 'AlphaData', alpha);
-% show dkfz logo
-axes(handles.axesDKFZLogo)
-[im, ~, alpha] = imread('DKFZ_Logo.png');
-p = image(im);
-axis equal off
-set(p, 'AlphaData', alpha);
-% Update handles structure
-guidata(hObject, handles);
-
-% UIWAIT makes matRad_importDicomGUI wait for user response (see UIRESUME)
-% uiwait(handles.figure1);
-
-
-% --- Outputs from this function are returned to the command line.
-function varargout = matRad_importDicomGUI_OutputFcn(hObject, eventdata, handles)
-% varargout cell array for returning output args (see VARARGOUT);
-% hObject handle to figure
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Get default command line output from handles structure
-varargout{1} = handles.output;
-
-
-% --- Executes on button press in browse_button.
-function patDir = browse_button_Callback(hObject, eventdata, handles)
-% hObject handle to browse_button (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-%uiwait(warndlg('Choose the input directory'));
-patDir = uigetdir('', 'Choose the input directory...');
-if patDir ~= 0
- patDir = [patDir filesep];
- %handles.dir_path_field.String = patDir;
- set(handles.dir_path_field,'String',patDir);
- % Update handles structure
- guidata(hObject, handles);
- scan(hObject, eventdata, handles)
-end
-
-function scan(hObject, eventdata, handles)
-[fileList, patient_listbox] = matRad_scanDicomImportFolder(get(handles.dir_path_field,'String'));
-if iscell(patient_listbox)
- handles.fileList = fileList;
- %handles.patient_listbox.String = patient_listbox;
- set(handles.patient_listbox,'String',patient_listbox,'Value',1);
- guidata(hObject, handles);
-end
-
-% --- Executes on selection change in patient_listbox.
-function patient_listbox_Callback(hObject, eventdata, handles)
-% hObject handle to patient_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns patient_listbox contents as cell array
-% contents{get(hObject,'Value')} returns selected item from patient_listbox
-
-if ~isempty(get(hObject,'String'))
- % enable Import button
- set(handles.import_button,'Enable','on');
-
- % handles.filelist:
- % 1. Filepath
- % 2. Modality
- % 3. PatientID
- % 4. SeriesUID
- % 5. SeriesNumber
- % 9. res_x
- % 10. res_y
- % 11. res_z
- % 12. detailed dose description - currently not in use for GUI user
- patient_listbox = get(handles.patient_listbox,'String');
- selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
- % this gets a list of rtss series for this patient
- set(handles.rtseries_listbox,'Value',1); % set dummy value to one
- set(handles.rtseries_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTSTRUCT') & strcmp(handles.fileList(:,3), selected_patient),4));
- % this gets a list of rt plan series for this patient
- set(handles.rtplan_listbox,'Value',[]); % set dummy value to none
- set(handles.rtplan_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTPLAN') & strcmp(handles.fileList(:,3), selected_patient),4));
- % this gets a list of dose series for this patient
- set(handles.doseseries_listbox,'Value',[]); % set dummy value to none
- set(handles.doseseries_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTDOSE') & strcmp(handles.fileList(:,3), selected_patient),4));
- % selectedDose
-
- if get(handles.SeriesUID_radiobutton,'Value') == 1
- % this gets a list of ct series for this patient
- set(handles.ctseries_listbox,'Value',1); % set dummy value to one
- set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),4)));
-
- selectedDoseSeriesString = get(handles.doseseries_listbox,'String');
- % this gets a resolution for this patient
- selectedCtSeriesString = get(handles.ctseries_listbox,'String');
- if ~isempty(selectedCtSeriesString)
- res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9));
- res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10));
- res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11));
- else
- res_x = NaN; res_y = NaN; res_z = NaN;
- end
- else
- set(handles.ctseries_listbox,'Value',1); % set dummy value to one
- set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),5)));
- selectedCtSeriesString = get(handles.ctseries_listbox,'String');
- if ~isempty(selectedCtSeriesString)
- res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9));
- res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10));
- res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11));
- else
- res_x = NaN; res_y = NaN; res_z = NaN;
- end
- end
- set(handles.resx_edit,'String',res_x);
- set(handles.resy_edit,'String',res_y);
- if numel(res_z) > 1
- set(handles.resz_edit,'String','not equi');
- else
- set(handles.resz_edit,'String',res_z);
- end
- % Update handles structure
- guidata(hObject, handles);
-end
-
-% --- Executes during object creation, after setting all properties.
-function patient_listbox_CreateFcn(hObject, eventdata, handles)
-% hObject handle to patient_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: listbox controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on selection change in ctseries_listbox.
-function ctseries_listbox_Callback(hObject, eventdata, handles)
-% hObject handle to ctseries_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns ctseries_listbox contents as cell array
-% contents{get(hObject,'Value')} returns selected item from ctseries_listbox
-
-
-% --- Executes during object creation, after setting all properties.
-function ctseries_listbox_CreateFcn(hObject, eventdata, handles)
-% hObject handle to ctseries_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: listbox controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on selection change in rtseries_listbox.
-function rtseries_listbox_Callback(hObject, eventdata, handles)
-% hObject handle to rtseries_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns rtseries_listbox contents as cell array
-% contents{get(hObject,'Value')} returns selected item from rtseries_listbox
-
-
-% --- Executes during object creation, after setting all properties.
-function rtseries_listbox_CreateFcn(hObject, eventdata, handles)
-% hObject handle to rtseries_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: listbox controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on button press in import_button.
-function import_button_Callback(hObject, eventdata, handles)
-% hObject handle to import_button (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-patient_listbox = get(handles.patient_listbox,'String');
-ctseries_listbox = get(handles.ctseries_listbox,'String');
-rtplan_listbox = get(handles.rtplan_listbox,'String');
-doseseries_listbox = get(handles.rtplan_listbox,'String');
-if ~isempty(patient_listbox)
- selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
-end
-if ~isempty(ctseries_listbox)
- selected_ctseries = ctseries_listbox(get(handles.ctseries_listbox,'Value'));
-end
-if ~isempty(rtplan_listbox)
- selected_rtplan = rtplan_listbox(get(handles.rtplan_listbox,'Value'));
-end
-
-if get(handles.SeriesUID_radiobutton,'Value') == 1
- files.ct = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & ...
- strcmp(handles.fileList(:,4), selected_ctseries),:);
-
- %files.rtss = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & ...
- % strcmp(handles.fileList(:,4), selected_rtseries),:);
-else
- files.ct = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & ...
- strcmp(handles.fileList(:,5), selected_ctseries) & strcmp(handles.fileList(:,2),'CT'),:);
-
- %files.rtss = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & ...
- % strcmp(handles.fileList(:,5), selected_rtseries),:);
-end
-
-allRtss = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,2),'RTSTRUCT'),:);
-if ~isempty(allRtss)
- files.rtss = allRtss(get(handles.rtseries_listbox,'Value'),:);
-else
- files.rtss = [];
-end
-
-files.resx = str2double(get(handles.resx_edit,'String'));
-files.resy = str2double(get(handles.resy_edit,'String'));
-% check if valid assignment is made when z slices are not equi-distant
-if strcmp(get(handles.resz_edit,'String'),'not equi')
- msgbox('Ct data not sliced equi-distantly in z direction! Chose uniform resolution.', 'Error','error');
- return;
-else
- files.resz = str2double(get(handles.resz_edit,'String'));
-end
-% selected RT Plan
-rtplan = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,2),'RTPLAN'),:);
-if ~isempty(rtplan) && ~isempty(get(handles.rtplan_listbox,'Value'))
- files.rtplan = rtplan(get(handles.rtplan_listbox,'Value'),:);
-end
-
-% selected RT Dose
-rtdose = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,2),'RTDOSE'),:);
-if ~isempty(rtdose) && ~isempty(get(handles.doseseries_listbox,'Value'))
- selectedRtDose = get(handles.doseseries_listbox,'String');
- selectedRtDoseIx = NaN*ones(1,numel(selectedRtDose));
- for i = 1:numel(selectedRtDose)
- selectedRtDoseIx(i) = find(strcmp(rtdose(:,4),selectedRtDose{i}));
- end
- files.rtdose = rtdose(selectedRtDoseIx,:);
-end
-
-% check if we should use the dose grid resolution
-files.useDoseGrid = get(handles.checkbox3,'Value');
-
-% dicomMetaBool: store complete DICOM information and patientName or not
-dicomMetaBool = logical(get(handles.checkPatientName,'Value'));
-matRad_importDicom(files, dicomMetaBool);
-
-
-% --- Executes on button press in cancel_button.
-function cancel_button_Callback(hObject, eventdata, handles)
-% hObject handle to cancel_button (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-close(handles.figure1);
-
-
-% --- Executes on button press in rescan_button.
-function rescan_button_Callback(hObject, eventdata, handles)
-% hObject handle to rescan_button (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-
-% --- Executes on mouse press over figure background.
-function figure1_ButtonDownFcn(hObject, eventdata, handles)
-% hObject handle to figure1 (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-
-
-function dir_path_field_Callback(hObject, eventdata, handles)
-% hObject handle to dir_path_field (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of dir_path_field as text
-% str2double(get(hObject,'String')) returns contents of dir_path_field as a double
-
-patDir = get(handles.dir_path_field,'String');
-if patDir(end) ~= filesep;
- patDir = [patDir filesep];
- set(handles.dir_path_field,'String',patDir);
- guidata(hObject, handles);
-end
-scan(hObject, eventdata, handles);
-
-
-% --- Executes on button press in SeriesUID_radiobutton.
-function SeriesUID_radiobutton_Callback(hObject, eventdata, handles)
-% hObject handle to SeriesUID_radiobutton (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-if get(hObject,'Value') == 1
- set(handles.SeriesNumber_radiobutton,'Value',0);
-else
- set(hObject,'Value',1);
- set(handles.SeriesNumber_radiobutton,'Value',0);
-end
-if isfield(handles, 'fileList')
- patient_listbox = get(handles.patient_listbox,'String');
- selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
- set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),4)));
- set(handles.rtseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'RTSTRUCT') & strcmp(handles.fileList(:,3), selected_patient),4)));
- set(handles.doseseries_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTDOSE') & strcmp(handles.fileList(:,3), selected_patient),4));
- set(handles.rtplan_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'RTPLAN') & strcmp(handles.fileList(:,3), selected_patient),4)));
-else
- fprintf('No patient loaded, so just switching default display option to SeriesUID. \n');
-end
-guidata(hObject, handles);
-
-% --- Executes on button press in SeriesNumber_radiobutton.
-function SeriesNumber_radiobutton_Callback(hObject, eventdata, handles)
-% hObject handle to SeriesNumber_radiobutton (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-if get(hObject,'Value') == 1
- set(handles.SeriesUID_radiobutton,'Value',0);
-else
- set(hObject,'Value',1);
- set(handles.SeriesUID_radiobutton,'Value',0);
-end
-if isfield(handles, 'fileList')
- patient_listbox = get(handles.patient_listbox,'String');
- selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
- set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),5)));
-else
- fprintf('No patient loaded, so just switching default display option to SeriesNumber. \n');
-end
-guidata(hObject, handles);
-
-
-
-function resx_edit_Callback(hObject, eventdata, handles)
-% hObject handle to resx_edit (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of resx_edit as text
-% str2double(get(hObject,'String')) returns contents of resx_edit as a double
-
-
-% --- Executes during object creation, after setting all properties.
-function resx_edit_CreateFcn(hObject, eventdata, handles)
-% hObject handle to resx_edit (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-
-function resy_edit_Callback(hObject, eventdata, handles)
-% hObject handle to resy_edit (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of resy_edit as text
-% str2double(get(hObject,'String')) returns contents of resy_edit as a double
-
-
-% --- Executes during object creation, after setting all properties.
-function resy_edit_CreateFcn(hObject, eventdata, handles)
-% hObject handle to resy_edit (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-
-function resz_edit_Callback(hObject, eventdata, handles)
-% hObject handle to resz_edit (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of resz_edit as text
-% str2double(get(hObject,'String')) returns contents of resz_edit as a double
-
-
-% --- Executes during object creation, after setting all properties.
-function resz_edit_CreateFcn(hObject, eventdata, handles)
-% hObject handle to resz_edit (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-%
-% % --- Executes on selection change in ctseries_listbox.
-% function ctseries_listbox_Callback(hObject, eventdata, handles)
-% % hObject handle to ctseries_listbox (see GCBO)
-% % eventdata reserved - to be defined in a future version of MATLAB
-% % handles structure with handles and user data (see GUIDATA)
-%
-% % Hints: contents = cellstr(get(hObject,'String')) returns ctseries_listbox contents as cell array
-% % contents{get(hObject,'Value')} returns selected item from ctseries_listbox
-%
-%
-% % --- Executes during object creation, after setting all properties.
-% function ctseries_listbox_CreateFcn(hObject, eventdata, handles)
-% % hObject handle to ctseries_listbox (see GCBO)
-% % eventdata reserved - to be defined in a future version of MATLAB
-% % handles empty - handles not created until after all CreateFcns called
-%
-% % Hint: listbox controls usually have a white background on Windows.
-% % See ISPC and COMPUTER.
-% if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
-% set(hObject,'BackgroundColor','white');
-% end
-%
-%
-% % --- Executes on selection change in rtseries_listbox.
-% function rtseries_listbox_Callback(hObject, eventdata, handles)
-% % hObject handle to rtseries_listbox (see GCBO)
-% % eventdata reserved - to be defined in a future version of MATLAB
-% % handles structure with handles and user data (see GUIDATA)
-%
-% % Hints: contents = cellstr(get(hObject,'String')) returns rtseries_listbox contents as cell array
-% % contents{get(hObject,'Value')} returns selected item from rtseries_listbox
-%
-%
-% % --- Executes during object creation, after setting all properties.
-% function rtseries_listbox_CreateFcn(hObject, eventdata, handles)
-% % hObject handle to rtseries_listbox (see GCBO)
-% % eventdata reserved - to be defined in a future version of MATLAB
-% % handles empty - handles not created until after all CreateFcns called
-%
-% % Hint: listbox controls usually have a white background on Windows.
-% % See ISPC and COMPUTER.
-% if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
-% set(hObject,'BackgroundColor','white');
-% end
-
-
-% --- Executes on selection change in doseseries_listbox.
-function doseseries_listbox_Callback(hObject, eventdata, handles)
-% hObject handle to doseseries_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns doseseries_listbox contents as cell array
-% contents{get(hObject,'Value')} returns selected item from doseseries_listbox
-
-if ~isempty(get(hObject,'Value'))
- set(handles.checkbox3,'Enable','on');
-else
- set(handles.checkbox3,'Value',0);
- set(handles.checkbox3,'Enable','off');
- % retrieve and display resolution for DICOM ct cube
- patient_listbox = get(handles.patient_listbox,'String');
- selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
- selectedCtSeriesString = get(handles.ctseries_listbox,'String');
- if get(handles.SeriesUID_radiobutton,'Value') == 1
- if ~isempty(selectedCtSeriesString)
- res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9));
- res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10));
- res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11));
- else
- res_x = NaN; res_y = NaN; res_z = NaN;
- end
- else
- if ~isempty(selectedCtSeriesString)
- res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9));
- res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10));
- res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11));
- else
- res_x = NaN; res_y = NaN; res_z = NaN;
- end
- end
- set(handles.resx_edit,'String',res_x);
- set(handles.resy_edit,'String',res_y);
- if numel(res_z) > 1
- set(handles.resz_edit,'String','not equi');
- else
- set(handles.resz_edit,'String',res_z);
- end
-
-end
-
-% --- Executes during object creation, after setting all properties.
-function doseseries_listbox_CreateFcn(hObject, eventdata, handles)
-% hObject handle to doseseries_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: listbox controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-% --- Executes on selection change in rtplan_listbox.
-function rtplan_listbox_Callback(hObject, eventdata, handles)
-% hObject handle to rtplan_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-contents = cellstr(get(hObject,'String'));
-if ~isempty(get(hObject,'Value')) && numel(get(hObject,'Value')) == 1
-
- selectedPlan = contents{get(hObject,'Value')};
- % point at plan in listbox
- selectedPlanLoc = strcmp(handles.fileList(:,4),selectedPlan);
-
- % show only the doses corresponding to the plan
- corrDoses = [handles.fileList{selectedPlanLoc,13}];
- numOfDoses = numel(corrDoses);
- corrDosesLoc = zeros(size(handles.fileList(:,1),1),1);
- for j = 1:numOfDoses
- if ~isnan(corrDoses{j})
- corrDosesLoc = corrDosesLoc | strcmp(handles.fileList(:,4),corrDoses{j});
- end
- end
-
- if sum(corrDosesLoc) == 0
- warndlg('no rt dose file directly associated to plan file. showing all rt dose files.');
- corrDosesLoc = strcmp(handles.fileList(:,2),'RTDOSE');
- end
-
- set(handles.doseseries_listbox,'Value',[]); % set dummy value to one
- set(handles.doseseries_listbox,'String',handles.fileList(corrDosesLoc,4));
-
- % disable checkbox for use dose grid is currently checked
- if get(handles.checkbox3,'Value') == 1
- set(handles.checkbox3,'Value',0);
- checkbox3_Callback(handles.checkbox3,[], handles);
- end
- set(handles.checkbox3,'Enable','off');
-
-
-elseif numel(get(hObject,'Value')) >=2
- warning('More than one RTPLAN selected. Unsetting selection ...');
- patient_listbox_Callback(hObject, eventdata, handles)
-else
- patient_listbox_Callback(hObject, eventdata, handles)
-end
-
-
-% --- Executes during object creation, after setting all properties.
-function rtplan_listbox_CreateFcn(hObject, eventdata, handles)
-% hObject handle to rtplan_listbox (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: listbox controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on button press in checkPatientName.
-function checkPatientName_Callback(hObject, eventdata, handles)
-% hObject handle to checkPatientName (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-%A = get(hObject,'Value');
-
-% Hint: get(hObject,'Value') returns toggle state of checkPatientName
-%guidata(hObject, handles);
-
-
-% --- Executes on button press in checkbox3.
-function checkbox3_Callback(hObject, eventdata, handles)
-% hObject handle to checkbox3 (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of checkbox3
-
-if get(hObject,'Value')
- set(handles.resx_edit,'Enable', 'off');
- set(handles.resy_edit,'Enable', 'off');
- set(handles.resz_edit,'Enable', 'off');
- % retrieve and display resolution for DICOM dose cube
- doseFilesInList = get(handles.doseseries_listbox,'String');
- selectedDoseFiles = get(handles.doseseries_listbox,'Value');
- if isempty(selectedDoseFiles)
- set(hObject,'Value',0)
- errordlg('no dose file selected');
- return;
- end
- for i = 1:numel(selectedDoseFiles)
- selectedDoseFile = doseFilesInList{selectedDoseFiles(i)};
- if verLessThan('matlab','9')
- dicomDoseInfo = dicominfo(handles.fileList{find(strcmp(handles.fileList(:,4),selectedDoseFile)),1});
- else
- dicomDoseInfo = dicominfo(handles.fileList{find(strcmp(handles.fileList(:,4),selectedDoseFile)),1},'UseDictionaryVR',true);
- end
- res_x{i} = dicomDoseInfo.PixelSpacing(1);
- res_y{i} = dicomDoseInfo.PixelSpacing(2);
- res_z{i} = dicomDoseInfo.SliceThickness;
- end
-
- if numel(unique(cell2mat(res_x)))*numel(unique(cell2mat(res_y)))*numel(unique(cell2mat(res_z))) ~= 1
- set(handles.checkbox3,'Value',0);
- warndlg('Different resolutions in dose file(s)');
- set(handles.resx_edit,'Enable', 'on');
- set(handles.resy_edit,'Enable', 'on');
- set(handles.resz_edit,'Enable', 'on');
- else
- set(handles.resx_edit,'String',num2str(res_x{1}));
- set(handles.resy_edit,'String',num2str(res_y{1}));
- set(handles.resz_edit,'String',num2str(res_z{1}));
- end
-
-else
- set(handles.resx_edit,'Enable', 'on');
- set(handles.resy_edit,'Enable', 'on');
- set(handles.resz_edit,'Enable', 'on');
- % retrieve and display resolution for DICOM ct cube
- patient_listbox = get(handles.patient_listbox,'String');
- selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
- selectedCtSeriesString = get(handles.ctseries_listbox,'String');
- if get(handles.SeriesUID_radiobutton,'Value') == 1
- if ~isempty(selectedCtSeriesString)
- res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9));
- res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10));
- res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11));
- else
- res_x = NaN; res_y = NaN; res_z = NaN;
- end
- else
- if ~isempty(selectedCtSeriesString)
- res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9));
- res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10));
- res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11));
- else
- res_x = NaN; res_y = NaN; res_z = NaN;
- end
- end
- set(handles.resx_edit,'String',res_x);
- set(handles.resy_edit,'String',res_y);
- if numel(res_z) > 1
- set(handles.resz_edit,'String','not equi');
- else
- set(handles.resz_edit,'String',res_z);
- end
-
-end
diff --git a/dicom/matRad_importDicomRTPlan.m b/dicom/matRad_importDicomRTPlan.m
deleted file mode 100644
index 4e8ab079a..000000000
--- a/dicom/matRad_importDicomRTPlan.m
+++ /dev/null
@@ -1,165 +0,0 @@
-function pln = matRad_importDicomRTPlan(ct, rtPlanFiles, dicomMetaBool)
-% matRad function to import dicom RTPLAN data
-%
-% call
-% pln = matRad_importDicomRTPlan(ct, rtPlanFiles, dicomMetaBool)
-%
-% input
-% ct: ct imported by the matRad_importDicomCt function
-% rtPlanFiles: list of RTPlan Dicom files
-% dicomMetaBool: import whole dicom information
-%
-% output
-% pln: matRad pln struct with meta information. Note that
-% bixelWidth is determined via the importSteering function.
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% load plan file
-% check size of RT Plan
-if size(rtPlanFiles,1) ~= 1
- errordlg('Too few or to many RTPlan files')
-end
-
-% read information out of the RT file
-if verLessThan('matlab','9')
- planInfo = dicominfo(rtPlanFiles{1});
-else
- planInfo = dicominfo(rtPlanFiles{1},'UseDictionaryVR',true);
-end
-
-% check which type of Radiation is used
-if isfield(planInfo, 'BeamSequence')
- BeamParam = 'BeamSequence';
- ControlParam = 'ControlPointSequence';
-elseif isfield(planInfo, 'IonBeamSequence')
- BeamParam = 'IonBeamSequence';
- ControlParam = 'IonControlPointSequence';
-else
- errordlg('Not supported kind of DICOM RT plan file.');
-end
-
-% get beam sequence
-BeamSequence = planInfo.(BeamParam);
-BeamSeqNames = fieldnames(BeamSequence);
-
-% use the treatment beams only
-if isfield(BeamSequence.(BeamSeqNames{1}),'TreatmentDeliveryType')
- for i = 1:length(BeamSeqNames)
- currBeamSeq = BeamSequence.(BeamSeqNames{i});
- try
- treatDelType = currBeamSeq.TreatmentDeliveryType;
- if ~strcmpi(treatDelType,'TREATMENT')
- BeamSequence = rmfield(BeamSequence,BeamSeqNames{i});
- end
- catch
- warning('Something went wrong while determining the type of the beam.');
- end
- end
- BeamSeqNames = fieldnames(BeamSequence);
-end
-
-
-%% get information may change between beams
-% loop over beams
-gantryAngles{length(BeamSeqNames)} = [];
-PatientSupportAngle{length(BeamSeqNames)} = [];
-isoCenter = NaN*ones(length(BeamSeqNames),3);
-for i = 1:length(BeamSeqNames)
- currBeamSeq = BeamSequence.(BeamSeqNames{i});
- % parameters not changing are stored in the first ControlPointSequence
- gantryAngles{i} = currBeamSeq.(ControlParam).Item_1.GantryAngle;
- PatientSupportAngle{i} = currBeamSeq.(ControlParam).Item_1.PatientSupportAngle;
- isoCenter(i,:) = currBeamSeq.(ControlParam).Item_1.IsocenterPosition';
-end
-
-% transform iso. At the moment just this way for HFS
-if ct.dicomInfo.ImageOrientationPatient == [1;0;0;0;1;0]
- isoCenter = isoCenter - ones(length(BeamSeqNames),1) * ...
- ([ct.x(1) ct.y(1) ct.z(1)] - [ct.resolution.x ct.resolution.y ct.resolution.z]);
-else
- error('This Orientation is not yet supported.');
-end
-
-%% read constant parameters
-% readout charge and mass to set radiationMode to matRad specific name
-radiationMode = planInfo.(BeamParam).Item_1.RadiationType;
-if ~strncmpi(radiationMode,'photons',6)
- try
- radiationMass = planInfo.(BeamParam).Item_1.RadiationMassNumber;
- radiationAtomicNumber = planInfo.(BeamParam).Item_1.RadiationAtomicNumber;
- catch
- warning('Could not determine mass and atomic number of the particle');
- end
-end
-
-if strncmpi(radiationMode,'photons',6)
- radiationMode = 'photons';
-elseif strncmpi(radiationMode,'proton',6)
- radiationMode = 'protons';
-elseif (strncmpi(radiationMode,'ion',3) && radiationMass == 12 && radiationAtomicNumber == 6)
- radiationMode = 'carbon';
-else
- warning('The given type of radiation is not yet supported');
-end
-
-% extract field shapes
-if strcmp(radiationMode, 'photons')
-
- fractionSequence = planInfo.FractionGroupSequence.Item_1;
- pln.propStf.collimation = matRad_importFieldShapes(BeamSequence,fractionSequence);
-
-end
-
-%% write parameters found to pln variable
-pln.radiationMode = radiationMode; % either photons / protons / carbon
-pln.numOfFractions = planInfo.FractionGroupSequence.Item_1.NumberOfFractionsPlanned;
-pln.machine = planInfo.(BeamParam).Item_1.TreatmentMachineName;
-
-pln.propStf.isoCenter = isoCenter;
-pln.propStf.bixelWidth = NaN; % [mm] / also corresponds to lateral spot spacing for particles
-pln.propStf.gantryAngles = [gantryAngles{1:length(BeamSeqNames)}];
-pln.propStf.couchAngles = [PatientSupportAngle{1:length(BeamSeqNames)}]; % [°]
-pln.propStf.numOfBeams = length(BeamSeqNames);
-
-
-pln.propOpt.bioOptimization = 'none'; % none: physical optimization; const_RBExD; constant RBE of 1.1;
- % LEMIV_effect: effect-based optimization; LEMIV_RBExD: optimization of RBE-weighted dose
-pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below
-pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles
-
-% if we imported field shapes then let's trigger field based dose calc by
-% setting the bixelWidth to 'field'
-if isfield(pln.propStf,'collimation')
- pln.propStf.bixelWidth = 'field';
-end
-
-% timestamp
-pln.DicomInfo.timeStamp = datestr(clock);
-
-try
- pln.DicomInfo.SOPClassUID = planInfo.SOPClassUID;
- pln.DicomInfo.SOPInstanceUID = planInfo.SOPInstanceUID;
- pln.DicomInfo.ReferencedDoseSequence = planInfo.ReferencedDoseSequence;
-catch
-end
-
-% safe entire dicomInfo
-if dicomMetaBool == true
- pln.DicomInfo.Meta = planInfo;
-end
-end
diff --git a/dicom/matRad_importDicomSteeringParticles.m b/dicom/matRad_importDicomSteeringParticles.m
deleted file mode 100644
index 744992bdf..000000000
--- a/dicom/matRad_importDicomSteeringParticles.m
+++ /dev/null
@@ -1,294 +0,0 @@
-function [stf, pln] = matRad_importDicomSteeringParticles(ct, pln, rtPlanFile)
-% matRad function to import a matRad stf struct from dicom RTPLAN data
-%
-% call
-% [stf, pln] = matRad_importDicomSteeringParticles(ct, pln, rtPlanFile)
-%
-% input
-% ct: ct imported by the matRad_importDicomCt function
-% pln: matRad pln struct with meta information
-% rtPlanFile: name of RTPLAN DICOM file
-%
-% output
-% stf matRad stf struct
-% pln: matRad pln struct.
-% Note: pln is input and output since pln.bixelWidth is
-% determined here.
-%
-% References
-% -
-% Note
-% not implemented - compensator. Fixed SAD.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% load plan file
-% load machine data
-
-dlgBaseDataText = ['Import steering information from DICOM Plan.','Choose corresponding matRad base data for ', ...
- pln.radiationMode, '.'];
-% messagebox only necessary for non windows users
-if ~ispc
- uiwait(helpdlg(dlgBaseDataText,['DICOM import - ', pln.radiationMode, ' base data' ]));
-end
-[fileName,pathName] = uigetfile('*.mat', dlgBaseDataText);
-load([pathName filesep fileName]);
-
-ix = find(fileName == '_');
-pln.machine = fileName(ix(1)+1:end-4);
-
-% RT Plan consists only on meta information
-if verLessThan('matlab','9')
- rtPlanInfo = dicominfo(rtPlanFile{1});
-else
- rtPlanInfo = dicominfo(rtPlanFile{1},'UseDictionaryVR',true);
-end
-BeamSeq = rtPlanInfo.IonBeamSequence;
-BeamSeqNames = fieldnames(BeamSeq);
-% Number of Beams from plan
-numOfBeamsPlan = length(pln.propStf.gantryAngles);
-
-% use only the treatment beams
-for i = 1:length(BeamSeqNames)
- currBeamSeq = BeamSeq.(BeamSeqNames{i});
- try
- treatDelType = currBeamSeq.TreatmentDeliveryType;
- if ~strcmpi(treatDelType,'TREATMENT')
- BeamSeq = rmfield(BeamSeq,BeamSeqNames{i});
- end
- catch
- warning('Something went wrong while determining the type of the beam.');
- end
-end
-
-% reinitialize the BeamSeqNames and length, as the Seq itself is reduced.
-BeamSeqNames = fieldnames(BeamSeq);
-
-% remove empty ControlPointSequences
-for i = 1:length(BeamSeqNames)
- currBeamSeq = BeamSeq.(BeamSeqNames{i});
- ControlPointSeq = currBeamSeq.IonControlPointSequence;
- ControlPointSeqNames = fieldnames(ControlPointSeq);
- numOfContrPointSeq = length(ControlPointSeqNames);
- for currContr = 1:numOfContrPointSeq
- currContrSeq = ControlPointSeq.(ControlPointSeqNames{currContr});
- if sum(currContrSeq.ScanSpotMetersetWeights) == 0
- ControlPointSeq = rmfield(ControlPointSeq,ControlPointSeqNames{currContr});
- end
- end
- BeamSeq.(BeamSeqNames{i}).IonControlPointSequence = ControlPointSeq;
-end
-
-% check if number of beams correspond
-if ~isequal(length(BeamSeqNames),numOfBeamsPlan)
- warning('Number of beams from beamsequences do not correspond to number of Gantry Angles');
-end
-
-%% generate stf struct
-% surfaceEntry = BeamSeq.Item_1.IonControlPointSequence.Item_1.SurfaceEntryPoint;
-
-% Preallocate stf
-stf(length(BeamSeqNames)).gantryAngle = [];
-stf(length(BeamSeqNames)).couchAngle = [];
-stf(length(BeamSeqNames)).bixelWidth = [];
-stf(length(BeamSeqNames)).radiationMode = [];
-stf(length(BeamSeqNames)).SAD = [];
-stf(length(BeamSeqNames)).isoCenter = [];
-stf(length(BeamSeqNames)).sourcePoint_bev = [];
-stf(length(BeamSeqNames)).numOfRays = [];
-stf(length(BeamSeqNames)).numOfBixelsPerRay = [];
-stf(length(BeamSeqNames)).totalNumOfBixels = [];
-stf(length(BeamSeqNames)).ray = [];
-
-for i = 1:length(BeamSeqNames)
- currBeamSeq = BeamSeq.(BeamSeqNames{i});
- ControlPointSeq = currBeamSeq.IonControlPointSequence;
- stf(i).gantryAngle = pln.propStf.gantryAngles(i);
- stf(i).couchAngle = pln.propStf.couchAngles(i);
- stf(i).bixelWidth = pln.propStf.bixelWidth;
- stf(i).radiationMode = pln.radiationMode;
- % there might be several SAD's, e.g. compensator?
- stf(i).SAD = machine.meta.SAD;
- stf(i).isoCenter = pln.propStf.isoCenter(i,:);
- stf(i).sourcePoint_bev = [0 -stf(i).SAD 0];
- % now loop over ControlPointSequences
- ControlPointSeqNames = fieldnames(ControlPointSeq);
- numOfContrPointSeq = length(ControlPointSeqNames);
- % create empty helper matrix
- temporarySteering = zeros(0,8);
- for currContr = 1:numOfContrPointSeq
- currContrSeq = ControlPointSeq.(ControlPointSeqNames{currContr});
- % get energy, equal for all coming elements in the next loop
- currEnergy = currContrSeq.NominalBeamEnergy;
- % get focusValue
- currFocus = unique(currContrSeq.ScanningSpotSize);
- % get the Spotpositions
- numOfScanSpots = currContrSeq.NumberOfScanSpotPositions;
- % x is 1, 3, 5 ...; y 2, 4, 6,
- c1_help = currContrSeq.ScanSpotPositionMap(1:2:(2 * numOfScanSpots));
- c2_help = currContrSeq.ScanSpotPositionMap(2:2:(2 * numOfScanSpots));
- weight_help = currContrSeq.ScanSpotMetersetWeights;
- if isfield(currContrSeq, 'RangeShifterSettingsSequence')
- % rangeshifter identification
- rashiID = currContrSeq.RangeShifterSettingsSequence.Item_1.ReferencedRangeShifterNumber;
- % rangeshifter waterequivalent thickness
- rashiWeThickness = currContrSeq.RangeShifterSettingsSequence.Item_1.RangeShifterWaterEquivalentThickness;
- % rangeshifter isocenter to range shifter distance
- rashiIsoRangeDist = currContrSeq.RangeShifterSettingsSequence.Item_1.IsocenterToRangeShifterDistance;
- elseif currContr == 1
- rashiID = 0;
- rashiWeThickness = 0;
- rashiIsoRangeDist = 0;
- else
- % in this case range shifter settings has not changed between this
- % and previous control sequence, so reuse values.
- end
- temporarySteering = [temporarySteering; c1_help c2_help ...
- (currEnergy * ones(numOfScanSpots,1)) weight_help (currFocus * ones(numOfScanSpots,1)) ...
- (rashiID * ones(numOfScanSpots,1)) (rashiWeThickness * ones(numOfScanSpots,1)) (rashiIsoRangeDist * ones(numOfScanSpots,1))];
- end
-
- % finds all unique rays and saves them in to the stf
- [RayPosTmp, ~, ic] = unique(temporarySteering(:,1:2), 'rows');
- clear ray;
- for j = 1:size(RayPosTmp,1)
- stf(i).ray(j).rayPos_bev = double([RayPosTmp(j,1) 0 RayPosTmp(j,2)]);
- stf(i).ray(j).energy = [];
- stf(i).ray(j).focusFWHM = [];
- stf(i).ray(j).focusIx = [];
- stf(i).ray(j).weight = [];
- stf(i).ray(j).rangeShifter = struct();
- ray(j).ID = [];
- ray(j).eqThickness = [];
- ray(j).sourceRashiDistance = [];
- end
-
- % saves all energies and weights to their corresponding ray
- for j = 1:size(temporarySteering,1)
- k = ic(j);
- stf(i).ray(k).energy = [stf(i).ray(k).energy double(temporarySteering(j,3))];
- stf(i).ray(k).focusFWHM = [stf(i).ray(k).focusFWHM double(temporarySteering(j,5))];
- stf(i).ray(k).weight = [stf(i).ray(k).weight double(temporarySteering(j,4)) / 1e6];
- % helpers to construct something like a(:).b = c.b(:) after this
- % loop
- ray(k).ID = [ray(k).ID double(temporarySteering(j,6))];
- ray(k).eqThickness = [ray(k).eqThickness double(temporarySteering(j,7))];
- ray(k).sourceRashiDistance = [ray(k).sourceRashiDistance double(temporarySteering(j,8))];
- end
-
- % reassign to preserve data structure
- for j = 1:numel(ray)
- for k = 1:numel(ray(j).ID)
- stf(i).ray(j).rangeShifter(k).ID = ray(j).ID(k);
- stf(i).ray(j).rangeShifter(k).eqThickness = ray(j).eqThickness(k);
- stf(i).ray(j).rangeShifter(k).sourceRashiDistance = stf(i).SAD - ray(j).sourceRashiDistance(k);
- end
- end
-
-
- % getting some information of the rays
- % clean up energies, so they appear only one time per energy
- numOfRays = size(stf(i).ray,2);
- for l = 1:numOfRays
- stf(i).ray(l).energy = unique(stf(i).ray(l).energy);
- stf(i).ray(l).targetPoint_bev = [2*stf(i).ray(l).rayPos_bev(1) ...
- machine.meta.SAD ...
- 2*stf(i).ray(l).rayPos_bev(3)];
- end
- stf(i).numOfRays = numel(stf(i).ray);
-
- % save total number of bixels
- numOfBixels = 0;
- for j = 1:numel(stf(i).ray)
- numOfBixels = numOfBixels + numel(stf(i).ray(j).energy);
- stf(i).numOfBixelsPerRay(j) = numel(stf(i).ray(j).energy);
-% w = [w stf(currBeam).ray(j).weight];
- end
-
- stf(i).totalNumOfBixels = numOfBixels;
-
- % get bixelwidth
- bixelWidth_help = zeros(size(stf(i).ray,2),2);
- for j = 1:stf(i).numOfRays
- bixelWidth_help(j,1) = stf(i).ray(j).rayPos_bev(1);
- bixelWidth_help(j,2) = stf(i).ray(j).rayPos_bev(3);
- end
- bixelWidth_help1 = unique(round(1e3*bixelWidth_help(:,1))/1e3,'sorted');
- bixelWidth_help2 = unique(round(1e3*bixelWidth_help(:,2))/1e3,'sorted');
-
- bixelWidth = unique([unique(diff(bixelWidth_help1))' unique(diff(bixelWidth_help2))']);
-
- if numel(bixelWidth) == 1
- stf(i).bixelWidth = bixelWidth;
- else
- stf(i).bixelWidth = NaN;
- end
-
- % coordinate transformation with rotation matrix.
- % use transpose matrix because we are working with row vectors
- rotMat_vectors_T = transpose(matRad_getRotationMatrix(pln.propStf.gantryAngles(i),pln.propStf.couchAngles(i)));
-
- % Rotated Source point (1st gantry, 2nd couch)
- stf(i).sourcePoint = stf(i).sourcePoint_bev*rotMat_vectors_T;
-
- % Save ray and target position in lps system.
- for j = 1:stf(i).numOfRays
- stf(i).ray(j).rayPos = stf(i).ray(j).rayPos_bev*rotMat_vectors_T;
- stf(i).ray(j).targetPoint = stf(i).ray(j).targetPoint_bev*rotMat_vectors_T;
- end
-
- % book keeping & calculate focus index
- for j = 1:stf(i).numOfRays
- stf(i).numOfBixelsPerRay(j) = numel([stf(i).ray(j).energy]);
- end
-
- % use the original machine energies
- for j = 1:stf(i).numOfRays
- % loop over all energies
- numOfEnergy = length(stf(i).ray(j).energy);
- for k = 1:numOfEnergy
- energyIndex = find(abs([machine.data(:).energy]-stf(i).ray(j).energy(k))<10^-2);
- if ~isempty(energyIndex)
- stf(i).ray(j).energy(k) = machine.data(energyIndex).energy;
- else
- error('No match between imported and machine data. Maybe wrong machine loaded.');
- end
- end
- end
-
- % get focusIx instead of focusFWHM
- for j = 1:stf(i).numOfRays
- % loop over all energies
- numOfEnergy = length(stf(i).ray(j).energy);
- for k = 1:numOfEnergy
- energyTemp = stf(i).ray(j).energy(k);
- focusFWHM = stf(i).ray(j).focusFWHM(k);
- energyIxTemp = find([machine.data.energy] == energyTemp);
- focusIxTemp = find(abs([machine.data(energyIxTemp).initFocus.SisFWHMAtIso] - focusFWHM )< 10^-3);
- stf(i).ray(j).focusIx(k) = focusIxTemp;
- stf(i).ray(j).focusFWHM(k) = machine.data(energyIxTemp).initFocus.SisFWHMAtIso(stf(i).ray(j).focusIx(k));
- end
- end
-
- stf(i).timeStamp = datestr(clock);
-
-end
-
-if any(isnan([stf(:).bixelWidth])) || numel(unique([stf(:).bixelWidth])) > 1
- pln.propStf.bixelWidth = NaN;
-else
- pln.propStf.bixelWidth = stf(1).bixelWidth;
-end
-
-end
diff --git a/dicom/matRad_importDicomSteeringPhotons.m b/dicom/matRad_importDicomSteeringPhotons.m
deleted file mode 100644
index c0ccd71b0..000000000
--- a/dicom/matRad_importDicomSteeringPhotons.m
+++ /dev/null
@@ -1,97 +0,0 @@
-function [stf, pln] = matRad_importDicomSteeringPhotons(pln)
-% matRad function to import a matRad stf struct from dicom RTPLAN data
-%
-% call
-% [stf, pln] = matRad_importDicomSteeringPhotons(pln)
-%
-% input
-% pln: matRad pln struct with meta information (collimation
-% data included)
-%
-% output
-% stf matRad stf struct
-% pln matRad pln struct
-%
-% References
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-stf = struct;
-if ~isfield(pln.propStf.collimation,'Fields')
- return
-end
-
-% get fields possessing a field weight vector greater than 0
-Fields = pln.propStf.collimation.Fields([pln.propStf.collimation.Fields(:).Weight] > 0);
-
-[UniqueComb,ia,ib] = unique( vertcat([Fields(:).GantryAngle], [Fields(:).CouchAngle])','rows');
-
-% return corret angles to pln, because some angle derivations might be
-% only in the control point sequences
-pln.propStf.gantryAngles = UniqueComb(:,1)';
-pln.propStf.couchAngles = UniqueComb(:,2)';
-
-stf = struct;
-% loop over all fields
-for i = 1:size(UniqueComb,1)
- % set necessary steering information
- stf(i).gantryAngle = UniqueComb(i,1);
- stf(i).couchAngle = UniqueComb(i,2);
- stf(i).isoCenter = pln.propStf.isoCenter(i,:);
-
- % bixelWidth = 'field' as keyword for whole field dose calc
- stf(i).bixelWidth = 'field';
- stf(i).radiationMode = 'photons';
-
- % only one bixel per ray and one ray for photon dose calc based on
- % fields
- stf(i).numOfBixelsPerRay = 1;
- stf(i).numOfRays = 1;
- stf(i).totalNumOfBixels = stf(i).numOfRays;
- stf(i).SAD = Fields(ia(i)).SAD;
- stf(i).sourcePoint_bev = [0 -stf(i).SAD 0];
-
- % coordinate transformation with rotation matrix.
- % use transpose matrix because we are working with row vectors
- rotMat_vectors_T = transpose(matRad_getRotationMatrix(stf(i).gantryAngle,stf(i).couchAngle));
-
-
- % Rotated Source point (1st gantry, 2nd couch)
- stf(i).sourcePoint = stf(i).sourcePoint_bev*rotMat_vectors_T;
-
- % only one ray in center position
- stf(i).ray.rayPos_bev = [0 0 0];
- stf(i).ray.rayPos = stf(i).ray.rayPos_bev*rotMat_vectors_T;
-
- % target point is for ray in center position at
- stf(i).ray.targetPoint_bev = [0 stf(i).SAD 0];
- stf(i).ray.targetPoint = stf(i).ray.targetPoint_bev*rotMat_vectors_T;
-
- % set weight for output field
- stf(i).ray.weight = 1; % weighting incorporated into primary fluence --> finalShape
- %stf(i).ray.SSD = Fields(ia(i)).SSD;
- stf(i).ray.energy = Fields(ia(i)).Energy;
-
- ix = (ib == i);
- currFieldSeq = Fields(ix);
-
- % add weighted shapes for the current beam
- finalShape = 0;
- for j = 1:sum(ix)
- finalShape = finalShape + currFieldSeq(j).Weight * currFieldSeq(j).Shape;
- end
- stf(i).ray.shape = finalShape;
-
-end
-
diff --git a/dicom/matRad_interpDicomCtCube.m b/dicom/matRad_interpDicomCtCube.m
deleted file mode 100644
index 83a9772a8..000000000
--- a/dicom/matRad_interpDicomCtCube.m
+++ /dev/null
@@ -1,87 +0,0 @@
-function interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution, grid)
-% matRad function to interpolate a 3D ct cube to a different resolution
-%
-% call
-% interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution)
-% interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution, grid)
-%
-% input
-% origCt: original CT as matlab 3D array
-% origCtInfo: meta information about the geometry of the orgiCt cube
-% resolution: target resolution [mm] in x, y, an z direction for the
-% new cube
-% grid: optional: externally specified grid vector
-%
-% output
-% interpCt: interpolated ct cube as matlab 3D array
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-coordsOfFirstPixel = [origCtInfo.ImagePositionPatient];
-
-% set up grid vectors
-x = coordsOfFirstPixel(1,1) + origCtInfo(1).ImageOrientationPatient(1) * ...
- origCtInfo(1).PixelSpacing(1)*double([0:origCtInfo(1).Columns-1]);
-y = coordsOfFirstPixel(2,1) + origCtInfo(1).ImageOrientationPatient(5) * ...
- origCtInfo(1).PixelSpacing(2)*double([0:origCtInfo(1).Rows-1]);
-z = coordsOfFirstPixel(3,:);
-
-if exist('grid','var')
- xq = grid{1};
- yq = grid{2};
- zq = grid{3};
-
- % calculate intersection of regions to avoid interpolation issues
- xqRe = coordsOfFirstPixel(1,1):origCtInfo(1).ImageOrientationPatient(1)*resolution.x: ...
- (coordsOfFirstPixel(1,1)+origCtInfo(1).ImageOrientationPatient(1)*origCtInfo(1).PixelSpacing(1)*double(origCtInfo(1).Columns-1));
- yqRe = [coordsOfFirstPixel(2,1):origCtInfo(1).ImageOrientationPatient(5)*resolution.y: ...
- (coordsOfFirstPixel(2,1)+origCtInfo(1).ImageOrientationPatient(5)*origCtInfo(1).PixelSpacing(2)*double(origCtInfo(1).Rows-1))];
- zqRe = coordsOfFirstPixel(3,1):resolution.z: coordsOfFirstPixel(3,end);
-
- % cut values
- xq(xq < min(xqRe)) = [];
- xq(xq > max(xqRe)) = [];
- yq(yq < min(yqRe)) = [];
- yq(yq > max(yqRe)) = [];
- zq(zq < min(zqRe)) = [];
- zq(zq > max(zqRe)) = [];
-else
- xq = coordsOfFirstPixel(1,1):origCtInfo(1).ImageOrientationPatient(1)*resolution.x: ...
- (coordsOfFirstPixel(1,1)+origCtInfo(1).ImageOrientationPatient(1)*origCtInfo(1).PixelSpacing(1)*double(origCtInfo(1).Columns-1));
- yq = [coordsOfFirstPixel(2,1):origCtInfo(1).ImageOrientationPatient(5)*resolution.y: ...
- (coordsOfFirstPixel(2,1)+origCtInfo(1).ImageOrientationPatient(5)*origCtInfo(1).PixelSpacing(2)*double(origCtInfo(1).Rows-1))];
- zq = coordsOfFirstPixel(3,1):resolution.z: coordsOfFirstPixel(3,end);
-end
-
-% set up grid matrices - implicit dimension permuation (X Y Z-> Y X Z)
-% Matlab represents internally in the first matrix dimension the
-% ordinate axis and in the second matrix dimension the abscissas axis
-[ X, Y, Z] = meshgrid(x,y,z);
-[Xq, Yq, Zq] = meshgrid(xq,yq,zq);
-
-% interpolate cube - cube is now stored in Y X Z
-interpCt.cubeIV{1} = interp3(X,Y,Z,double(origCt),Xq,Yq,Zq);
-
-% some meta information
-interpCt.resolution = resolution;
-
-interpCt.x = xq;
-interpCt.y = yq;
-interpCt.z = zq;
-
-interpCt.cubeDim = [numel(yq) numel(xq) numel(zq)];
-interpCt.numOfCtScen = 1;
diff --git a/dicom/matRad_interpDicomDoseCube.m b/dicom/matRad_interpDicomDoseCube.m
deleted file mode 100644
index e6fe22513..000000000
--- a/dicom/matRad_interpDicomDoseCube.m
+++ /dev/null
@@ -1,101 +0,0 @@
-function [ dose ] = matRad_interpDicomDoseCube( ct, currDose )
-% matRad function to interpolate a given Dicom Dose Cube dicom RTDOSE data
-%
-% call
-% [ dose ] = matRad_interpDicomDoseCube( ct, currDose )
-%
-% input
-% ct: ct imported by the matRad_importDicomCt function
-% currDose: one (of several) dose cubes which should be interpolated
-%
-% output
-% dose: struct with different actual current dose cube and several
-% meta data
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% read information out of the RT file
-dosefile = currDose{1};
-if verLessThan('matlab','9')
- doseInfo = dicominfo(dosefile);
-else
- doseInfo = dicominfo(dosefile,'UseDictionaryVR',true);
-end
-
-% read the dosefile itself
-dosedata = dicomread(dosefile);
-dose.cube = double(dosedata);
-% dose.cube = single(dosedata);
-
-% give it an internal name
-dose.internalName = currDose{12};
-
-% read out the resolution
-dose.resolution.x = doseInfo.PixelSpacing(1);
-dose.resolution.y = doseInfo.PixelSpacing(2);
-dose.resolution.z = doseInfo.SliceThickness;
-
-% target resolution is ct.resolution
-target_resolution = ct.resolution;
-
-% convert dosedata to 3-D cube
-dose.cube = squeeze(dose.cube(:,:,1,:));
-
-% ct resolution is target resolution, now convert to new cube;
-
-% generating grid vectors
-x = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ...
- doseInfo.PixelSpacing(1) * double([0:doseInfo.Columns - 1]);
-y = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ...
- doseInfo.PixelSpacing(2) * double([0:doseInfo.Rows - 1]);
-z = [doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector];
-
-% set up grid matrices - implicit dimension permuation (X Y Z-> Y X Z)
-% Matlab represents internally in the first matrix dimension the
-% ordinate axis and in the second matrix dimension the abscissas axis
-[ X, Y, Z] = meshgrid(x,y,z);
-[Xq, Yq, Zq] = meshgrid(ct.x,ct.y,ct.z);
-
-% get GridScalingFactor
-gridScale = double(doseInfo.DoseGridScaling);
-% rescale dose.cube
-dose.cube = gridScale * dose.cube;
-
-% interpolation to ct grid - cube is now stored in Y X Z
-dose.cube = interp3(X,Y,Z,dose.cube,Xq,Yq,Zq,'linear',0);
-
-% write new parameters
-dose.resolution = ct.resolution;
-dose.x = ct.x;
-dose.y = ct.y;
-dose.z = ct.z;
-
-% write Dicom-Tags
-dose.dicomInfo.PixelSpacing = [target_resolution.x; ...
- target_resolution.y];
-dose.dicomInfo.ImagePositionPatient = [min(dose.x); min(dose.y); min(dose.z)];
-dose.dicomInfo.SliceThickness = target_resolution.z;
-dose.dicomInfo.ImageOrientationPatient = doseInfo.ImageOrientationPatient;
-dose.dicomInfo.DoseType = doseInfo.DoseType;
-dose.dicomInfo.DoseSummationType = doseInfo.DoseSummationType;
-%dose.dicomInfo.InstanceNumber = doseInfo.InstanceNumber; %Not
-%always given
-dose.dicomInfo.SOPClassUID = doseInfo.SOPClassUID;
-dose.dicomInfo.SOPInstanceUID = doseInfo.SOPInstanceUID;
-dose.dicomInfo.ReferencedRTPlanSequence = doseInfo.ReferencedRTPlanSequence;
-
-end
diff --git a/dicom/matRad_progress.m b/dicom/matRad_progress.m
deleted file mode 100644
index ff6f8a352..000000000
--- a/dicom/matRad_progress.m
+++ /dev/null
@@ -1,49 +0,0 @@
-function matRad_progress(currentIndex, totalNumberOfEvaluations)
-% matRad progress bar
-%
-% call
-% matRad_progress(currentIndex, totalNumberOfEvaluations)
-%
-% input
-% currentIndex: current iteration index
-% totalNumberOfEvaluations: maximum iteration index
-%
-% output
-% graphical display of progess. make sure there is no other output
-% written during the loop to prevent confusion
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% If it's not the first step, erase the stuff printed before
-if (currentIndex == 1 || nargin > 2)
- fprintf('Progress: ');
-end
-
-if (currentIndex > 1 && nargin < 3)
- Length = numel(sprintf('%3.2f %%',(currentIndex-1)/totalNumberOfEvaluations*100));
- fprintf(repmat('\b',1,Length));
-end
-
-% Print the progress tool
-fprintf('%3.2f %%',currentIndex/totalNumberOfEvaluations*100);
-
-% After the last iteration print a newline command
-if (currentIndex == totalNumberOfEvaluations)
- fprintf('\n');
-end
-
-end
diff --git a/dicom/matRad_scanDicomImportFolder.m b/dicom/matRad_scanDicomImportFolder.m
deleted file mode 100644
index 25a37a9ce..000000000
--- a/dicom/matRad_scanDicomImportFolder.m
+++ /dev/null
@@ -1,235 +0,0 @@
-function [ fileList, patientList ] = matRad_scanDicomImportFolder( patDir )
-% matRad function to scan a folder for dicom data
-%
-% call
-% [ fileList, patientList ] = matRad_scanDicomImportFolder( patDir )
-%
-% input
-% patDir: folder to be scanned
-%
-% output
-% fileList: matlab struct with a list of dicom files including meta
-% infomation (type, series number etc.)
-% patientList: list of patients with dicom data in the folder
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-matRad_cfg = MatRad_Config.instance();
-
-%% print current status of the import script
-matRad_cfg.dispInfo('Dose series matched to the different plans are displayed and could be selected.\n');
-matRad_cfg.dispInfo('Rechecking of correct matching procedure is recommended.\n');
-
-global warnDlgDICOMtagShown;
-warnDlgDICOMtagShown = false;
-
-%% get all files in search directory
-
-% dicom import needs image processing toolbox -> check if available
-v = ver;
-if ~license('checkout','image_toolbox')
- matRad_cfg.dispError('Image Processing Toolbox and/or corresponding license not available');
-elseif ~any(strcmp('Image Processing Toolbox', {v.Name}))
- matRad_cfg.dispError('Image Processing Toolbox not installed');
-end
-
-fileList = matRad_listAllFiles(patDir);
-
-if ~isempty(fileList)
- %% check for dicom files and differentiate patients, types, and series
- numOfFiles = numel(fileList(:,1));
- h = waitbar(0,'Please wait...');
- %h.WindowStyle = 'Modal';
- steps = numOfFiles;
- for i = numOfFiles:-1:1
- waitbar((numOfFiles+1-i) / steps)
- try % try to get DicomInfo
- if verLessThan('matlab','9')
- info = dicominfo(fileList{i});
- else
- info = dicominfo(fileList{i},'UseDictionaryVR',true);
- end
- catch
- fileList(i,:) = [];
- matRad_progress(numOfFiles+1-i, numOfFiles);
- continue;
- end
- try
- fileList{i,2} = info.Modality;
- catch
- fileList{i,2} = NaN;
- end
-
- fileList = parseDicomTag(fileList,info,'PatientID',i,3);
-
- switch fileList{i,2}
- case 'CT'
-
- fileList = parseDicomTag(fileList,info,'SeriesInstanceUID',i,4);
-
- case {'RTPLAN','RTDOSE','RTSTRUCT'}
-
- fileList = parseDicomTag(fileList,info,'SOPInstanceUID',i,4);
-
- otherwise
-
- fileList = parseDicomTag(fileList,info,'SeriesInstanceUID',i,4);
-
- end
-
- fileList = parseDicomTag(fileList,info,'SeriesNumber',i,5,@seriesnum2str); %We want to make sure the series number is stored as string
- fileList = parseDicomTag(fileList,info,'FamilyName',i,6);
- fileList = parseDicomTag(fileList,info,'GivenName',i,7);
- fileList = parseDicomTag(fileList,info,'PatientBirthDate',i,8);
-
- try
- if strcmp(info.Modality,'CT')
- fileList{i,9} = num2str(info.PixelSpacing(1));
- else
- fileList{i,9} = NaN;
- end
- catch
- fileList{i,9} = NaN;
- end
- try
- if strcmp(info.Modality,'CT')
- fileList{i,10} = num2str(info.PixelSpacing(2));
- else
- fileList{i,10} = NaN;
- end
- catch
- fileList{i,10} = NaN;
- end
- try
- if strcmp(info.Modality,'CT')
- %usually the Attribute should be SliceThickness, but it
- %seems like some data uses "SpacingBetweenSlices" instead.
- if isfield(info,'SliceThickness') && ~isempty(info.SliceThickness)
- fileList{i,11} = num2str(info.SliceThickness);
- elseif isfield(info,'SpacingBetweenSlices')
- fileList{i,11} = num2str(info.SpacingBetweenSlices);
- else
- matRad_cfg.dispError('Could not identify spacing between slices since neither ''SliceThickness'' nor ''SpacingBetweenSlices'' are specified');
- end
- else
- fileList{i,11} = NaN;
- end
- catch
- fileList{i,11} = NaN;
- end
- try
- if strcmp(info.Modality,'RTDOSE')
- dosetext_helper = strcat('Instance','_', num2str(info.InstanceNumber),'_', ...
- info.DoseSummationType, '_', info.DoseType);
- fileList{i,12} = dosetext_helper;
- else
- fileList{i,12} = NaN;
- end
- catch
- fileList{i,12} = NaN;
- end
- % writing corresponding dose dist.
- try
- if strcmp(fileList{i,2},'RTPLAN')
- corrDose = [];
- numDose = length(fieldnames(info.ReferencedDoseSequence));
- for j = 1:numDose
- fieldName = strcat('Item_',num2str(j));
- corrDose{j} = info.ReferencedDoseSequence.(fieldName).ReferencedSOPInstanceUID;
- end
- fileList{i,13} = corrDose;
- else
- fileList{i,13} = {'NaN'};
- end
-
- catch
- fileList{i,13} = {'NaN'};
- end
- matRad_progress(numOfFiles+1-i, numOfFiles);
-
- end
- close(h)
-
- if ~isempty(fileList)
- patientList = unique(fileList(:,3));
-
- if isempty(patientList)
- msgbox('No patient found with DICOM CT _and_ RT structure set in patient directory!', 'Error','error');
- end
- else
- msgbox('No DICOM files found in patient directory!', 'Error','error');
- %h.WindowStyle = 'Modal';
- %error('No DICOM files found in patient directory');
- end
-else
- msgbox('Search folder empty!', 'Error','error');
-
-end
-
-clear warnDlgDICOMtagShown;
-
-end
-
-function fileList = parseDicomTag(fileList,info,tag,row,column,parsefcn)
-
-global warnDlgDICOMtagShown;
-
-defaultPlaceHolder = '001';
-
-if nargin < 6
- parsefcn = @(x) x;
-end
-
-try
- if isfield(info,tag)
- if ~isempty(info.(tag))
- fileList{row,column} = parsefcn(info.(tag));
- else
- fileList{row,column} = defaultPlaceHolder;
- end
- else
- fileList{row,column} = defaultPlaceHolder;
- end
-catch
- fileList{row,column} = NaN;
-end
-
-if ~warnDlgDICOMtagShown && strcmp(fileList{row,column},defaultPlaceHolder) && (column == 3 || column == 4)
-
- dlgTitle = 'Dicom Tag import';
- dlgQuestion = ['matRad_scanDicomImportFolder: Could not parse dicom tag: ' tag '. Using placeholder ' defaultPlaceHolder ' instead. Please check imported data carefully! Do you want to continue?'];
- answer = questdlg(dlgQuestion,dlgTitle,'Yes','No', 'Yes');
-
- warnDlgDICOMtagShown = true;
-
- switch answer
- case 'No'
- matRad_cfg.dispError('Inconsistency in DICOM tags')
- end
-end
-
-end
-
-function value = seriesnum2str(value)
- if isnumeric(value)
- value = num2str(value);
- end
-end
-
-
-
-
diff --git a/dicom/matrad_logo.png b/dicom/matrad_logo.png
deleted file mode 100644
index 296e1e858..000000000
Binary files a/dicom/matrad_logo.png and /dev/null differ
diff --git a/examples/html/matRad_example8_protonsRobust.pdf b/examples/html/matRad_example8_protonsRobust.pdf
new file mode 100644
index 000000000..e783b500b
Binary files /dev/null and b/examples/html/matRad_example8_protonsRobust.pdf differ
diff --git a/examples/matRad_example10_4DphotonRobust.m b/examples/matRad_example10_4DphotonRobust.m
new file mode 100644
index 000000000..4545a2e7f
--- /dev/null
+++ b/examples/matRad_example10_4DphotonRobust.m
@@ -0,0 +1,303 @@
+%% Example: 4D robust Treatment Planning with photons
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% In this example we will
+% (i) create a small artifical phantom
+% (ii) add a movement to the phantom to create a 4D CT
+% (iii) create a photon treatment plan with two beams
+% (iv) perform dose calculation on each 4D CT
+% (iv) perform first a fluence optimization on the first CT scenario and then secondly
+% another fluence optimization using the composite worst case paradigm
+% considering all 4D CTs
+% (v) visualise all individual dose scenarios
+% (vi) sample discrete scenarios from Gaussian uncertainty assumptions
+
+%% set matRad runtime configuration
+matRad_rc
+
+%% Create an artifiical CT image series
+xDim = 150;
+yDim = 150;
+zDim = 50;
+
+ct.cubeDim = [xDim yDim zDim];
+ct.resolution.x = 2; % mm
+ct.resolution.y = 2; % mm
+ct.resolution.z = 3; % mm
+ct.numOfCtScen = 1;
+
+% create an ct image series with zeros - it will be filled later
+ct.cubeHU{1} = ones(ct.cubeDim) * -1024;
+
+%% Create the VOI data for the phantom
+% Now we define two structures for the phantom
+ixOAR = 1;
+ixPTV = 2;
+
+% define general VOI properties
+cst{ixOAR,1} = 0;
+cst{ixOAR,2} = 'contour';
+cst{ixOAR,3} = 'OAR';
+cst{ixPTV,1} = 1;
+cst{ixPTV,2} = 'target';
+cst{ixPTV,3} = 'TARGET';
+
+% define optimization parameter for both VOIs
+cst{ixOAR,5}.TissueClass = 1;
+cst{ixOAR,5}.alphaX = 0.1000;
+cst{ixOAR,5}.betaX = 0.0500;
+cst{ixOAR,5}.Priority = 2; % overlap priority for optimization - a higher number corresponds to a lower priority
+cst{ixOAR,5}.Visible = 1;
+
+cst{ixOAR,6}{1} = struct(DoseObjectives.matRad_SquaredOverdosing(10,30));
+
+cst{ixPTV,5}.TissueClass = 1;
+cst{ixPTV,5}.alphaX = 0.1000;
+cst{ixPTV,5}.betaX = 0.0500;
+cst{ixPTV,5}.Priority = 1; % overlap priority for optimization - a lower number corresponds to a higher priority
+cst{ixPTV,5}.Visible = 1;
+
+cst{ixPTV,6}{1} = struct(DoseObjectives.matRad_SquaredDeviation(50,60));
+
+
+%% Lets create a cubic phantom
+% first define the dimensions of the OAR
+cubeHelper = zeros(ct.cubeDim);
+xLowOAR = round(xDim/2 - xDim/6);
+xHighOAR = round(xDim/2 + xDim/6);
+yLowOAR = round(yDim/2 - yDim/6);
+yHighOAR = round(yDim/2 + yDim/6);
+zLowOAR = round(zDim/2 - zDim/4);
+zHighOAR = round(zDim/2 + zDim/4);
+
+for x = xLowOAR:1:xHighOAR
+ for y = yLowOAR:1:yHighOAR
+ for z = zLowOAR:1:zHighOAR
+ cubeHelper(x,y,z) = 1;
+ end
+ end
+end
+
+% extract the linear voxel indices and save it in the cst
+cst{ixOAR,4}{1} = find(cubeHelper);
+
+% second the PTV
+cubeHelper = zeros(ct.cubeDim);
+radiusPTV = xDim/14;
+for x = 1:xDim
+ for y = 1:yDim
+ for z = 1:zDim
+ currPost = [x y z] - round([ct.cubeDim./2]);
+ if sqrt(sum(currPost.^2)) < radiusPTV
+ cubeHelper(x,y,z) = 1;
+ end
+ end
+ end
+end
+
+% extract the linear voxel indices and save it in the cst
+cst{ixPTV,4}{1} = find(cubeHelper);
+
+% assign relative electron densities
+vIxOAR = cst{ixOAR,4}{1};
+vIxPTV = cst{ixPTV,4}{1};
+
+ct.cubeHU{1}(vIxOAR) = 300; % assign HU of soft tissue
+ct.cubeHU{1}(vIxPTV) = 0; % assign HU of water
+
+%% add motion to the phantom and artificially create a 4D CT with vector fields
+amplitude = [5 0 0]; % [voxels]
+numOfCtScen = 5;
+motionPeriod = 2.5; % [s]
+
+[ct,cst] = matRad_addMovement(ct, cst,motionPeriod, numOfCtScen, amplitude,'visBool',true);
+
+% show the deformation vector field
+slice = 25; % select a specific slice and to plot the vector field
+[a,xDim,yDim,zDim] = size(ct.dvf{1});
+
+[mX,mY] = meshgrid(1:xDim,1:yDim);
+
+figure,
+for ctPhase = 1:ct.numOfCtScen
+ clf;
+ xVectorField = squeeze(ct.dvf{ctPhase}(1,:,:,slice)); % retrieve the deformation vector field in x-direction of slice 25
+ yVectorField = squeeze(ct.dvf{ctPhase}(2,:,:,slice)); % retrieve the deformation vector field in y-direction of slice 25
+ quiver(mX,mY,yVectorField,xVectorField); title(['deformation vector field of phase ' num2str(ctPhase)]),
+ set(gca,'XLim',[70 80]);set(gca,'YLim',[70 80]);
+ % flip y axis to be consistent with the previous plot
+ ax = gca; set(ax,'YDir','reverse');
+ pause(0.5);
+end
+
+% magnitude of the vector field should change over ct scenarios
+% vector field refers to the first (initial) ct scenario
+% investigate in the difference of 4D dose accumulatino
+
+
+% clear helper variables to get clean workspace
+clear x y z xDim yDim zDim xHighOAR xLowOAR xHighOAR yHighOAR yLowOAR zHighOAR zLowOAR vIxOAR vIxPTV cubeHelper currPost radiusPTV
+%% Treatment Plan
+% The next step is to define your treatment plan labeled as 'pln'. This
+% structure requires input from the treatment planner and defines the most
+% important cornerstones of your treatment plan.
+%%
+% First of all, we need to define what kind of radiation modality we would
+% like to use. Possible values are photons, protons or carbon. In this
+% example we would like to use protons for robust treatment planning. Next, we
+% need to define a treatment machine to correctly load the corresponding
+% base data. matRad features generic base data in the file
+% 'carbon_Generic.mat'; consequently the machine has to be set accordingly
+pln.radiationMode = 'photons';
+pln.machine = 'Generic';
+
+%%
+% Define the biological optimization model for treatment planning along
+% with the quantity that should be used for optimization. Possible model values
+% are:
+% 'none': physical optimization;
+% 'constRBE': constant RBE of 1.1;
+% 'MCN': McNamara-variable RBE model for protons;
+% 'WED': Wedenberg-variable RBE model for protons
+% 'LEM': Local Effect Model
+% and possible quantityOpt are 'physicalDose', 'effect' or 'RBExD'.
+% As we use protons, we use a constant RBE of 1.1.
+modelName = 'none';
+quantityOpt = 'physicalDose';
+
+%%
+% The remaining plan parameters are set like in the previous example files
+pln.numOfFractions = 20;
+pln.propStf.gantryAngles = [0 90];
+pln.propStf.couchAngles = [0 0];
+pln.propStf.bixelWidth = 5;
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+pln.propOpt.runDAO = 0;
+pln.propOpt.runSequencing = 0;
+
+% dose calculation settings
+pln.propDoseCalc.doseGrid.resolution.x = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = 5; % [mm]
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName);
+
+% retrieve 9 worst case scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
+
+%% Generate Beam Geometry STF
+stf = matRad_generateStf(ct,cst,pln);
+
+%% Dose Calculation
+dij = matRad_calcPhotonDose(ct,stf,pln,cst);
+
+%% Inverse Optimization for IMPT based on RBE-weighted dose
+% The goal of the fluence optimization is to find a set of bixel/spot
+% weights which yield the best possible dose distribution according to the
+% clinical objectives and constraints underlying the radiation treatment.
+%
+
+%Activate 4D Optimization
+%pln.propOpt.scen4D = 'all';
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+
+%% Trigger robust optimization
+% Make the objective to a composite worst case objective
+cst{ixPTV,6}{1}.robustness = 'COWC';
+cst{ixOAR,6}{1}.robustness = 'COWC';
+
+%Activate 4D Optimization
+pln.propOpt.scen4D = 'all';
+
+% voxel wise worst case 'VWWC' does not work for 4D robust optimization
+
+% parameters for stochastic optimization
+%cst{ixPTV,6}.robustness = 'STOCH';
+%cst{ixOAR,6}.robustness = 'STOCH';
+%pln.multScen.scenProb = (1/ct.numOfCtScen) * ones(ct.numOfCtScen,1); % assign probabilities to 4D scenarios
+
+% % parameters for objective wise worst case
+%cst{ixPTV,6}.robustness = 'OWC';
+%cst{ixOAR,6}.robustness = 'OWC';
+
+resultGUIrobust = matRad_fluenceOptimization(dij,cst,pln);
+
+% add resultGUIrobust dose cubes to the existing resultGUI structure to allow the visualization in the GUI
+resultGUI = matRad_appendResultGUI(resultGUI,resultGUIrobust,0,'robust');
+
+%% calc 4D dose
+totalPhaseMatrix = ones(dij.totalNumOfBixels,ct.numOfCtScen)/ct.numOfCtScen; % the total phase matrix determines a mapping what fluence will be delivered in the which phase
+totalPhaseMatrix = bsxfun(@times,totalPhaseMatrix,resultGUIrobust.w); % equally distribute the fluence over all fluences
+
+[resultGUIrobust4D, timeSequence] = matRad_calc4dDose(ct, pln, dij, stf, cst, resultGUIrobust,totalPhaseMatrix);
+
+%% Visualize results
+
+plane = 3;
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
+maxDose = max([max(resultGUI.([quantityOpt])(:,:,slice)) max(resultGUIrobust.([quantityOpt])(:,:,slice))])+1e-4;
+doseIsoLevels = linspace(0.1 * maxDose,maxDose,10);
+figure,
+subplot(121),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.([quantityOpt '_' 'beam1']) ,plane,slice,[],[],colorcube,[],[0 maxDose],doseIsoLevels);title('conventional plan - beam1')
+subplot(122),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.([quantityOpt]) ,plane,slice,[],[],colorcube,[],[0 maxDose],doseIsoLevels);title('conventional plan')
+
+figure
+subplot(121),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrobust.([quantityOpt '_' 'beam1']),plane,slice,[],[],colorcube,[],[0 maxDose],doseIsoLevels);title('robust plan - beam1')
+subplot(122),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrobust.([quantityOpt]),plane,slice,[],[],colorcube,[],[0 maxDose],doseIsoLevels);title('robust plan')
+
+
+figure
+subplot(131),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrobust4D.([quantityOpt]),plane,slice,[],[],colorcube,[],[0 maxDose],doseIsoLevels);title('robust plan')
+subplot(132),matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrobust4D.('accPhysicalDose'),plane,slice,[],[],colorcube,[],[0 maxDose],doseIsoLevels);title('robust plan dose accumulation')
+
+% create an interactive plot to slide through individual scnearios
+f = figure; title('individual scenarios');
+numScen = 1;
+maxDose = max(max(resultGUIrobust.([quantityOpt '_scen' num2str(round(numScen))])(:,:,slice)))+0.2;
+doseIsoLevels = linspace(0.1 * maxDose,maxDose,10);
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrobust.([quantityOpt '_scen' num2str(round(numScen))]),plane,slice,[],[],colorcube,[],[0 maxDose],doseIsoLevels);
+
+[env,envver] = matRad_getEnvironment();
+if strcmp(env,'MATLAB') || str2double(envver(1)) >= 5
+ b = uicontrol('Parent',f,'Style','slider','Position',[50,5,419,23],...
+ 'value',numScen, 'min',1, 'max',pln.multScen.totNumScen,'SliderStep', [1/(pln.multScen.totNumScen-1) , 1/(pln.multScen.totNumScen-1)]);
+ set(b,'Callback',@(es,ed) matRad_plotSliceWrapper(gca,ct,cst,round(get(es,'Value')),resultGUIrobust.([quantityOpt '_scen' num2str(round(get(es,'Value')))]),plane,slice,[],[],colorcube,[],[0 maxDose],doseIsoLevels));
+end
+
+%% Indicator calculation and show DVH and QI
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln);
+
+%% Perform sampling
+% select structures to include in sampling; leave empty to sample dose for all structures
+
+% sampling does not know on which scenario sampling should be performed
+structSel = {}; % structSel = {'PTV','OAR1'};
+[caSamp, mSampDose, plnSamp, resultGUInomScen] = matRad_sampling(ct,stf,cst,pln,resultGUI.w,structSel);
+[cstStat, resultGUISamp, meta] = matRad_samplingAnalysis(ct,cst,plnSamp,caSamp, mSampDose, resultGUInomScen);
+
+[caSampRob, mSampDoseRob, plnSampRob, resultGUInomScen] = matRad_sampling(ct,stf,cst,pln,resultGUIrobust.w,structSel);
+[cstStatRob, resultGUISampRob, metaRob] = matRad_samplingAnalysis(ct,cst,plnSampRob,caSampRob, mSampDoseRob, resultGUInomScen);
+
+figure,title('std dose cube based on sampling - conventional')
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUISamp.stdCube,plane,slice,[],[],colorcube,[],[0 max(resultGUISamp.stdCube(:))]);
+
+figure,title('std dose cube based on sampling - robust')
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUISampRob.stdCube,plane,slice,[],[],colorcube,[],[0 max(resultGUISampRob.stdCube(:))]);
+
+
+
diff --git a/examples/matRad_example11_helium.m b/examples/matRad_example11_helium.m
new file mode 100644
index 000000000..4a217e021
--- /dev/null
+++ b/examples/matRad_example11_helium.m
@@ -0,0 +1,103 @@
+%% Example: Proton Treatment Plan with subsequent Isocenter shift
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% In this example we will show
+% (i) how to load patient data into matRad
+% (ii) how to setup a helium dose calculation
+% (iii) how to inversely optimize the pencil beam intensities directly from command window in MATLAB.
+
+%% set matRad runtime configuration
+matRad_rc
+
+%% Patient Data Import
+load('BOXPHANTOM.mat');
+
+%% Treatment Plan
+% The next step is to define your treatment plan labeled as 'pln'. This
+% structure requires input from the treatment planner and defines the most
+% important cornerstones of your treatment plan.
+
+%%
+% First of all, we need to define what kind of radiation modality we would
+% like to use. Possible values are photons, protons or carbon. In this
+% example we would like to use protons for treatment planning. Next, we
+% need to define a treatment machine to correctly load the corresponding
+% base data. matRad features generic base data in the file
+% 'proton_Generic.mat'; consequently the machine has to be set accordingly
+pln.radiationMode = 'helium';
+pln.machine = 'Generic';
+
+%%
+% for particles it is possible to also calculate the LET disutribution
+% alongside the physical dose. Therefore you need to activate the
+% corresponding option during dose calculcation
+pln.propDoseCalc.calcLET = true;
+
+%%
+% Now we have to set the remaining plan parameters.
+pln.numOfFractions = 30;
+pln.propStf.gantryAngles = [0];
+pln.propStf.couchAngles = [0];
+pln.propStf.bixelWidth = 5;
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+pln.propOpt.runDAO = 0;
+pln.propOpt.runSequencing = 0;
+
+% Define the flavor of biological optimization for treatment planning along
+% with the quantity that should be used for optimization. As we use helium,
+% we follow a data-driven RBE parametrization to obtbain the variable
+% relative biological effectiveness.
+
+quantityOpt = 'RBExD'; % either physicalDose / effect / RBExD
+modelName = 'HEL'; % none: for photons, protons, carbon constRBE: constant RBE model
+ % MCN: McNamara-variable RBE model for protons WED: Wedenberg-variable RBE model for protons
+ % LEM: Local Effect Model for carbon ions HEL: data-driven RBE parametrization for helium
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% dose calculation settings
+pln.propDoseCalc.doseGrid.resolution.x = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = 5; % [mm]
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
+
+%% Generate Beam Geometry STF
+stf = matRad_generateStf(ct,cst,pln);
+
+%% Dose Calculation
+% Lets generate dosimetric information by pre-computing dose influence
+% matrices for unit beamlet intensities. Having dose influences available
+% allows for subsequent inverse optimization.
+dij = matRad_calcParticleDose(ct,stf,pln,cst);
+
+%% Inverse Optimization for IMPT
+% The goal of the fluence optimization is to find a set of bixel/spot
+% weights which yield the best possible dose distribution according to the
+% clinical objectives and constraints underlying the radiation treatment
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+
+%% Plot the Resulting Dose Slice
+% Let's plot the transversal iso-center dose slice
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
+figure
+subplot(121),imagesc(resultGUI.physicalDose(:,:,slice)),colorbar,colormap(jet),title('physical dose')
+subplot(122),imagesc(resultGUI.RBExD(:,:,slice)),colorbar,colormap(jet),title('RBExDose')
+
+
+
diff --git a/examples/matRad_example12_simpleParticleMonteCarlo.m b/examples/matRad_example12_simpleParticleMonteCarlo.m
new file mode 100644
index 000000000..af4f6bcad
--- /dev/null
+++ b/examples/matRad_example12_simpleParticleMonteCarlo.m
@@ -0,0 +1,115 @@
+% matRad example script
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2020 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_rc
+
+% load patient data, i.e. ct, voi, cst
+%load TG119.mat
+load BOXPHANTOM.mat
+%load LIVER.mat
+%load PHANTOM_control.mat; ct.resolution.x = 2; ct.resolution.y = 2; ct.resolution.z = 2;
+
+% meta information for treatment plan
+pln.radiationMode = 'protons'; % either photons / protons / carbon
+%pln.machine = 'generic_TOPAS_cropped';
+pln.machine = 'generic_MCsquare';
+
+
+pln.numOfFractions = 1;
+
+% beam geometry settings
+pln.propStf.bixelWidth = 5; % [mm] / also corresponds to lateral spot spacing for particles
+pln.propStf.longitudinalSpotSpacing = 3;
+pln.propStf.gantryAngles = 0; % [?]
+pln.propStf.couchAngles = 0; % [?]
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+%pln.propStf.isoCenter = [51 0 51];
+
+% dose calculation settings
+pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = 3; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = 3; % [mm]
+%pln.propDoseCalc.doseGrid.resolution = ct.resolution;
+
+%Turn on to correct for nozzle-to-skin air WEPL in analytical calculation
+pln.propDoseCalc.airOffsetCorrection = true;
+
+%Biology
+modelName = 'none';
+quantityOpt = 'physicalDose';
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName);
+
+% optimization settings
+pln.propOpt.optimizer = 'IPOPT';
+
+
+pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles
+pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen'); % optimize on the nominal scenario
+
+%Enable/Disable use of range shifter (has effect only when we need to fill
+%up the low-range region)
+pln.propStf.useRangeShifter = false;
+pln.propStf.generator = 'ParticleSingleSpot';
+
+%Enable LET calculation
+pln.propDoseCalc.calcLET = true;
+
+% Enable/Disable local computation with TOPAS. Enabling this will generate
+% the necessary TOPAS files to run the simulation on any machine or server.
+% pln.propMC.externalCalculation = true;
+
+%% generate steering file
+stf = matRad_generateStf(ct,cst,pln);
+
+%% analytical dose calculation
+pln.propDoseCalc.engine = 'MCsquare';
+pln.propDoseCalc.numHistoriesPerBeamlet = 1e4;
+
+dij = matRad_calcDoseInfluence(ct, cst,stf, pln); %Calculate particle dose influence matrix (dij) with analytical algorithm
+
+resultGUI = matRad_calcCubes(ones(dij.totalNumOfBixels,1),dij); %Use uniform weights
+%resultGUI = matRad_fluenceOptimization(dij,cst,pln); %Optimize
+
+
+%% Monte Carlo dose calculation
+% select Monte Carlo engine ('MCsquare' very fast for physical protons, 'TOPAS' slow but versatile for everything else)
+pln.propDoseCalc.engine = 'MCsquare';
+%pln.propDoseCalc.engine = 'TOPAS';
+
+% set number of histories lower than default for this example (default: 1e8)
+pln.propDoseCalc.numHistoriesDirect = 1e3;
+%pln.propDoseCalc.externalCalculation = 'write';
+resultGUI_MC = matRad_calcDoseForward(ct,cst,stf,pln,resultGUI.w);
+
+%% Read an external calculation with TOPAS if externalCalculation was set to 'write'
+%pln.propDoseCalc.externalCalculation = resultGUI_MC.meta.TOPASworkingDir;
+%resultGUI_MC = matRad_calcDoseForward(ct,cst,stf,pln,resultGUI.w);
+
+%% Compare Dose
+resultGUI = matRad_appendResultGUI(resultGUI,resultGUI_MC,true,pln.propDoseCalc.engine);
+matRad_compareDose(resultGUI.physicalDose, resultGUI.(['physicalDose_' pln.propDoseCalc.engine]), ct, cst, [1, 1, 0] , 'off', pln, [2, 2], 3, 'global');
+
+
+%% Compare LET
+if isfield(resultGUI,'LET') && isfield(resultGUI_MC,'LET')
+ matRad_compareDose(resultGUI.LET, resultGUI.(['LET_' pln.propDoseCalc.engine]), ct, cst, [1, 1, 0] , 'off', pln, [2, 2], 1, 'global');
+end
+
+%% GUI
+matRadGUI
diff --git a/examples/matRad_example13_fitAnalyticalParticleBaseData.m b/examples/matRad_example13_fitAnalyticalParticleBaseData.m
new file mode 100644
index 000000000..9fa4b004e
--- /dev/null
+++ b/examples/matRad_example13_fitAnalyticalParticleBaseData.m
@@ -0,0 +1,226 @@
+%% Example: baseData fitting to mcSquare simulation
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ %% 1) CT & CST creation
+clear
+matRad_rc
+
+ixOAR = 1;
+ixPTV = 2;
+
+% define general VOI properties
+cst{ixOAR,1} = 0;
+cst{ixOAR,2} = 'contour';
+cst{ixOAR,3} = 'OAR';
+
+cst{ixPTV,1} = 1;
+cst{ixPTV,2} = 'target';
+cst{ixPTV,3} = 'TARGET';
+
+% define optimization parameter for both VOIs
+cst{ixOAR,5}.TissueClass = 1;
+cst{ixOAR,5}.alphaX = 0.1000;
+cst{ixOAR,5}.betaX = 0.0500;
+cst{ixOAR,5}.Priority = 2;
+cst{ixOAR,5}.Visible = 1;
+cst{ixOAR,5}.visibleColor = [1 0 0];
+cst{ixOAR,6}{1,1}.className = 'DoseObjectives.matRad_SquaredOverdosing';
+cst{ixOAR,6}{1,1}.parameters{1} = 5;
+cst{ixOAR,6}{1,1}.penalty = 100;
+
+
+cst{ixPTV,5}.TissueClass = 1;
+cst{ixPTV,5}.alphaX = 0.1000;
+cst{ixPTV,5}.betaX = 0.0500;
+cst{ixPTV,5}.Priority = 1;
+cst{ixPTV,5}.Visible = 1;
+cst{ixPTV,5}.visibleColor = [0 1 0];
+cst{ixPTV,6}{1,1}.className = 'DoseObjectives.matRad_SquaredOverdosing';
+cst{ixPTV,6}{1,1}.parameters{1} = 60;
+cst{ixPTV,6}{1,1}.penalty = 800;
+
+
+%% Create CT
+xDim = 1100;
+yDim = 250;
+zDim = 250;
+
+cubeDim = [xDim yDim zDim];
+ct.cubeDim = cubeDim;
+
+ct.cube{1} = ones(cubeDim) * 1;
+ct.cube{1}(1,1,1) = 0;
+
+ct.resolution.x = 0.32;
+ct.resolution.y = 0.32;
+ct.resolution.z = 0.32;
+
+ct.numOfCtScen = 1;
+
+
+%% Create a cubic phantom
+iso = [800,125,125];
+
+% create an ct image series with zeros
+ct.cubeHU{1} = ones(ct.cubeDim) * 0;
+ct.cubeHU{1}(1,1,1) = -1000;
+
+ct.hlut = [1,0;0,-1024];
+
+% create body of full phantom size
+body = ones(ct.cubeDim);
+cst{1,4}{1} = find(body == 1);
+
+% create target
+centerP_corr = iso;
+height_corr = 10;
+width_corr = 10;
+depth_corr = 10;
+
+target = zeros(ct.cubeDim);
+for i=-height_corr/2+1:height_corr/2
+ for j=-width_corr/2:width_corr/2
+ for k=-depth_corr/2:depth_corr/2
+ target(centerP_corr(1)+i,centerP_corr(2)+j,centerP_corr(3)+k) = 1;
+ end
+ end
+end
+cst{2,4}{1} = find(target == 1);
+
+disp('CT creation done!');
+clearvars -except ct cst matRad_cfg
+
+
+%% 2) MCsquare computation and baseData fitting
+
+% meta information for treatment plan
+pln.radiationMode = 'protons';
+
+% create meta machine data
+machine.meta.machine = 'example'; %name of the machine
+machine.meta.radiationMode = 'protons'; %modality
+machine.meta.dataType = 'singleGauss'; %singleGauss or doubleGauss
+machine.meta.created_on = date;
+machine.meta.created_by = 'matRad_example';
+machine.meta.SAD = (2218 + 1839) / 2; %This is the (virtual) source to axis distance
+machine.meta.BAMStoIsoDist = 420.0; %distance from beam nozzle ot isocenter
+machine.meta.LUT_bxWidthminFWHM = [0, Inf; 5 ,5]; %Specifies which minimum FWHM to use as spot sice for which ranges of lateral spot distance (here, each spot distance of 0 to to Inf gets at least 5mm wide spots
+machine.meta.fitAirOffset = 420.0; %Tells matRad how much "air" was considered during fitting. Set this to 0 if the fit is obtained in vacuum and no air transport is simulated up to the phantom. matRad assumes that the phantom starts at the isocenter.
+
+% Now add the example machine to the pln and then save it
+pln.machine = machine.meta.machine;
+pln.radiationMode = machine.meta.radiationMode;
+fileName = [pln.radiationMode '_' pln.machine];
+filePath = fullfile(matRad_cfg.userfolders{1},'machines',[fileName '.mat']);
+
+matRad_cfg.dispInfo('Saving temporary machine %s to %s\n',fileName,filePath);
+save(filePath,'machine','-v7');
+clear machine;
+
+
+% beam geometry settings
+pln.propStf.bixelWidth = 50; % [mm] / also corresponds to lateral spot spacing for particles
+pln.propStf.longitudinalSpotSpacing = 50;
+pln.propStf.gantryAngles = 0; % [?]
+pln.propStf.couchAngles = 0; % [?]
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+
+% set isoCenter at entrance of beam into phantom
+pln.propStf.isoCenter = [ct.cubeDim(2) / 2 * ct.resolution.y, ...
+ 0, ...
+ ct.cubeDim(3) / 2 * ct.resolution.z];
+
+% dose calculation settings
+pln.propDoseCalc.doseGrid.resolution.x = ct.resolution.x; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = ct.resolution.y; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = ct.resolution.z; % [mm]
+
+% optimization settings
+pln.propOpt.optimizer = 'IPOPT';
+pln.propOpt.bioOptimization = 'none'; % none: physical optimization; const_RBExD; constant RBE of 1.1;
+ % LEMIV_effect: effect-based optimization; LEMIV_RBExD: optimization of RBE-weighted dose
+pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles
+pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
+
+
+quantityOpt = 'physicalDose'; % either physicalDose / effect / RBExD
+modelName = 'none'; % none: for photons, protons, carbon constRBE: constant RBE model
+ % MCN: McNamara-variable RBE model for protons WED: Wedenberg-variable RBE model for protons
+ % LEM: Local Effect Model for carbon ions
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+
+%% generate steering file
+stf = matRad_generateSingleBixelStf(ct,cst,pln);
+
+% Select existing BDL file to load and fit
+pln.loadExistingBDL = 'BDL_matRad.txt';
+
+% read MC phase space data
+dataMC = importdata(pln.loadExistingBDL, ' ', 16);
+energyMC = dataMC.data(:, 1);
+spotMC = (dataMC.data(:, 6) + dataMC.data(:, 9)) / 2;
+divMC = (dataMC.data(:, 7) + dataMC.data(:,10)) / 2;
+corMC = (dataMC.data(:, 8) + dataMC.data(:,11)) / 2;
+
+%% Run Base Data Fitting
+
+% define energy range
+minEnergy = 70;
+maxEnergy = 225;
+nEnergy = 75;
+
+% Number of histories for the MC simulation
+pln.propMC.numHistories = 1e5;
+
+% We create a figure to display the fit
+hf = figure();
+
+% Here we loop over all energies we want to fit
+count = 1;
+for currentEnergy = linspace(minEnergy, maxEnergy, nEnergy)
+
+ % calculate sigma/spotsize at isocenter using MC phase space data
+ divNozzle = interp1(energyMC,divMC,currentEnergy);
+ corNozzle = interp1(energyMC,corMC,currentEnergy);
+ spotNozzle = interp1(energyMC,spotMC,currentEnergy);
+ z = 420;
+
+ mcData.divNozzle = divNozzle;
+ mcData.corNozzle = corNozzle;
+ mcData.spotNozzle = spotNozzle;
+ mcData.z = z;
+ spotIso = sqrt(spotNozzle^2 + 2 * (corNozzle*spotNozzle + divNozzle * z) * divNozzle * z - divNozzle^2*z^2);
+
+ % assign energy to stf and run MC simulation
+ stf.ray.energy = currentEnergy;
+
+ %% needs to use correct BDL file in calcParticleDoseMC
+ resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,1);
+
+ machine.data(count) = matRad_fitBaseData(resultGUI.physicalDose, ct.resolution, currentEnergy, mcData);
+
+ matRad_plotParticleBaseDataEntry(machine,count,hf);
+ count = count + 1;
+end
+
+%% save final machine
+matRad_cfg.dispInfo('Saving final machine %s to %s\n',fileName,filePath);
+save(filePath,'machine','-v7');
+clear machine;
diff --git a/examples/matRad_example14_spotRemoval.m b/examples/matRad_example14_spotRemoval.m
new file mode 100644
index 000000000..8f2fc546c
--- /dev/null
+++ b/examples/matRad_example14_spotRemoval.m
@@ -0,0 +1,108 @@
+%% Example: Proton Treatment Plan with subsequent spot removal for Monte Carlo
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2021 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% set matRad runtime configuration
+matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
+
+%% Patient Data Import
+% Let's begin with a clear Matlab environment and import the prostate
+% patient into your workspace
+
+load('PROSTATE.mat');
+
+%% Treatment Plan
+% The next step is to define your treatment plan labeled as 'pln'. This
+% structure requires input from the treatment planner and defines the most
+% important cornerstones of your treatment plan.
+
+%%
+% First of all, we need to define what kind of radiation modality we would
+% like to use. Possible values are photons, protons or carbon. In this
+% example we would like to use protons for treatment planning. Next, we
+% need to define a treatment machine to correctly load the corresponding
+% base data. matRad features generic base data in the file
+% 'proton_Generic.mat'; consequently the machine has to be set accordingly
+pln.radiationMode = 'protons';
+pln.machine = 'Generic';
+
+%%
+% for particles it is possible to also calculate the LET disutribution
+% alongside the physical dose. Therefore you need to activate the
+% corresponding option during dose calculcation
+pln.propDoseCalc.calcLET = 0;
+
+%%
+% Now we have to set the remaining plan parameters.
+pln.numOfFractions = 30;
+pln.propStf.gantryAngles = [90 270];
+pln.propStf.couchAngles = [0 0];
+pln.propStf.bixelWidth = 3;
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+pln.propOpt.runDAO = 0;
+pln.propOpt.runSequencing = 0;
+
+% Define the flavor of biological optimization for treatment planning along
+% with the quantity that should be used for optimization.
+
+quantityOpt = 'RBExD'; % either physicalDose / effect / RBExD
+modelName = 'constRBE'; % none: for photons, protons, carbon constRBE: constant RBE model
+ % MCN: McNamara-variable RBE model for protons WED: Wedenberg-variable RBE model for protons
+ % LEM: Local Effect Model for carbon ions
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
+
+% dose calculation settings
+pln.propDoseCalc.doseGrid.resolution.x = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = 5; % [mm]
+
+%% Generate Beam Geometry STF
+stf = matRad_generateStf(ct,cst,pln);
+
+%% Dose Calculation
+% Lets generate dosimetric information by pre-computing dose influence
+% matrices for unit beamlet intensities. Having dose influences available
+% allows for subsequent inverse optimization.
+dij = matRad_calcParticleDose(ct,stf,pln,cst);
+
+%% Inverse Optimization for IMPT
+% The goal of the fluence optimization is to find a set of bixel/spot
+% weights which yield the best possible dose distribution according to the
+% clinical objectives and constraints underlying the radiation treatment
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+
+%% Spot removal
+% instantiate spot removal class
+sr_cfg = MatRad_spotRemovalDij(dij,resultGUI.w);
+
+sr_cfg.removalMode = 'relative';
+sr_cfg.propSpotRemoval.relativeThreshold = 0.05;
+resultGUI2 = sr_cfg.reoptimize(cst,pln);
+
+% numOfRemovedSpots = sr_cfg.numOfRemovedSpots;
+
+% stf2 = sr_cfg.getStf(stf);
+% dij2 = sr_cfg.getDij;
+% weight2 = sr_cfg.getWeights;
+% weightLogical = sr_cfg.getLogical;
+
+%% Plot difference of the doses
+matRad_compareDose(resultGUI.RBExD,resultGUI2.RBExD,ct,cst);
+
+
diff --git a/examples/matRad_example15_brachy.m b/examples/matRad_example15_brachy.m
new file mode 100644
index 000000000..085d0609e
--- /dev/null
+++ b/examples/matRad_example15_brachy.m
@@ -0,0 +1,242 @@
+%% Example: Brachytheraphy Treatment Plan
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2021 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+%% List of contents
+% In this example we will show
+% (i) how to load patient data into matRad
+% (ii) how to setup an HDR brachy dose calculation and
+% (iii) how to inversely optimize holding position intensties
+% (iv) how to visually and quantitatively evaluate the result
+% (v) how to verify that functions do the right thing
+
+%% I Patient Data Import
+% Let's begin with a clear Matlab environment. Then, import the TG119
+% phantom into your workspace. The phantom is comprised of a 'ct' and 'cst'
+% structure defining the CT images and the structure set. Make sure the
+% matRad root directory with all its subdirectories is added to the Matlab
+% search path.
+
+matRad_rc;
+load 'PROSTATE.mat';
+
+%% I - update/set dose objectives for brachytherapy
+% The sixth column represents dose objectives and constraints for
+% optimization: First, the objective function for the individual structure
+% is chosen, its parameters denote doses that should not be tranceeded
+% towards higher or lower doses (SquaredOverdose, SquaredUnderdose) or
+% doses that are particularly aimed for (SquaredUnderDose).
+
+display(cst{6,6}{1});
+
+% Following frequently prescribed planning doses of 15 Gy
+% (https://pubmed.ncbi.nlm.nih.gov/22559663/) objectives can be updated to:
+
+% the planning target was changed to the clinical segmentation of the
+% prostate bed.
+cst{5,3} = 'TARGET';
+cst{5,6}{1} = struct(DoseObjectives.matRad_SquaredUnderdosing(100,15));
+cst{5,6}{2} = struct(DoseObjectives.matRad_SquaredOverdosing(100,17.5));
+cst{6,5}.Priority = 1;
+
+% In this example, the lymph nodes will not be part of the treatment:
+cst{7,6} = [];
+cst{7,3} = 'OAR';
+
+%A PTV is not needed, but we will use it to control the dose fall off
+cst{6,3} = 'OAR';
+cst{6,6}{1} = struct(DoseObjectives.matRad_SquaredOverdosing(100,12));
+cst{6,5}.Priority = 2;
+
+% Rectum Objective
+cst{1,6}{1} = struct(DoseObjectives.matRad_SquaredOverdosing(10,7.5));
+
+% Bladder Objective
+cst{8,6}{1} = struct(DoseObjectives.matRad_SquaredOverdosing(10,7.5));
+
+% Body NT objective
+cst{9,6}{1} = struct(DoseObjectives.matRad_MeanDose(1));
+
+
+%% II.1 Treatment Plan
+% The next step is to define your treatment plan labeled as 'pln'. This
+% matlab structure requires input from the treatment planner and defines
+% the most important cornerstones of your treatment plan.
+
+% First of all, we need to define what kind of radiation modality we would
+% like to use. Possible values are photons, protons or carbon
+% (external beam) or brachy as an invasive tratment option.
+% In this case we want to use brachytherapy. Then, we need to define a
+% treatment machine to correctly load the corresponding base data.
+% matRad includes example base data for HDR and LDR brachytherapy.
+% Here we will use HDR. By this means matRad will look for 'brachy_HDR.mat'
+% in our root directory and will use the data provided in there for
+% dose calculation.
+
+pln.radiationMode = 'brachy';
+pln.machine = 'HDR'; % 'LDR' or 'HDR' for brachy
+
+quantityOpt = 'physicalDose';
+modelName = 'none';
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
+% dose calculation settings
+%Choose BT Engine
+pln.propDoseCalc.engine = 'TG43';
+
+
+
+%% II.1 - needle and template geometry
+% Now we have to set some parameters for the template and the needles.
+% Let's start with the needles: Seed distance is the distance between
+% two neighbouring seeds or holding points on one needle or catheter. The
+% seeds No denotes how many seeds/holding points there are per needle.
+
+pln.propStf.needle.seedDistance = 10; % [mm]
+pln.propStf.needle.seedsNo = 6;
+
+%% II.1 - template position
+% The implantation is normally done through a 13 x 13 template from the
+% patients inferior, which is the negative z axis here.
+% The direction of the needles is defined by template normal.
+% Neighbour distances are called by bixelWidth, because this field is also
+% used for external beam therapy.
+% The needles will be positioned right under the target volume pointing up.
+
+
+pln.propStf.bixelWidth = 5; % [mm] template grid distance
+
+%Template Type
+pln.propStf.template.type = 'checkerboard'; % 'checkerboard' if template is created automatically
+ % 'manual' if template is needed as preset manually (see below)
+
+%Template Root - mass center of target in x and y and bottom in z
+pln.propStf.template.root = matRad_getTemplateRoot(ct,cst);
+
+% Here, we define active needles as 1 and inactive needles
+% as 0. This is the x-y plane and needles point in z direction.
+% A checkerboard pattern is frequantly used. The whole geometry will become
+% clearer when it is displayed in 3D view in the next section.
+if strcmp(pln.propStf.template.type,'manual')
+ pln.propStf.template.activeNeedles = [0 0 0 1 0 1 0 1 0 1 0 0 0;... % 7.0
+ 0 0 1 0 1 0 0 0 1 0 1 0 0;... % 6.5
+ 0 1 0 1 0 1 0 1 0 1 0 1 0;... % 6.0
+ 1 0 1 0 1 0 0 0 1 0 1 0 1;... % 5.5
+ 0 1 0 1 0 1 0 1 0 1 0 1 0;... % 5.0
+ 1 0 1 0 1 0 0 0 1 0 1 0 1;... % 4.5
+ 0 1 0 1 0 1 0 1 0 1 0 1 0;... % 4.0
+ 1 0 1 0 1 0 0 0 1 0 1 0 1;... % 4.5
+ 0 1 0 1 0 1 0 1 0 1 0 1 0;... % 3.0
+ 1 0 1 0 1 0 1 0 1 0 1 0 1;... % 2.5
+ 0 1 0 1 0 1 0 1 0 1 0 1 0;... % 2.0
+ 1 0 1 0 1 0 0 0 0 0 1 0 1;... % 1.5
+ 0 0 0 0 0 0 0 0 0 0 0 0 0]; % 1.0
+ %A a B b C c D d E e F f G
+end
+
+%% II.1 - dose calculation options
+% for dose calculation we use eather the 2D or the 1D formalism proposed by
+% TG 43. Also, set resolution of dose calculation and optimization.
+% If your system gets stuck with the resolution, you can lower it to 10 or
+% 20, just to get an initial result. Otherwise, reduce the number of
+% needles.
+% Calculation time will be reduced by one tenth when we define a dose
+% cutoff distance.
+
+
+pln.propDoseCalc.TG43approximation = '2D'; %'1D' or '2D'
+
+pln.propDoseCalc.doseGrid.resolution.x = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = 5; % [mm]
+
+
+
+% We can also use other solver for optimization than IPOPT. matRad
+% currently supports simulannealbnd from the MATLAB Global Optimization Toolbox. First we
+% check if the simulannealbnd-Solver is available, and if it is, we set in in the
+% pln.propOpt.optimizer varirable. Otherwise we set to the default
+% optimizer 'IPOPT'
+if matRad_OptimizerSimulannealbnd.IsAvailable()
+ pln.propOpt.optimizer = 'simulannealbnd';
+else
+ pln.propOpt.optimizer = 'IPOPT';
+end
+
+pln.propOpt.optimizer = 'IPOPT';
+%% II.1 - book keeping
+% Some field names have to be kept although they don't have a direct
+% relevance for brachy therapy.
+pln.propOpt.bioOptimization = 'none';
+pln.propOpt.runDAO = false;
+pln.propOpt.runSequencing = false;
+pln.propStf.gantryAngles = [];
+pln.propStf.couchAngles = [];
+pln.propStf.numOfBeams = 0;
+pln.numOfFractions = 1;
+
+%% II.1 - view plan
+% Et voila! Our treatment plan structure is ready. Lets have a look:
+disp(pln);
+
+
+%% II.2 Steering Seed Positions From STF
+% The steering file struct contains all needls/catheter geometry with the
+% target volume, number of needles, seeds and the positions of all needles
+% The one in the end enables visualization.
+
+stf = matRad_generateStf(ct,cst,pln);
+
+
+%% II.2 - view stf
+% The 3D view is interesting, but we also want to know how the stf struct
+% looks like.
+disp(stf);
+
+%% II.3 - Dose Calculation
+% Let's generate dosimetric information by pre-computing a dose influence
+% matrix for seed/holding point intensities. Having dose influences
+% available allows subsequent inverse optimization.
+% Don't get inpatient, this can take a few seconds...
+
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+
+%% III Inverse Optimization for brachy therapy
+% The goal of the fluence optimization is to find a set of holding point
+% times which yield the best possible dose distribution according to
+% the clinical objectives and constraints underlying the radiation
+% treatment. Once the optimization has finished, trigger to
+% visualize the optimized dose cubes.
+
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+matRadGUI;
+
+%% IV.1 Plot the Resulting Dose Slice
+% Let's plot the transversal iso-center dose slice
+
+slice = matRad_world2cubeIndex(matRad_getIsoCenter(cst,ct),ct);
+slice = slice(3);
+figure
+imagesc(resultGUI.physicalDose(:,:,slice)),colorbar, colormap(jet);
+
+%% IV.2 Obtain dose statistics
+% Two more columns will be added to the cst structure depicting the DVH and
+% standard dose statistics such as D95,D98, mean dose, max dose etc.
+[dvh,qi] = matRad_indicatorWrapper(cst,pln,resultGUI);
+
diff --git a/examples/matRad_example16_photonMC_MLC.m b/examples/matRad_example16_photonMC_MLC.m
new file mode 100644
index 000000000..d374964ee
--- /dev/null
+++ b/examples/matRad_example16_photonMC_MLC.m
@@ -0,0 +1,98 @@
+%% Example: Photon Treatment Plan using VMC++ dose calculation
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% In this example we will show
+% (i) how to load patient data into matRad
+% (ii) how to setup a photon dose calculation based on the VMC++ Monte Carlo algorithm
+% (iii) how to inversely optimize the beamlet intensities directly from command window in MATLAB.
+% (iv) how to visualize the result
+
+%% set matRad runtime configuration
+matRad_rc %If this throws an error, run it from the parent directory first to set the paths
+
+%% Patient Data Import
+% Let's begin with a clear Matlab environment and import the boxphantom
+% into your workspace.
+load('BOXPHANTOM.mat');
+
+%% Treatment Plan
+% The next step is to define your treatment plan labeled as 'pln'. This
+% structure requires input from the treatment planner and defines the most
+% important cornerstones of your treatment plan.
+
+pln.radiationMode = 'photons';
+pln.machine = 'Generic';
+pln.numOfFractions = 30;
+pln.propStf.gantryAngles = [0:72:359];
+pln.propStf.couchAngles = [0 0 0 0 0];
+%pln.propStf.gantryAngles = [0];
+%pln.propStf.couchAngles = [0];
+pln.propStf.bixelWidth = 10;
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+% Enable sequencing and direct aperture optimization (DAO).
+pln.propOpt.runSequencing = 1;
+pln.propOpt.runDAO = 1;
+
+quantityOpt = 'physicalDose';
+modelName = 'none';
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_NominalScenario(ct);
+% dose calculation settings
+pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = 3; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = 3; % [mm]
+
+%% Generate Beam Geometry STF
+stf = matRad_generateStf(ct,cst,pln);
+
+%% Dose Calculation
+
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+
+%% Inverse Optimization for IMRT
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+%% Sequencing
+% This is a multileaf collimator leaf sequencing algorithm that is used in
+% order to modulate the intensity of the beams with multiple static
+% segments, so that translates each intensity map into a set of deliverable
+% aperture shapes.
+resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,5,1);
+[pln,stf] = matRad_aperture2collimation(pln,stf,resultGUI.sequencing,resultGUI.apertureInfo);
+%% Aperture visualization
+% Use a matrad function to visualize the resulting aperture shapes
+matRad_visApertureInfo(resultGUI.apertureInfo)
+%% Plot the Resulting Dose Slice
+% Just let's plot the transversal iso-center dose slice
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
+figure,
+imagesc(resultGUI.physicalDose(:,:,slice)),colorbar, colormap(jet)
+
+%% Dose Calculation
+%resultGUI_MC = matRad_calcDoseInfluence(ct,cst,stf,pln);
+pln.propDoseCalc.engine = 'TOPAS';
+pln.propDoseCalc.beamProfile = 'phasespace';
+pln.propDoseCalc.externalCalculation = 'write';
+resultGUI_MC = matRad_calcDoseForward(ct,cst,stf,pln,resultGUI.w);
+
+%% readout
+waitforbuttonpress; %We will wait since we do need to do the external calculation first
+pln.propDoseCalc.externalCalculation = resultGUI_MC.meta.TOPASworkingDir;
+resultGUI_MC = matRad_calcDoseForward(ct,cst,stf,pln,resultGUI.w);
diff --git a/examples/matRad_example1_phantom.m b/examples/matRad_example1_phantom.m
index 5bd19775c..6e07a10cb 100644
--- a/examples/matRad_example1_phantom.m
+++ b/examples/matRad_example1_phantom.m
@@ -2,179 +2,70 @@
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
-% Copyright 2018 the matRad development team.
+% Copyright 2023 the matRad development team.
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-% In this example we will show
+%% In this example we will show
% (i) how to create arbitrary ct data (resolution, ct numbers)
% (ii) how to create a cst structure containing the volume of interests of the phantom
% (iii) generate a treatment plan for this phantom
+%% set matRad runtime configuration
+%clear all; %somewhat needed for the phantom builder
matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
+matRad_cfg = MatRad_Config.instance(); %This creates a matRad-Config object holding global configuration parameters
%% Create a CT image series
-xDim = 200;
-yDim = 200;
-zDim = 50;
-
-ct.cubeDim = [yDim xDim zDim]; % second cube dimension represents the x-coordinate
-ct.resolution.x = 2;
-ct.resolution.y = 2;
-ct.resolution.z = 3;
-ct.numOfCtScen = 1;
-
-% create an ct image series with zeros - it will be filled later
-ct.cubeHU{1} = ones(ct.cubeDim) * -1000; % assign HU of Air
+
+ctDim = [200,200,100]; % x,y,z dimensions
+ctResolution = [2,2,3]; % x,y,z the same here!
+
+%This uses the phantombuilder class, which helps to easily implement a
+%water phantom containing geometrical 3D shapes as targets and organs
+builder = matRad_PhantomBuilder(ctDim,ctResolution,1);
%% Create the VOI data for the phantom
-% Now we define structures a contour for the phantom and a target
-
-ixOAR = 1;
-ixPTV = 2;
-
-% define general VOI properties
-cst{ixOAR,1} = 0;
-cst{ixOAR,2} = 'contour';
-cst{ixOAR,3} = 'OAR';
-
-cst{ixPTV,1} = 1;
-cst{ixPTV,2} = 'target';
-cst{ixPTV,3} = 'TARGET';
-
-% define optimization parameter for both VOIs
-cst{ixOAR,5}.TissueClass = 1;
-cst{ixOAR,5}.alphaX = 0.1000;
-cst{ixOAR,5}.betaX = 0.0500;
-cst{ixOAR,5}.Priority = 2;
-cst{ixOAR,5}.Visible = 1;
-cst{ixOAR,5}.visibleColor = [0 0 0];
-
-% define objective as struct for compatibility with GNU Octave I/O
-cst{ixOAR,6}{1} = struct(DoseObjectives.matRad_SquaredOverdosing(10,30));
-
-cst{ixPTV,5}.TissueClass = 1;
-cst{ixPTV,5}.alphaX = 0.1000;
-cst{ixPTV,5}.betaX = 0.0500;
-cst{ixPTV,5}.Priority = 1;
-cst{ixPTV,5}.Visible = 1;
-cst{ixPTV,5}.visibleColor = [1 1 1];
-
-% define objective as struct for compatibility with GNU Octave I/O
-cst{ixPTV,6}{1} = struct(DoseObjectives.matRad_SquaredDeviation(800,60));
-
-%% Lets create either a cubic or a spheric phantom
-
-TYPE = 'spheric'; % either 'cubic' or 'spheric'
-
-% first the OAR
-cubeHelper = zeros(ct.cubeDim);
-
-switch TYPE
-
- case {'cubic'}
-
- xLowOAR = round(xDim/2 - xDim/4);
- xHighOAR = round(xDim/2 + xDim/4);
- yLowOAR = round(yDim/2 - yDim/4);
- yHighOAR = round(yDim/2 + yDim/4);
- zLowOAR = round(zDim/2 - zDim/4);
- zHighOAR = round(zDim/2 + zDim/4);
-
- for x = xLowOAR:1:xHighOAR
- for y = yLowOAR:1:yHighOAR
- for z = zLowOAR:1:zHighOAR
- cubeHelper(y,x,z) = 1;
- end
- end
- end
-
- case {'spheric'}
-
- radiusOAR = xDim/4;
-
- for x = 1:xDim
- for y = 1:yDim
- for z = 1:zDim
- currPost = [y x z] - round([ct.cubeDim./2]);
- if sqrt(sum(currPost.^2)) < radiusOAR
- cubeHelper(y,x,z) = 1;
- end
- end
- end
- end
-
-end
-
-% extract the voxel indices and save it in the cst
-cst{ixOAR,4}{1} = find(cubeHelper);
-
-
-% second the PTV
-cubeHelper = zeros(ct.cubeDim);
-
-switch TYPE
-
- case {'cubic'}
-
- xLowPTV = round(xDim/2 - xDim/8);
- xHighPTV = round(xDim/2 + xDim/8);
- yLowPTV = round(yDim/2 - yDim/8);
- yHighPTV = round(yDim/2 + yDim/8);
- zLowPTV = round(zDim/2 - zDim/8);
- zHighPTV = round(zDim/2 + zDim/8);
-
- cubeHelper = zeros(ct.cubeDim);
-
- for x = xLowPTV:1:xHighPTV
- for y = yLowPTV:1:yHighPTV
- for z = zLowPTV:1:zHighPTV
- cubeHelper(y,x,z) = 1;
- end
- end
- end
-
- case {'spheric'}
-
- radiusPTV = xDim/12;
-
- for x = 1:xDim
- for y = 1:yDim
- for z = 1:zDim
- currPost = [x y z] - round([ct.cubeDim./2]);
- if sqrt(sum(currPost.^2)) < radiusPTV
- cubeHelper(y,x,z) = 1;
- end
- end
- end
- end
-
-end
-
-
-
-% extract the voxel indices and save it in the cst
-cst{ixPTV,4}{1} = find(cubeHelper);
-
-
-% now we have ct data and cst data for a new phantom
-display(ct);
-display(cst);
-
-%% Assign relative electron densities
-vIxOAR = cst{ixOAR,4}{1};
-vIxPTV = cst{ixPTV,4}{1};
-
-ct.cubeHU{1}(vIxOAR) = 0;
-ct.cubeHU{1}(vIxPTV) = 0;
+%To add a VOI there are (so far) two different options
+%either a Box or a spherical Volume (either OAR or Target)
+%NOTE: The order in which the objectives are initialized matters!
+% In case of overlaps in the objectives, the firstly created objectives have
+% a higher priority! This means that if two VOI have an overlap with
+% different HU, then the value of the firstly initialized objective will be
+% set in the overlap region
+
+
+%define objectives for the VOI
+
+objective1 = struct(DoseObjectives.matRad_SquaredDeviation(800,45));
+objective2 = struct(DoseObjectives.matRad_SquaredOverdosing(400,0));
+objective3 = struct(DoseObjectives.matRad_SquaredOverdosing(10,0));
+
+builder.addSphericalTarget('Volume1',20,'objectives',objective1,'HU',0);
+builder.addBoxOAR('Volume2',[60,30,60],'offset',[0 -15 0],'objectives',objective2);
+builder.addBoxOAR('Volume3',[60,30,60],'offset',[0 15 0],'objectives',objective3);
+
+% This adds two Box OAR and one Spherical Target in the middle
+% For Box Volumes a name (here Volume2 and Volume3) has to be specified,
+% as well as the dimension of the Volume.
+% For spherical Volumes a name has to be specified, as well as the radius
+% of the sphere
+%(Optional) To move the VOI from the center of the ct an offset can be set.
+%(Optional) The objectives can already be set here, however this can also
+%be done later on
+%(Optional) The HU of the VOI can be set (normal value: 0 (water))
+
+
+%% Get the ct and cst (stored as properties of the phantomBuilder class)
+
+[ct,cst] = builder.getctcst();
%% Treatment Plan
% The next step is to define your treatment plan labeled as 'pln'. This
@@ -191,50 +82,67 @@
pln.machine = 'Generic';
%%
-% Define the flavor of biological optimization for treatment planning along
-% with the quantity that should be used for optimization. Possible values
-% are (none: physical dose based optimization; const_RBExD: constant RBE of 1.1;
-% LEMIV_effect: effect-based optimization; LEMIV_RBExD: optimization of
-% RBE-weighted dose. As we use photons, we select 'none' as we want to optimize the
-% physical dose.
-pln.propOpt.bioOptimization = 'none';
+% Define the biological optimization model for treatment planning along
+% with the quantity that should be used for optimization. Possible model values
+% are:
+% 'none': physical optimization;
+% 'constRBE': constant RBE of 1.1;
+% 'MCN': McNamara-variable RBE model for protons;
+% 'WED': Wedenberg-variable RBE model for protons
+% 'LEM': Local Effect Model
+% and possible quantityOpt are 'physicalDose', 'effect' or 'RBExD'.
+modelName = 'none';
+quantityOpt = 'physicalDose';
%%
% The remaining plan parameters are set like in the previous example files
pln.numOfFractions = 30;
-pln.propStf.gantryAngles = [0 45];
-pln.propStf.couchAngles = [0 0];
+pln.propStf.gantryAngles = [0:70:355];
+pln.propStf.couchAngles = zeros(size(pln.propStf.gantryAngles));
pln.propStf.bixelWidth = 5;
pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
pln.propOpt.runDAO = 0;
pln.propOpt.runSequencing = 0;
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName);
+
+% retrieve nominal scenario for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
+
% dose calculation settings
pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.y = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.z = 3; % [mm]
+%%
+matRadGUI;
%% Generate Beam Geometry STF
stf = matRad_generateStf(ct,cst,pln);
%% Dose Calculation
-dij = matRad_calcPhotonDose(ct,stf,pln,cst);
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+%% Export dij matrix
+%matRad_exportDij('dij.bin',dij,stf);
%% Inverse Optimization for intensity-modulated photon therapy
% The goal of the fluence optimization is to find a set of bixel/spot
% weights which yield the best possible dose distribution according to the
% clinical objectives and constraints underlying the radiation treatment.
resultGUI = matRad_fluenceOptimization(dij,cst,pln);
-
+%%
+matRadGUI
%% Plot the resulting dose slice
plane = 3;
-slice = round(pln.propStf.isoCenter(1,3)./ct.resolution.z);
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
doseWindow = [0 max([resultGUI.physicalDose(:)])];
figure,title('phantom plan')
matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.physicalDose,plane,slice,[],[],colorcube,[],doseWindow,[]);
+
%%
% We export the the created phantom & dose as dicom. This is handled by the
% class matRad_DicomExporter. When no arguments are given, the exporter searches
@@ -242,6 +150,6 @@
% the property dicomDir. While the different DICOM datasets (ct, RTStruct, etc)
% can be exported individually, we call the wrapper to do all possible exports.
dcmExport = matRad_DicomExporter();
-dcmExport.dicomDir = [pwd filesep 'dicomExport'];
+dcmExport.dicomDir = [matRad_cfg.primaryUserFolder filesep 'dicomExport'];
dcmExport.matRad_exportDicom();
diff --git a/examples/matRad_example2_photons.m b/examples/matRad_example2_photons.m
index 328276735..ddf7f86a1 100644
--- a/examples/matRad_example2_photons.m
+++ b/examples/matRad_example2_photons.m
@@ -6,15 +6,14 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-% In this example we will show
+%% In this example we will show
% (i) how to load patient data into matRad
% (ii) how to setup a photon dose calculation and
% (iii) how to inversely optimize beamlet intensities
@@ -27,8 +26,7 @@
% matRad root directory with all its subdirectories is added to the Matlab
% search path.
-matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
-
+matRad_cfg = matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
load('TG119.mat');
%%
@@ -38,7 +36,6 @@
% dimensions, resolution, number of CT scenarios). Please note that
%multiple ct cubes (e.g. 4D CT) can be stored in the cell array ct.cube{}
display(ct);
-
%%
% The 'cst' cell array defines volumes of interests along with information
% required for optimization. Each row belongs to one certain volume of
@@ -48,7 +45,6 @@
% column contains a linear index vector that lists all voxels belonging to
% a certain VOI.
display(cst);
-
%%
% The fifth column represents meta parameters for optimization. The overlap
% priority is used to resolve ambiguities of overlapping structures (voxels
@@ -58,6 +54,7 @@
% parameter of the linear quadratic model. These parameter are required for
% biological treatment planning using a variable RBE. Let's output the meta
% optimization parameter of the target, which is stored in the thrid row:
+
ixTarget = 3;
display(cst{ixTarget,5});
@@ -68,6 +65,7 @@
% individual structures. Here, we have defined a squared deviation
% objective making it 'expensive/costly' for the optimizer to over- and
% underdose the target structure (both are equally important).
+
display(cst{ixTarget,6});
%% Treatment Plan
@@ -83,17 +81,26 @@
% generic photon linear accelerator called 'Generic'. By this means matRad
% will look for 'photons_Generic.mat' in our root directory and will use
% the data provided in there for dose calculation
+
pln.radiationMode = 'photons';
pln.machine = 'Generic';
%%
% Define the flavor of optimization along with the quantity that should be
-% used for optimization. Possible values are (none: physical optimization;
-% const_RBExD: constant RBE of 1.1; LEMIV_effect: effect-based
-% optimization; LEMIV_RBExD: optimization of RBE-weighted dose. As we are
-% using photons, we simply set the parameter to 'none' thereby indicating
-% the physical dose should be optimized.
-pln.propOpt.bioOptimization = 'none';
+% used for optimization. Possible quantities used for optimization are:
+% physicalDose: physical dose based optimization;
+% effect: biological effect based optimization;
+% RBExD: RBE weighted dose based optimzation;
+% Possible biological models are:
+% none: use no specific biological model
+% constRBE: use a constant RBE
+% MCN: use the variable RBE McNamara model for protons
+% WED: use the variable RBE Wedenberg model for protons
+% LEM: use the biophysical variable RBE Local Effect model for carbons
+% As we are using photons, we simply set the parameter to 'physicalDose' and
+% and 'none'
+quantityOpt = 'physicalDose';
+modelName = 'none';
%%
% Now we have to set some beam parameters. We can define multiple beam
@@ -115,7 +122,7 @@
% calculate the iso-center which is per default the center of gravity of
% all target voxels.
pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
-pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+pln.propStf.isoCenter = matRad_getIsoCenter(cst,ct,0);
%% dose calculation settings
% set resolution of dose calculation and optimization
@@ -126,16 +133,24 @@
%%
% Enable sequencing and disable direct aperture optimization (DAO) for now.
% A DAO optimization is shown in a seperate example.
-pln.propOpt.runSequencing = 1;
+pln.propSeq.runSequencing = 1;
pln.propOpt.runDAO = 0;
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
+
%%
% and et voila our treatment plan structure is ready. Lets have a look:
display(pln);
+
%% Generate Beam Geometry STF
% The steering file struct comprises the complete beam geometry along with
% ray position, pencil beam positions and energies, source to axis distance (SAD) etc.
+
stf = matRad_generateStf(ct,cst,pln);
%%
@@ -146,7 +161,7 @@
% Let's generate dosimetric information by pre-computing dose influence
% matrices for unit beamlet intensities. Having dose influences available
% allows subsequent inverse optimization.
-dij = matRad_calcPhotonDose(ct,stf,pln,cst);
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
%% Inverse Optimization for IMRT
% The goal of the fluence optimization is to find a set of beamlet/pencil
@@ -159,7 +174,8 @@
%% Plot the Resulting Dose Slice
% Let's plot the transversal iso-center dose slice
-slice = round(pln.propStf.isoCenter(1,3)./ct.resolution.z);
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
figure
imagesc(resultGUI.physicalDose(:,:,slice)),colorbar, colormap(jet);
@@ -168,11 +184,15 @@
pln.propStf.gantryAngles = [0:50:359];
pln.propStf.couchAngles = zeros(1,numel(pln.propStf.gantryAngles));
pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+
+
+
stf = matRad_generateStf(ct,cst,pln);
pln.propStf.isoCenter = vertcat(stf.isoCenter);
-dij = matRad_calcPhotonDose(ct,stf,pln,cst);
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
resultGUI_coarse = matRad_fluenceOptimization(dij,cst,pln);
+
%% Visual Comparison of results
% Let's compare the new recalculation against the optimization result.
% Check if you have added all subdirectories to the Matlab search path,
@@ -196,16 +216,16 @@
%% Obtain dose statistics
% Two more columns will be added to the cst structure depicting the DVH and
% standard dose statistics such as D95,D98, mean dose, max dose etc.
-[dvh,qi] = matRad_indicatorWrapper(cst,pln,resultGUI);
-[dvh_coarse,qi_coarse] = matRad_indicatorWrapper(cst,pln,resultGUI_coarse);
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln);
+resultGUI_coarse = matRad_planAnalysis(resultGUI_coarse,ct,cst,stf,pln);
%%
% The treatment plan using more beams should in principle result in a
% better OAR sparing. Therefore lets have a look at the D95 of the OAR of
% both plans
ixOAR = 2;
-display(qi(ixOAR).D_95);
-display(qi_coarse(ixOAR).D_95);
+display(resultGUI.qi(ixOAR).D_95);
+display(resultGUI.qi_coarse(ixOAR).D_95);
%%
@@ -215,6 +235,4 @@
% also store more optional parameters, but requires only the resolution to be s
% set.
metadata = struct('resolution',[ct.resolution.x ct.resolution.y ct.resolution.z]);
-matRad_writeCube([pwd filesep 'photonDose_example2.nrrd'],resultGUI.physicalDose,'double',metadata);
-
-
+matRad_writeCube(fullfile(matRad_cfg.primaryUserFolder,'photonDose_example2.nrrd'),resultGUI.physicalDose,'double',metadata);
diff --git a/examples/matRad_example3_photonsDAO.m b/examples/matRad_example3_photonsDAO.m
index eb67ebc55..c83357284 100644
--- a/examples/matRad_example3_photonsDAO.m
+++ b/examples/matRad_example3_photonsDAO.m
@@ -6,15 +6,14 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-% In this example we will show
+%% In this example we will show
% (i) how to load patient data into matRad
% (ii) how to setup a photon dose calculation and
% (iii) how to inversely optimize directly from command window in MatLab.
@@ -22,12 +21,12 @@
% (v) how to run a direct aperture optimization
% (iv) how to visually and quantitatively evaluate the result
-%% Patient Data Import
-% Let's begin with a clear Matlab environment and import the head &
-% neck patient into your workspace.
-
+%% set matRad runtime configuration
matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
+%% Patient Data Import
+% import the head & neck patient into your workspace.
+
load('HEAD_AND_NECK.mat');
%% Treatment Plan
@@ -38,14 +37,23 @@
pln.radiationMode = 'photons'; % either photons / protons / carbon
pln.machine = 'Generic';
pln.numOfFractions = 30;
-
-pln.propOpt.bioOptimization = 'none';
+
pln.propStf.gantryAngles = [0:72:359];
pln.propStf.couchAngles = [0 0 0 0 0];
pln.propStf.bixelWidth = 5;
pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+quantityOpt = 'physicalDose'; % either physicalDose / effect / RBExD
+modelName = 'none'; % none: for photons, protons, carbon constRBE: constant RBE model
+ % MCN: McNamara-variable RBE model for protons WED: Wedenberg-variable RBE model for protons
+ % LEM: Local Effect Model for carbon ions
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
+
% dose calculation settings
pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.y = 3; % [mm]
@@ -64,8 +72,8 @@
%%
% Enable sequencing and direct aperture optimization (DAO).
-pln.propOpt.runSequencing = 1;
-pln.propOpt.runDAO = 1;
+pln.propSeq.runSequencing = true;
+pln.propOpt.runDAO = true;
%% Generate Beam Geometry STF
stf = matRad_generateStf(ct,cst,pln);
@@ -74,7 +82,7 @@
% Lets generate dosimetric information by pre-computing dose influence
% matrices for unit beamlet intensities. Having dose influences available
% allows for subsequent inverse optimization.
-dij = matRad_calcPhotonDose(ct,stf,pln,cst);
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
%% Inverse Planning for IMRT
% The goal of the fluence optimization is to find a set of beamlet weights
@@ -90,7 +98,7 @@
% order to modulate the intensity of the beams with multiple static
% segments, so that translates each intensity map into a set of deliverable
% aperture shapes.
-resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,5);
+resultGUI = matRad_sequencing(resultGUI,stf,dij,pln);
%% DAO - Direct Aperture Optimization
% The Direct Aperture Optimization is an optimization approach where we
@@ -102,4 +110,4 @@
matRad_visApertureInfo(resultGUI.apertureInfo);
%% Indicator Calculation and display of DVH and QI
-[dvh,qi] = matRad_indicatorWrapper(cst,pln,resultGUI);
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln);
diff --git a/examples/matRad_example4_photonsMC.m b/examples/matRad_example4_photonsMC.m
index e6e34fbe3..583540061 100644
--- a/examples/matRad_example4_photonsMC.m
+++ b/examples/matRad_example4_photonsMC.m
@@ -6,26 +6,25 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-% In this example we will show
+%% In this example we will show
% (i) how to load patient data into matRad
% (ii) how to setup a photon dose calculation based on the VMC++ Monte Carlo algorithm
% (iii) how to inversely optimize the beamlet intensities directly from command window in MATLAB.
% (iv) how to visualize the result
+%% set matRad runtime configuration
+matRad_rc %If this throws an error, run it from the parent directory first to set the paths
+
%% Patient Data Import
% Let's begin with a clear Matlab environment and import the boxphantom
% into your workspace.
-
-matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
-
load('BOXPHANTOM.mat');
%% Treatment Plan
@@ -33,19 +32,29 @@
% structure requires input from the treatment planner and defines the most
% important cornerstones of your treatment plan.
-pln.radiationMode = 'photons';
-pln.machine = 'Generic';
-pln.numOfFractions = 30;
-pln.propOpt.bioOptimization = 'none';
+pln.radiationMode = 'photons';
+pln.machine = 'Generic';
+pln.numOfFractions = 30;
pln.propStf.gantryAngles = [0];
pln.propStf.couchAngles = [0];
pln.propStf.bixelWidth = 10;
pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
-pln.propOpt.runSequencing = 0;
+pln.propSeq.runSequencing = 0;
pln.propOpt.runDAO = 0;
+quantityOpt = 'physicalDose';
+modelName = 'none';
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
% dose calculation settings
+%Choose MC Engine
+pln.propDoseCalc.engine = 'ompMC';
+
pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.y = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.z = 3; % [mm]
@@ -56,14 +65,15 @@
%% Dose Calculation
% Calculate dose influence matrix for unit pencil beam intensities using
% a Monte Carlo algorithm
-dij = matRad_calcPhotonDoseMC(ct,stf,pln,cst);
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
%% Inverse Optimization for IMRT
resultGUI = matRad_fluenceOptimization(dij,cst,pln);
%% Plot the Resulting Dose Slice
% Just let's plot the transversal iso-center dose slice
-slice = round(pln.propStf.isoCenter(1,3)./ct.resolution.z);
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
figure,
imagesc(resultGUI.physicalDose(:,:,slice)),colorbar, colormap(jet)
@@ -72,7 +82,6 @@
ixTarget = cst{2,4}{1};
doseInTarget = resultGUI.physicalDose(ixTarget);
figure
-[env, ~] = matRad_getEnvironment();
hist(doseInTarget);
% use hist for compatibility with GNU Octave
diff --git a/examples/matRad_example5_protons.m b/examples/matRad_example5_protons.m
index 0d743fe82..af13d6acc 100644
--- a/examples/matRad_example5_protons.m
+++ b/examples/matRad_example5_protons.m
@@ -6,15 +6,14 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-% In this example we will show
+%% In this example we will show
% (i) how to load patient data into matRad
% (ii) how to setup a proton dose calculation
% (iii) how to inversely optimize the pencil beam intensities directly from command window in MATLAB.
@@ -22,12 +21,13 @@
% (v) how to recalculated the dose considering the shifted geometry and the previously optimized pencil beam intensities
% (vi) how to compare the two results
+%% set matRad runtime configuration
+matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
+
%% Patient Data Import
% Let's begin with a clear Matlab environment and import the prostate
% patient into your workspace
-matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
-
load('PROSTATE.mat');
%% Treatment Plan
@@ -45,32 +45,37 @@
pln.radiationMode = 'protons';
pln.machine = 'Generic';
-%%
-% Define the flavor of biological optimization for treatment planning along
-% with the quantity that should be used for optimization. Possible values
-% are (none: physical optimization; const_RBExD: constant RBE of 1.1;
-% LEMIV_effect: effect-based optimization; LEMIV_RBExD: optimization of
-% RBE-weighted dose. As we use protons, we follow here the clinical
-% standard and use a constant relative biological effectiveness of 1.1.
-% Therefore we set bioOptimization to const_RBExD
-pln.propOpt.bioOptimization = 'const_RBExD';
-
%%
% for particles it is possible to also calculate the LET disutribution
% alongside the physical dose. Therefore you need to activate the
-% corresponding option during dose calculcation
-pln.propDoseCalc.calcLET = 1;
+% corresponding option during dose calculcation. We also explicitly say to
+% use the Hong Pencil Beam Algorithm
+pln.propDoseCalc.calcLET = 0;
+pln.propDoseCalc.engine = 'HongPB';
%%
% Now we have to set the remaining plan parameters.
pln.numOfFractions = 30;
pln.propStf.gantryAngles = [90 270];
pln.propStf.couchAngles = [0 0];
-pln.propStf.bixelWidth = 3;
+pln.propStf.bixelWidth = 5;
pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
pln.propOpt.runDAO = 0;
-pln.propOpt.runSequencing = 0;
+pln.propSeq.runSequencing = 0;
+
+% Define the flavor of biological optimization for treatment planning along
+% with the quantity that should be used for optimization.
+
+quantityOpt = 'RBExD'; % either physicalDose / effect / RBExD
+modelName = 'constRBE'; % none: for photons, protons, carbon constRBE: constant RBE model
+ % MCN: McNamara-variable RBE model for protons WED: Wedenberg-variable RBE model for protons
+ % LEM: Local Effect Model for carbon ions
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen');
% dose calculation settings
pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
@@ -84,7 +89,7 @@
% Lets generate dosimetric information by pre-computing dose influence
% matrices for unit beamlet intensities. Having dose influences available
% allows for subsequent inverse optimization.
-dij = matRad_calcParticleDose(ct,stf,pln,cst);
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
%% Inverse Optimization for IMPT
% The goal of the fluence optimization is to find a set of bixel/spot
@@ -94,22 +99,23 @@
%% Plot the Resulting Dose Slice
% Let's plot the transversal iso-center dose slice
-slice = round(pln.propStf.isoCenter(1,3)./ct.resolution.z);
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
figure
-imagesc(resultGUI.RBExDose(:,:,slice)),colorbar,colormap(jet)
+imagesc(resultGUI.RBExD(:,:,slice)),colorbar,colormap(jet)
%% Plot the Resulting Beam Dose Slice
% Let's plot the transversal iso-center dose slice of beam 1 and beam 2
% separately
figure
-subplot(121),imagesc(resultGUI.RBExDose_beam1(:,:,slice)),colorbar,colormap(jet),title('dose of beam 1')
-subplot(122),imagesc(resultGUI.RBExDose_beam2(:,:,slice)),colorbar,colormap(jet),title('dose of beam 2')
-
+subplot(121),imagesc(resultGUI.RBExD_beam1(:,:,slice)),colorbar,colormap(jet),title('dose of beam 1')
+subplot(122),imagesc(resultGUI.RBExD_beam2(:,:,slice)),colorbar,colormap(jet),title('dose of beam 2')
%% and the corresponding LET distribution
% Transversal iso-center slice
-figure
-imagesc(resultGUI.LET(:,:,slice)),colormap(jet),colorbar,title('LET [keV/�m]')
-
+if pln.propDoseCalc.calcLET
+ figure
+ imagesc(resultGUI.LET(:,:,slice)),colormap(jet),colorbar,title('LET [keV/�m]')
+end
%%
% Now let's simulate a patient shift in y direction for both beams
stf(1).isoCenter(2) = stf(1).isoCenter(2) - 4;
@@ -118,55 +124,52 @@
%% Recalculate Plan
% Let's use the existing optimized pencil beam weights and recalculate the RBE weighted dose
-resultGUI_isoShift = matRad_calcDoseDirect(ct,stf,pln,cst,resultGUI.w);
+resultGUI_isoShift = matRad_calcDoseForward(ct,cst,stf,pln,resultGUI.w);
%% Visual Comparison of results
% Let's compare the new recalculation against the optimization result.
plane = 3;
-doseWindow = [0 max([resultGUI.RBExDose(:); resultGUI_isoShift.RBExDose(:)])];
+doseWindow = [0 max([resultGUI.RBExD(:); resultGUI_isoShift.RBExD(:)])];
figure,title('original plan')
-matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.RBExDose,plane,slice,[],0.75,colorcube,[],doseWindow,[]);
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.RBExD,plane,slice,[],0.75,colorcube,[],doseWindow,[]);
figure,title('shifted plan')
-matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI_isoShift.RBExDose,plane,slice,[],0.75,colorcube,[],doseWindow,[]);
-
-absDiffCube = resultGUI.RBExDose-resultGUI_isoShift.RBExDose;
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI_isoShift.RBExD,plane,slice,[],0.75,colorcube,[],doseWindow,[]);
+
+absDiffCube = resultGUI.RBExD-resultGUI_isoShift.RBExD;
figure,title('absolute difference')
matRad_plotSliceWrapper(gca,ct,cst,1,absDiffCube,plane,slice,[],[],colorcube);
% Let's plot single profiles that are perpendicular to the beam direction
-ixProfileY = round(pln.propStf.isoCenter(1,2)./ct.resolution.y);
-
-profileOrginal = resultGUI.RBExDose(:,ixProfileY,slice);
-profileShifted = resultGUI_isoShift.RBExDose(:,ixProfileY,slice);
-
-figure,plot(profileOrginal,'LineWidth',2),grid on,hold on,
- plot(profileShifted,'LineWidth',2),legend({'original profile','shifted profile'}),
- xlabel('mm'),ylabel('Gy(RBE)'),title('profile plot')
-
+ixProfileY = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+ixProfileY = ixProfileY(2);
+profileOrginal = resultGUI.RBExD(:,ixProfileY,slice);
+profileShifted = resultGUI_isoShift.RBExD(:,ixProfileY,slice);
+
+figure,plot(profileOrginal,'LineWidth',2),grid on,hold on,
+plot(profileShifted,'LineWidth',2),legend({'original profile','shifted profile'}),
+xlabel('mm'),ylabel('Gy(RBE)'),title('profile plot')
%% Quantitative Comparison of results
% Compare the two dose cubes using a gamma-index analysis. The gamma index
% is a composite quality distribution equally taking into account a dose
% difference and a distance to agreement criterion in order to quantify
% differences between two dose cubes. A gamma-index value of smaller than 1
% indicates a successful test and a value greater than 1 illustrates a
-% failed test.
+% failed test. An alternative analysis could be performed with the
+% matRad_compareDose function, which includes the gamma test
doseDifference = 2;
distToAgreement = 2;
n = 1;
[gammaCube,gammaPassRateCell] = matRad_gammaIndex(...
- resultGUI_isoShift.RBExDose,resultGUI.RBExDose,...
+ resultGUI_isoShift.RBExD,resultGUI.RBExD,...
[ct.resolution.x, ct.resolution.y, ct.resolution.z],...
[doseDifference distToAgreement],slice,n,'global',cst);
-
-[env, ~] = matRad_getEnvironment();
% Let's plot the gamma index histogram
figure
hist(gammaCube(gammaCube>0),100)
title('gamma index histogram')
-
diff --git a/examples/matRad_example6_protonsNoise.m b/examples/matRad_example6_protonsNoise.m
index 6a2bcb732..ccddcebb7 100644
--- a/examples/matRad_example6_protonsNoise.m
+++ b/examples/matRad_example6_protonsNoise.m
@@ -1,4 +1,5 @@
-%% Example: Proton Treatment Plan with Manipulated CT values
+%% Example: Proton Treatment Plan with Manipulated CT values including fine
+% sampling algorithm
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
@@ -6,15 +7,14 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-% In this example we will show
+%% In this example we will show
% (i) how to load patient data into matRad
% (ii) how to setup a proton dose calculation
% (iii) how to inversely optimize the pencil beam intensities directly from command window in MATLAB.
@@ -23,30 +23,52 @@
% (vi) how to recalculate the dose considering the manipulated CT cube and the previously optimized pencil beam intensities
% (vii) how to compare the two results
+%% set matRad runtime configuration
+matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
+
%% Patient Data Import
% Let's begin with a clear Matlab environment and import the prostate
% patient into your workspace.
-
-matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
-
load('PROSTATE.mat');
%% Treatment Plan
% The next step is to define your treatment plan labeled as 'pln'. This
% structure requires input from the treatment planner and defines
% the most important cornerstones of your treatment plan.
+pln.radiationMode = 'protons';
+pln.machine = 'generic_MCsquare'; %Use the base data fitted to MC here
+pln.numOfFractions = 30;
+pln.propStf.gantryAngles = [90 270];
+pln.propStf.couchAngles = [0 0];
+pln.propStf.bixelWidth = 5;
+pln.propStf.longitudinalSpotSpacing = 5;
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+pln.propOpt.runDAO = 0;
+pln.propOpt.runSequencing = 0;
+
+
+%%
+% Define the biological optimization model for treatment planning along
+% with the quantity that should be used for optimization. Possible model values
+% are:
+%('none': physical optimization;
+%'constRBE': constant RBE of 1.1;
+% 'MCN': McNamara-variable RBE model for protons;
+% 'WED': Wedenberg-variable RBE model for protons
+% 'LEM': local effect model
+% As we use protons, we follow here the clinical
+% standard and use a constant relative biological effectiveness of 1.1.
+% Therefore we set modelName to constRBE
+modelName = 'constRBE';
+quantityOpt = 'RBExD';
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen'); % optimize on the nominal scenario
-pln.radiationMode = 'protons';
-pln.machine = 'generic_MCsquare';
-pln.numOfFractions = 30;
-pln.propOpt.bioOptimization = 'const_RBExD';
-pln.propStf.gantryAngles = [90 270];
-pln.propStf.couchAngles = [0 0];
-pln.propStf.bixelWidth = 3;
-pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
-pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
-pln.propOpt.runDAO = 0;
-pln.propOpt.runSequencing = 0;
% dose calculation settings
pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
@@ -57,15 +79,21 @@
stf = matRad_generateStf(ct,cst,pln);
%% Dose Calculation
-dij = matRad_calcParticleDoseMC(ct,stf,pln,cst);
+%We do a Monte Carlo Dose calculation here to demonstrate how long an MC
+%simulation on pencil-beam basis will take. If you just want to get through
+%the example, feel free to use analytical dose calculation instead by
+%uncommenting the first line or explicitly switch engine to HongPB
+
+pln.propDoseCalc.engine = 'MCsquare';
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
%% Inverse Optimization for IMPT
resultGUI = matRad_fluenceOptimization(dij,cst,pln);
%% Calculate quality indicators
-[dvh,qi] = matRad_indicatorWrapper(cst,pln,resultGUI);
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln);
ixRectum = 1;
-display(qi(ixRectum).D_5);
+display(resultGUI.qi(ixRectum).D_5);
%%
% Let's change the optimization parameter of the rectum in such a way that it
@@ -76,58 +104,69 @@
objective = cst{ixRectum,6}{1}; %This gives a struct
objective = matRad_DoseOptimizationFunction.createInstanceFromStruct(objective); %Now we turn it into a class
objective = objective.setDoseParameters(40); %We can simply call this function to change the/all dose parameter(s)
-cst{ixRectum,6}{1} = struct(objective); % We put it back as struct
+cst{ixRectum,6}{1} = struct(objective); % We put it back as struct for storage & compatability
+
+cst{ixRectum,6}{1}.parameters{1} = 40; % Change the reference dose
+cst{ixRectum,6}{1}.penalty = 500; % Change the penalty
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln);
-cst{ixRectum,6}{1}.parameters{1} = 40;
-cst{ixRectum,6}{1}.penalty = 500;
-resultGUI = matRad_fluenceOptimization(dij,cst,pln);
-[dvh2,qi2] = matRad_indicatorWrapper(cst,pln,resultGUI);
-display(qi2(ixRectum).D_5);
+display(resultGUI.qi(ixRectum).D_5);
%% Plot the Resulting Dose Slice
% Let's plot the transversal iso-center dose slice
-slice = round(pln.propStf.isoCenter(1,3)./ct.resolution.z);
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
figure
-imagesc(resultGUI.RBExDose(:,:,slice)),colorbar, colormap(jet)
+imagesc(resultGUI.RBExD(:,:,slice)),colorbar, colormap(jet)
-%%
-% Now let's simulate a range undershoot by scaling the relative stopping power cube by 3.5% percent
+%% Add Range Uncertainty
+% Now let's manually simulate a range undershoot by scaling the relative
+% stopping power cube by 3.5% percent. For that to happen, we need to tell
+% matRad that it should not convert the HU-cube (ct.cubeHU) to RSP cube
+% implicitly, but directly use the RSP cube we provide in ct.cube
+% Note that such uncertainty scenarios can also be computed by using the
+% functionalities of matRad_multScen
+
+pln.propDoseCalc.useGivenEqDensityCube = true;
ct_manip = ct;
-ct_manip.cubeHU{1} = 1.035*ct_manip.cubeHU{1};
+ct_manip.cube{1} = 1.035*ct_manip.cube{1};
%% Recalculate Plan with MC square
% Let's use the existing optimized pencil beam weights and recalculate the RBE weighted dose
-resultGUI_noise = matRad_calcDoseDirectMC(ct_manip,stf,pln,cst,resultGUI.w);
-
-%% Visual Comparison of results
-% Let's compare the new recalculation against the optimization result.
-plane = 3;
-doseWindow = [0 max([resultGUI.RBExDose(:); resultGUI_noise.RBExDose(:)])];
-
-figure,title('original plan')
-matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.RBExDose,plane,slice,[],0.75,colorcube,[],doseWindow,[]);
-figure,title('manipulated plan')
-matRad_plotSliceWrapper(gca,ct_manip,cst,1,resultGUI_noise.RBExDose,plane,slice,[],0.75,colorcube,[],doseWindow,[]);
-
-% Let's plot single profiles along the beam direction
-ixProfileY = round(pln.propStf.isoCenter(1,1)./ct.resolution.x);
-
-profileOrginal = resultGUI.RBExDose(:,ixProfileY,slice);
-profileNoise = resultGUI_noise.RBExDose(:,ixProfileY,slice);
-
-figure,plot(profileOrginal,'LineWidth',2),grid on,hold on,
- plot(profileNoise,'LineWidth',2),legend({'original profile','noise profile'}),
- xlabel('mm'),ylabel('Gy(RBE)'),title('profile plot')
-
-%% Quantitative Comparison of results
-% Compare the two dose cubes using a gamma-index analysis.
-
-doseDifference = 2;
-distToAgreement = 2;
-n = 1;
-
-[gammaCube,gammaPassRateCell] = matRad_gammaIndex(...
- resultGUI_noise.RBExDose,resultGUI.RBExDose,...
- [ct.resolution.x, ct.resolution.y, ct.resolution.z],...
- [doseDifference distToAgreement],slice,n,'global',cst);
-
+resultGUI_noise = matRad_calcDoseForward(ct_manip,cst,stf,pln,resultGUI.w);
+
+%% Recalculate Plan with analytical fine sampling algorithm
+% Again use the existing optimized pencil beam weights and recalculate the
+% RBE weighted dose, now using the fine sampling algorithm instead of MC
+
+% pln.propDoseCalc.fineSampling stores parameters defining the fine
+% sampling simulation
+
+pln.propDoseCalc.engine = 'SubsamplingPB';
+
+pln.propDoseCalc.fineSampling.method = 'fitCircle';
+pln.propDoseCalc.fineSampling.sigmaSub = 2; %mm
+pln.propDoseCalc.fineSampling.N = 2;
+% method for weight calculation, availabe methods:
+% 'russo'
+% 'fitCircle', supports N = 2,3 and 8
+% 'fitSquare', supports N = 2 and 3
+
+% pln.propDoseCalc.fineSampling.N = n sets the number of used fine
+% sampling sub beams, default is N = 21
+% parameter to modify number of calculated FS sub beams
+% 'russo', total number of beams = N^2
+% 'fitCircle', total number of beams = (2*N + 1)^2
+% 'fitSquare', total number of beams = (2^N - 1) * 6 + 1
+
+% pln.propDoseCalc.fineSampling.sigmaSub = s set the Gaussian standard
+% deviation of the sub Gaussian beams, only used when fine sampling
+% method 'russo' is selected', default is s = 1;
+
+% Direct call for fine sampling dose calculation:
+resultGUI_FS = matRad_calcDoseForward(ct,cst,stf,pln,resultGUI.w);
+
+%% Visual Comparison of results using the "compareDose" helper function
+matRad_compareDose(resultGUI_noise.RBExD,resultGUI.RBExD,ct,cst);
+matRad_compareDose(resultGUI_FS.RBExD,resultGUI.RBExD,ct,cst);
diff --git a/examples/matRad_example7_carbon.m b/examples/matRad_example7_carbon.m
index 550f4c205..58240d93e 100644
--- a/examples/matRad_example7_carbon.m
+++ b/examples/matRad_example7_carbon.m
@@ -6,15 +6,14 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-% In this example we will show
+%% In this example we will show
% (i) how to load patient data into matRad
% (ii) how to setup a carbon ion dose calculation plan including variable RBE optimization
% (iii) how to inversely optimize the pencil beam intensities based on the
@@ -25,12 +24,10 @@
% (vi) how to recalculated the dose considering the previously optimized pencil beam intensities
% (vii) how to compare the two results
-%% Patient Data Import
-% Let's begin with a clear Matlab environment and import the liver
-% patient into your workspace.
-
+%% set matRad runtime configuration
matRad_rc; %If this throws an error, run it from the parent directory first to set the paths
+%% Patient Data Import
load('LIVER.mat');
%% Treatment Plan
@@ -48,31 +45,44 @@
pln.machine = 'Generic';
%%
-% Define the flavor of biological optimization for treatment planning along
-% with the quantity that should be used for optimization. Possible values
-% are (none: physical optimization; const_RBExD: constant RBE of 1.1;
-% LEMIV_effect: effect-based optimization; LEMIV_RBExD: optimization of
-% RBE-weighted dose. As we use carbon ions, we decide to use base data from
-% the local effect model IV and want to optimize the RBE-weighted dose.
-% Therefore we set bioOptimization to LEMIV_RBExD
-pln.propOpt.bioOptimization = 'LEMIV_RBExD';
+% Define the biological optimization model for treatment planning along
+% with the quantity that should be used for optimization. Possible model values
+% are:
+%('none': physical optimization;
+%'constRBE': constant RBE of 1.1;
+% 'MCN': McNamara-variable RBE model for protons;
+% 'WED': Wedenberg-variable RBE model for protons
+% 'LEM': local effect model
+% As we use carbons, we use the local effect model.
+% Therefore we set modelName to LEM
+modelName = 'LEM';
+quantityOpt = 'RBExD';
%%
% The remaining plan parameters are set like in the previous example files
pln.numOfFractions = 30;
pln.propStf.gantryAngles = 315;
pln.propStf.couchAngles = 0;
-pln.propStf.bixelWidth = 3;
+pln.propStf.bixelWidth = 6;
pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
pln.propOpt.runDAO = 0;
-pln.propOpt.runSequencing = 0;
+pln.propSeq.runSequencing = 0;
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'nomScen'); % optimize on the nominal scenario
% dose calculation settings
pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.y = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.z = 3; % [mm]
+%Let's also calculate the LET
+pln.propDoseCalc.calcLET = true;
+
%% Generate Beam Geometry STF
stf = matRad_generateStf(ct,cst,pln);
@@ -83,14 +93,14 @@
% and orientation of the ray, we can also find pencil beam information. If
% the ray coincides with the target, pencil beams were defined along the
% ray from target entry to target exit.
-display(stf.ray(end));
+display(stf.ray(100));
%%
-% Here are the energies selected on the last ray:
-display(stf.ray(end).energy);
+% Here are the energies selected on ray # 100:
+display(stf.ray(100).energy);
%% Dose Calculation
-dij = matRad_calcParticleDose(ct,stf,pln,cst);
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
%% Inverse Optimization for IMPT based on RBE-weighted dose
% The goal of the fluence optimization is to find a set of bixel/spot
@@ -100,15 +110,25 @@
%% Plot the Resulting Dose Slice
% Let's plot the transversal iso-center dose slice
-slice = round(pln.propStf.isoCenter(3)./ct.resolution.z);
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
figure,
-imagesc(resultGUI.RBExDose (:,:,slice)),colorbar, colormap(jet);
+imagesc(resultGUI.RBExD(:,:,slice)),colorbar, colormap(jet);
+
+%% Let's check out the LET
+% Let's plot the transversal iso-center LET slice
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
+figure;
+imagesc(resultGUI.LET(:,:,slice)),colorbar, colormap(jet);
%% Inverse Optimization for IMPT based on biological effect
% To perform a dose optimization for carbon ions we can also use the
% biological effect instead of the RBE-weighted dose. Therefore we have to
% change the optimization mode and restart the optimization
-pln.propOpt.bioOptimization = 'LEMIV_effect';
+quantityOpt = 'effect';
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName);
+
resultGUI_effect = matRad_fluenceOptimization(dij,cst,pln);
%% Visualize differences
@@ -116,7 +136,7 @@
% different dose distribution as visualized by the following dose
% difference map
figure;
-imagesc(resultGUI.RBExDose (:,:,slice)-resultGUI_effect.RBExDose(:,:,slice));
+imagesc(resultGUI.RBExD(:,:,slice)-resultGUI_effect.RBExD(:,:,slice));
colorbar;
colormap(jet);
@@ -132,29 +152,27 @@
%% Recalculate Plan
% Let's use the existing optimized pencil beam weights and recalculate the RBE weighted dose
-resultGUI_tissue = matRad_calcDoseDirect(ct,stf,pln,cst,resultGUI.w);
+resultGUI_tissue = matRad_calcDoseForward(ct,cst,stf,pln,resultGUI.w);
%% Result Comparison
% Let's compare the new recalculation against the optimization result.
plane = 3;
-doseWindow = [0 max([resultGUI_effect.RBExDose(:); resultGUI_tissue.RBExDose(:)])];
+doseWindow = [0 max([resultGUI_effect.RBExD(:); resultGUI_tissue.RBExD(:)])];
figure,
-matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI_effect.RBExDose,plane,slice,[],[],colorcube,[],doseWindow,[]);
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI_effect.RBExD,plane,slice,[],[],colorcube,[],doseWindow,[]);
title('original plan')
figure,
-matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI_tissue.RBExDose,plane,slice,[],[],colorcube,[],doseWindow,[]);
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI_tissue.RBExD,plane,slice,[],[],colorcube,[],doseWindow,[]);
title('manipulated plan')
%%
% At this point we would like to see the absolute difference of the original optimization and the
% recalculation.
-absDiffCube = resultGUI_effect.RBExDose-resultGUI_tissue.RBExDose;
+absDiffCube = resultGUI_effect.RBExD-resultGUI_tissue.RBExD;
figure,
matRad_plotSliceWrapper(gca,ct,cst,1,absDiffCube,plane,slice,[],[],colorcube);
title('absolute difference')
%%
% Plot both doses with absolute difference and gamma analysis
-[gammaCube,gammaPassRate,hfigure]=matRad_compareDose(resultGUI_effect.RBExDose, resultGUI.RBExDose, ct, cst,[1 1 1],'on');
-
-
+[gammaCube,gammaPassRate,hfigure]=matRad_compareDose(resultGUI_effect.RBExD, resultGUI_tissue.RBExD, ct, cst,[1 1 1],'on');
diff --git a/examples/matRad_example8_protonsRobust.m b/examples/matRad_example8_protonsRobust.m
new file mode 100644
index 000000000..bd4419e3a
--- /dev/null
+++ b/examples/matRad_example8_protonsRobust.m
@@ -0,0 +1,171 @@
+%% Example: Robust Treatment Planning with Protons
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% In this example we will
+% (i) create a small artifical phantom
+% (ii) create a scanned proton treatment plan considering a constant RBE of 1.1
+% (iii) we will enable dose calculation on nine selected worst case scenarios
+% (iv) robustly optimize the pencil beam intensities on all 9 dose scenarios
+% using the composite worst case paradigm
+% (v) visualise all individual dose scenarios
+% (vi) sample discrete scenarios from Gaussian uncertainty assumptions
+
+%% set matRad runtime configuration
+matRad_rc
+
+%% Create a CT image series with the Phantom Builder
+
+ctDim = [120,120,60]; % x,y,z dimensions
+ctResolution = [3,3,3]; % x,y,z the same here!
+
+builder = matRad_PhantomBuilder(ctDim,ctResolution,1);
+
+% Now we define three structures for the phantom
+objective1 = struct(DoseObjectives.matRad_SquaredDeviation(800,45));
+objective2 = struct(DoseObjectives.matRad_SquaredOverdosing(400,0));
+
+builder.addSphericalTarget('target',ctDim(1)/13,'objectives',struct(DoseObjectives.matRad_SquaredDeviation(100,60)));
+builder.addSphericalOAR('OAR',ctDim(1)/15,'offset',[-10 10 0],'HU',-100,'objectives',struct(DoseObjectives.matRad_SquaredOverdosing(5,40)));
+builder.addBoxOAR('contour',ctDim./2,'HU',0,'objectives',struct(DoseObjectives.matRad_SquaredOverdosing(1,20)));
+
+%Keep indices for assignment of robustness objectives later
+ixTarget = 1;
+ixOAR = 2;
+ixNT = 3;
+
+[ct,cst] = builder.getctcst();
+
+%% Treatment Plan
+% The next step is to define your treatment plan labeled as 'pln'. This
+% structure requires input from the treatment planner and defines the most
+% important cornerstones of your treatment plan.
+%%
+% First of all, we need to define what kind of radiation modality we would
+% like to use. Possible values are photons, protons or carbon. In this
+% example we would like to use protons for robust treatment planning. Next, we
+% need to define a treatment machine to correctly load the corresponding
+% base data. matRad features generic base data in the file
+% 'carbon_Generic.mat'; consequently the machine has to be set accordingly
+pln.radiationMode = 'protons';
+pln.machine = 'Generic';
+
+%%
+% Define the biological optimization model for treatment planning along
+% with the quantity that should be used for optimization. Possible model values
+% are:
+% 'none': physical optimization;
+% 'constRBE': constant RBE of 1.1;
+% 'MCN': McNamara-variable RBE model for protons;
+% 'WED': Wedenberg-variable RBE model for protons
+% 'LEM': Local Effect Model
+% and possible quantityOpt are 'physicalDose', 'effect' or 'RBExD'.
+% As we use protons, we use a constant RBE of 1.1.
+modelName = 'constRBE';
+quantityOpt = 'RBExD';
+
+%%
+% The remaining plan parameters are set like in the previous example files
+pln.numOfFractions = 20;
+pln.propStf.gantryAngles = [0 90];
+pln.propStf.couchAngles = [0 0];
+pln.propStf.bixelWidth = 5;
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+pln.propOpt.runDAO = 0;
+pln.propOpt.runSequencing = 0;
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName);
+
+% retrieve 9 worst case scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,'wcScen');
+
+pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = 3; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = 3; % [mm]
+
+%% Generate Beam Geometry STF
+stf = matRad_generateStf(ct,cst,pln);
+
+%% Dose Calculation
+dij = matRad_calcParticleDose(ct,stf,pln,cst);
+
+%% Inverse Optimization for IMPT based on RBE-weighted dose
+% The goal of the fluence optimization is to find a set of bixel/spot
+% weights which yield the best possible dose distribution according to the
+% clinical objectives and constraints underlying the radiation treatment.
+
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+
+%% Trigger robust optimization
+% Make the objective to a composite worst case objective
+
+ROBUST_OPT = {'COWC'}; %{'STOCH','VWWC','VWWC_INV','COWC','OWC','PROB'};
+
+for ixRob = 1:numel(ROBUST_OPT)
+ cst{ixTarget,6}{1}.robustness = ROBUST_OPT{1,ixRob};
+ cst{ixOAR,6}{1}.robustness = ROBUST_OPT{1,ixRob};
+ cst{ixNT,6}{1}.robustness = ROBUST_OPT{1,ixRob};
+
+ % add a max constraint
+ %cst{ixOAR,6}{1,2} = struct(DoseConstraints.matRad_MinMaxDose([0 20],'voxel'));
+ %cst{ixOAR,6}{1,2}.robustness = 'COWC';
+
+ resultGUIrobust = matRad_fluenceOptimization(dij,cst,pln);
+
+ % combine resultGUI structures
+ resultGUI = matRad_appendResultGUI(resultGUI,resultGUIrobust,0,['robust' ROBUST_OPT{1,ixRob}]);
+
+end
+
+% matRadGUI
+
+%% Visualize results
+plane = 3;
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
+
+figure,matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.RBExD_beam1 ,plane,slice,[],[],colorcube,[],[0 max(resultGUI.RBExD_beam1(:))],[]);title('conventional plan - beam1')
+figure,matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrobust.RBExD_beam1,plane,slice,[],[],colorcube,[],[0 max(resultGUIrobust.RBExD_beam1(:))],[]);title('robust plan - beam1')
+
+% create an interactive plot to slide through individual scnearios
+f = figure;title('individual scenarios');
+numScen = 1;doseWindow = [0 3.5];
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrobust.(['RBExD_scen' num2str(round(numScen))]),plane,slice,[],[],colorcube,[],doseWindow,[]);
+
+[env,envver] = matRad_getEnvironment();
+if strcmp(env,'MATLAB') || str2double(envver(1)) >= 5
+ b = uicontrol('Parent',f,'Style','slider','Position',[50,5,419,23],...
+ 'value',numScen, 'min',1, 'max',pln.multScen.totNumScen,'SliderStep', [1/(pln.multScen.totNumScen-1) , 1/(pln.multScen.totNumScen-1)]);
+ set(b,'Callback',@(es,ed) matRad_plotSliceWrapper(gca,ct,cst,1,resultGUIrobust.(['RBExD_scen' num2str(round(get(es,'Value')))]),plane,slice,[],[],colorcube,[],doseWindow,[]));
+end
+
+%% Indicator calculation and show DVH and QI
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln);
+%% Perform sampling
+% select structures to include in sampling; leave empty to sample dose for all structures
+structSel = {}; % structSel = {'PTV','OAR1'};
+[caSamp, mSampDose, plnSamp, resultGUInomScen] = matRad_sampling(ct,stf,cst,pln,resultGUI.w,structSel);
+[cstStat, resultGUISamp, meta] = matRad_samplingAnalysis(ct,cst,plnSamp,caSamp, mSampDose, resultGUInomScen);
+
+[caSampRob, mSampDoseRob, plnSampRob, resultGUInomScen] = matRad_sampling(ct,stf,cst,pln,resultGUIrobust.w,structSel);
+[cstStatRob, resultGUISampRob, metaRob] = matRad_samplingAnalysis(ct,cst,plnSampRob,caSampRob, mSampDoseRob, resultGUInomScen);
+
+figure,title('std dose cube based on sampling - conventional')
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUISamp.stdCube,plane,slice,[],[],colorcube,[],[0 max(resultGUISamp.stdCube(:))]);
+
+figure,title('std dose cube based on sampling - robust')
+matRad_plotSliceWrapper(gca,ct,cst,1,resultGUISampRob.stdCube,plane,slice,[],[],colorcube,[],[0 max(resultGUISampRob.stdCube(:))]);
+
diff --git a/examples/matRad_example9_4DDoseCalcMinimal.m b/examples/matRad_example9_4DDoseCalcMinimal.m
new file mode 100644
index 000000000..84343c5dc
--- /dev/null
+++ b/examples/matRad_example9_4DDoseCalcMinimal.m
@@ -0,0 +1,108 @@
+%% 4D dose calculation workflow
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% In this example we will show
+% (i) the structure of 4D data within matRad
+% (ii) how to perform standard treatment planning
+% (iii) how to run a dose recalculation considering interplay effects
+
+%% set matRad runtime configuration
+matRad_rc
+
+%% Load data, add generic 4D information, and display 'moving' geometry
+load BOXPHANTOM.mat
+
+%%
+
+amplitude = [0 3 0]; % [voxels]
+numOfCtScen = 5;
+motionPeriod = 2.5; % [s]
+
+[ct,cst] = matRad_addMovement(ct, cst,motionPeriod, numOfCtScen, amplitude,'dvfType','pull');
+% Set up a plan, compute dose influence on all phases, conventional optimization
+% meta information for treatment plan
+pln.numOfFractions = 30;
+pln.radiationMode = 'protons'; % either photons / protons / helium / carbon
+pln.machine = 'Generic';
+
+% beam geometry settings
+pln.propStf.bixelWidth = 5; % [mm] / also corresponds to lateral spot spacing for particles
+pln.propStf.longitudinalSpotSpacing = 5; % only relevant for HIT machine, not generic
+pln.propStf.gantryAngles = [90];
+pln.propStf.couchAngles = [0];
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+
+%optimization settings
+pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles
+pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below
+
+quantityOpt = 'RBExD'; % options: physicalDose, effect, RBExD
+modelName = 'constRBE'; % none: for photons, protons, carbon % constRBE: constant RBE
+ % MCN: McNamara-variable RBE model for protons % WED: Wedenberg-variable RBE model for protons
+ % LEM: Local Effect Model for carbon ions
+
+scenGenType = 'nomScen'; % scenario creation type 'nomScen' 'wcScen' 'impScen' 'rndScen'
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,scenGenType);
+
+%%
+% generate steering file
+stf = matRad_generateStf(ct,cst,pln);
+
+%%
+% dose calculation
+dij = matRad_calcParticleDose(ct,stf,pln,cst);
+
+%%
+% inverse planning for imrt on a static CT
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+
+%%
+% post processing
+% This step is necessary to remove beam spots with too few particles that
+% cannot not be delivered, dose is recalculated accordingly
+resultGUI = matRad_postprocessing(resultGUI, dij, pln, cst, stf) ;
+
+%%
+% calc 4D dose
+% make sure that the correct pln, dij and stf are loeaded in the workspace
+[resultGUI, timeSequence] = matRad_calc4dDose(ct, pln, dij, stf, cst, resultGUI);
+
+% plot the result in comparison to the static dose
+slice = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:),ct);
+slice = slice(3);
+
+figure
+
+subplot(2,2,1)
+imagesc(resultGUI.RBExD(:,:,slice)),colorbar, colormap(jet);
+title('static dose distribution [Gy (RBE)]')
+axis equal
+
+subplot(2,2,3)
+imagesc(resultGUI.accRBExD(:,:,slice)),colorbar, colormap(jet);
+title('accumulated (4D) dose distribution [Gy (RBE)]')
+axis equal
+
+subplot(2,2,2)
+imagesc(resultGUI.RBExD(:,:,slice) - resultGUI.accRBExD(:,:,slice)) ,colorbar, colormap(jet);
+title('static dose distribution - accumulated (4D) dose distribution [Gy (RBE)]')
+
+axis equal
+
diff --git a/examples/matRad_publishExamples.m b/examples/matRad_publishExamples.m
index d7c69cd0c..d937edf19 100644
--- a/examples/matRad_publishExamples.m
+++ b/examples/matRad_publishExamples.m
@@ -6,7 +6,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad.m b/matRad.m
index a56a806ed..e704a6c98 100644
--- a/matRad.m
+++ b/matRad.m
@@ -6,72 +6,78 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% set matRad runtime configuration
matRad_rc
-% load patient data, i.e. ct, voi, cst
-
-%load HEAD_AND_NECK
+%% load patient data, i.e. ct, voi, cst
load TG119.mat
+%load HEAD_AND_NECK
%load PROSTATE.mat
%load LIVER.mat
%load BOXPHANTOM.mat
% meta information for treatment plan
-
-pln.radiationMode = 'photons'; % either photons / protons / carbon
-pln.machine = 'Generic';
-
pln.numOfFractions = 30;
+pln.radiationMode = 'photons'; % either photons / protons / helium / carbon / brachy
+pln.machine = 'Generic'; % generic for RT / LDR or HDR for BT
% beam geometry settings
pln.propStf.bixelWidth = 5; % [mm] / also corresponds to lateral spot spacing for particles
-pln.propStf.gantryAngles = [0:72:359]; % [?]
-pln.propStf.couchAngles = [0 0 0 0 0]; % [?]
+pln.propStf.gantryAngles = [0:72:359]; % [°] ;
+pln.propStf.couchAngles = [0 0 0 0 0]; % [°] ;
pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+% optimization settings
+pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles
+pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below
+quantityOpt = 'physicalDose'; % options: physicalDose, effect, RBExD
+modelName = 'none'; % none: for photons, protons, carbon, brachy % constRBE: constant RBE for photons and protons
+ % MCN: McNamara-variable RBE model for protons % WED: Wedenberg-variable RBE model for protons
+ % LEM: Local Effect Model for carbon ions % HEL: data-driven RBE parametrization for helium
% dose calculation settings
pln.propDoseCalc.doseGrid.resolution.x = 5; % [mm]
pln.propDoseCalc.doseGrid.resolution.y = 5; % [mm]
pln.propDoseCalc.doseGrid.resolution.z = 5; % [mm]
+scenGenType = 'nomScen'; % scenario creation type 'nomScen' 'wcScen' 'impScen' 'rndScen'
+
+% retrieve bio model parameters
+pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName);
+
+% retrieve scenarios for dose calculation and optimziation
+pln.multScen = matRad_multScen(ct,scenGenType);
+
% optimization settings
pln.propOpt.optimizer = 'IPOPT';
-pln.propOpt.bioOptimization = 'none'; % none: physical optimization; const_RBExD; constant RBE of 1.1;
- % LEMIV_effect: effect-based optimization; LEMIV_RBExD: optimization of RBE-weighted dose
+% pln.propOpt.bioOptimization = 'none'; % none: physical optimization; const_RBExD; constant RBE of 1.1;
+% % LEMIV_effect: effect-based optimization; LEMIV_RBExD: optimization of RBE-weighted dose
pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles
-pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below
+pln.propSeq.runSequencing = true; % true: run sequencing, false: don't / will be ignored for particles and also triggered by runDAO below
+
%% initial visualization and change objective function settings if desired
matRadGUI
-%% generate steering file
+%% generate steering file
stf = matRad_generateStf(ct,cst,pln);
%% dose calculation
-if strcmp(pln.radiationMode,'photons')
- dij = matRad_calcPhotonDose(ct,stf,pln,cst);
- %dij = matRad_calcPhotonDoseVmc(ct,stf,pln,cst);
-elseif strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon')
- dij = matRad_calcParticleDose(ct,stf,pln,cst);
-end
+dij = matRad_calcDoseInfluence(ct, cst, stf, pln);
%% inverse planning for imrt
-resultGUI = matRad_fluenceOptimization(dij,cst,pln);
+resultGUI = matRad_fluenceOptimization(dij,cst,pln);
%% sequencing
-if strcmp(pln.radiationMode,'photons') && (pln.propOpt.runSequencing || pln.propOpt.runDAO)
- %resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,5);
- %resultGUI = matRad_engelLeafSequencing(resultGUI,stf,dij,5);
- resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,5);
-end
+resultGUI = matRad_sequencing(resultGUI,stf,dij,pln);
+
%% DAO
if strcmp(pln.radiationMode,'photons') && pln.propOpt.runDAO
@@ -83,5 +89,5 @@
matRadGUI
%% indicator calculation and show DVH and QI
-[dvh,qi] = matRad_indicatorWrapper(cst,pln,resultGUI);
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln);
diff --git a/matRad.prj b/matRad.prj
deleted file mode 100644
index e6d71a64c..000000000
--- a/matRad.prj
+++ /dev/null
@@ -1,177 +0,0 @@
-
-
-
- matRad
- ${PROJECT_ROOT}\standalone\matRad_icon.ico
-
- ${PROJECT_ROOT}\standalone\matRad_icon_48.png
- ${PROJECT_ROOT}\standalone\matRad_icon_32.png
- ${PROJECT_ROOT}\standalone\matRad_icon_24.png
- ${PROJECT_ROOT}\standalone\matRad_icon_16.png
-
- 2.10
- matRad development team @ DKFZ
- contact@matrad.org
- German Cancer Research Center - DKFZ
- matRad is an open source treatment planning system for radiation therapy written in Matlab.
- !!!matRad development version build from branch/commit prepareBlaise-ef7488df!!! !!!matRad development version build from branch\commit prepareBlaise-18cec97a!!! !!!matRad development version build from branch\commit prepareBlaise-18cec97a!!! !!!matRad development version build from branch\commit prepareBlaise-18cec97a!!! !!!matRad development version build from branch\commit prepareBlaise-18cec97a!!! !!!matRad development version build from branch\commit prepareBlaise-18cec97a!!! This package constains the standalone version of matRad. The standalone version is mainly for educational and demonstration purposes. It only enables access to the graphical user interface and therefore does not contain all available features and might work with lower resolutions and accuracy settings to be efficient enough on common and also less powerful machines. For researchers, we highly recommend to work with the source code directly.
- ${PROJECT_ROOT}\standalone\matRad_splashscreen.png
-
- \matRad
- option.installpath.programfiles
- ${PROJECT_ROOT}\standalone\matRad_installscreen.png
-
-
- ${PROJECT_ROOT}\standalone\for_testing
- ${PROJECT_ROOT}\matRad\for_redistribution_files_only
- ${PROJECT_ROOT}\standalone\for_redistribution
- ${PROJECT_ROOT}\matRad
- false
-
- subtarget.standalone
-
- true
- false
- false
- matRad_installerWin64_v2.10.1
- matRad_installerWin64_wRT_v2.10.1
- MyAppInstaller_app
- true
- true
- matRad_consoleOutput.log
- false
- false
-
- Syntax
- matRadGUI -?
- matRadGUI varargin
- Input Arguments
- -? print help on how to use the application
- varargin input arguments
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${PROJECT_ROOT}\matRadGUI.m
-
-
- ${PROJECT_ROOT}\basedata\photons_Generic.mat
- ${PROJECT_ROOT}\basedata\protons_Generic.mat
- ${PROJECT_ROOT}\basedata\carbon_Generic.mat
- ${PROJECT_ROOT}\dicom
- ${PROJECT_ROOT}\IO
- ${PROJECT_ROOT}\matRad.m
- ${PROJECT_ROOT}\matRad_addMargin.m
- ${PROJECT_ROOT}\matRad_calcCubes.m
- ${PROJECT_ROOT}\matRad_calcDoseDirect.m
- ${PROJECT_ROOT}\matRad_calcDoseDirectMC.m
- ${PROJECT_ROOT}\matRad_calcDoseFillDij.m
- ${PROJECT_ROOT}\matRad_calcDoseInit.m
- ${PROJECT_ROOT}\matRad_calcDoseInitBeam.m
- ${PROJECT_ROOT}\matRad_calcDVH.m
- ${PROJECT_ROOT}\matRad_calcGeoDists.m
- ${PROJECT_ROOT}\matRad_calcLateralParticleCutOff.m
- ${PROJECT_ROOT}\matRad_calcLQParameter.m
- ${PROJECT_ROOT}\matRad_calcParticleDose.m
- ${PROJECT_ROOT}\matRad_calcParticleDoseBixel.m
- ${PROJECT_ROOT}\matRad_calcParticleDoseMC.m
- ${PROJECT_ROOT}\matRad_calcPhotonDose.m
- ${PROJECT_ROOT}\matRad_calcPhotonDoseBixel.m
- ${PROJECT_ROOT}\matRad_calcPhotonDoseMC.m
- ${PROJECT_ROOT}\matRad_calcQualityIndicators.m
- ${PROJECT_ROOT}\matRad_calcSigmaRashi.m
- ${PROJECT_ROOT}\matRad_compileStandalone.m
- ${PROJECT_ROOT}\matRad_computeSSD.m
- ${PROJECT_ROOT}\MatRad_Config.m
- ${PROJECT_ROOT}\matRad_DijSampling.m
- ${PROJECT_ROOT}\matRad_directApertureOptimization.m
- ${PROJECT_ROOT}\matRad_electronDensitiesToHU.m
- ${PROJECT_ROOT}\matRad_engelLeafSequencing.m
- ${PROJECT_ROOT}\matRad_fluenceOptimization.m
- ${PROJECT_ROOT}\matRad_generateStf.m
- ${PROJECT_ROOT}\matRad_getIsoCenter.m
- ${PROJECT_ROOT}\matRad_getPhotonLQMParameters.m
- ${PROJECT_ROOT}\matRad_getRotationMatrix.m
- ${PROJECT_ROOT}\matRad_indicatorWrapper.m
- ${PROJECT_ROOT}\matRad_interp1.m
- ${PROJECT_ROOT}\matRad_interp3.m
- ${PROJECT_ROOT}\matRad_interpRadDepth.m
- ${PROJECT_ROOT}\matRad_progress.m
- ${PROJECT_ROOT}\matRad_rayTracing.m
- ${PROJECT_ROOT}\matRad_rc.m
- ${PROJECT_ROOT}\matRad_resizeCstToGrid.m
- ${PROJECT_ROOT}\matRad_sequencing2ApertureInfo.m
- ${PROJECT_ROOT}\matRad_setOverlapPriorities.m
- ${PROJECT_ROOT}\matRad_showDVH.m
- ${PROJECT_ROOT}\matRad_showQualityIndicators.m
- ${PROJECT_ROOT}\matRad_siddonRayTracer.m
- ${PROJECT_ROOT}\matRad_siochiLeafSequencing.m
- ${PROJECT_ROOT}\matRad_version.m
- ${PROJECT_ROOT}\matRad_visApertureInfo.m
- ${PROJECT_ROOT}\matRad_xiaLeafSequencing.m
- ${PROJECT_ROOT}\matRadGUI.fig
- ${PROJECT_ROOT}\matRadGUI.m
- ${PROJECT_ROOT}\optimization
- ${PROJECT_ROOT}\plotting
- ${PROJECT_ROOT}\tools
-
-
- ${PROJECT_ROOT}\phantoms
- ${PROJECT_ROOT}\standalone\readme_standalone_linux.txt
- ${PROJECT_ROOT}\standalone\readme_standalone_mac.txt
- ${PROJECT_ROOT}\standalone\readme_standalone_windows.txt
-
-
- ${PROJECT_ROOT}\matRad.prj
-
-
- D:\MATLAB\matRadMaster\standalone\for_testing\readme.txt
- D:\MATLAB\matRadMaster\standalone\for_testing\matRad.exe
- D:\MATLAB\matRadMaster\standalone\for_testing\splash.png
-
-
-
- C:\Program Files\MATLAB\R2019a
-
-
- false
- false
- true
- false
- false
- false
- false
- false
- 10.0
- false
- true
- Win64
- true
-
-
-
\ No newline at end of file
diff --git a/matRad/4D/matRad_addMovement.m b/matRad/4D/matRad_addMovement.m
new file mode 100644
index 000000000..db8551dff
--- /dev/null
+++ b/matRad/4D/matRad_addMovement.m
@@ -0,0 +1,157 @@
+function [ct, cst] = matRad_addMovement(ct, cst, motionPeriod, numOfCtScen, amp, varargin)
+% adds artificial sinosodal patient motion by creating a deformation vector
+% field and applying it to the ct.cube by geometric transformation
+%
+% call
+% ct = matRad_addMovement(ct, ct.motionPeriod, ct.numOfCtScen, amp)
+%
+% input
+% ct: matRad ct struct
+% cst: matRad cst struct
+% motionPeriod: the length of a whole breathing cycle (in seconds)
+% numOfCtScen: number of ct phases
+% amp: amplitude of the sinosoidal movement (in pixels)
+% varargin: dvfType: push or pull dvf
+% visBool boolean flag for visualization
+%
+% note: 1st dim --> x LPS coordinate system
+% 2nd dim --> y LPS coordinate system
+% 3rd dim --> z LPS coordinate system
+% a positive amplitude moves the phantom to the right,
+% anterior, inferior
+%
+% output
+% ct: modified matRad ct struct including dvf and cubes for
+% all phases
+% cst: modified matRad cst struct
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+expectedDVF = {'pull', 'push'};
+
+p = inputParser;
+addParameter(p,'dvfType','pull', @(x) any(validatestring(x,expectedDVF)))
+addParameter(p,'visBool',false, @islogical);
+parse(p,varargin{:});
+
+matRad_cfg = MatRad_Config.instance();
+
+% book keeping
+ct.motionPeriod = motionPeriod;
+ct.numOfCtScen = numOfCtScen;
+
+% set type
+ct.dvfMetadata.dvfType = p.Results.dvfType;
+if strcmp(p.Results.dvfType,'push')
+ amp = -amp; %dvf_pull = -dvf_push;
+end
+
+env = matRad_getEnvironment();
+
+% generate scenarios
+for i = 1:numOfCtScen
+
+ if isfield(ct,'hlut')
+ padValue = min(ct.hlut(:,2));
+ else
+ padValue = -1024;
+ end
+
+ ct.dvf{i} = zeros([ct.cubeDim, 3]);
+
+ dVec = arrayfun(@(A) A*sin((i-1)*pi / numOfCtScen)^2, amp);
+
+ ct.dvf{i}(:,:,:,1) = dVec(1); % deformation along x direction (i.e. 2nd coordinate in dose/ct)
+ ct.dvf{i}(:,:,:,2) = dVec(2);
+ ct.dvf{i}(:,:,:,3) = dVec(3);
+
+ matRad_cfg.dispInfo('Deforming ct phase %d with [dx,dy,dz] = [%f,%f,%f] voxels\n',i,dVec(1),dVec(2),dVec(3));
+
+ % warp ct
+ switch env
+ case 'MATLAB'
+ ct.cubeHU{i} = imwarp(ct.cubeHU{1}, ct.dvf{i},'FillValues',padValue);
+
+ if isfield(ct,'cube')
+ ct.cube{i} = imwarp(ct.cube{1}, ct.dvf{i},'FillValues',0);
+ end
+
+ % warp cst
+ for j = 1:size(cst,1)
+ tmp = zeros(ct.cubeDim);
+ tmp(cst{j,4}{1}) = 1;
+ tmpWarp = imwarp(tmp, ct.dvf{i});
+
+ cst{j,4}{i} = find(tmpWarp > .5);
+ end
+ case 'OCTAVE'
+ ct.cubeHU{i} = displaceOctave(ct.cubeHU{1}, ct.dvf{i},'linear',padValue);
+
+ if isfield(ct,'cube')
+ ct.cube{i} = displaceOctave(ct.cube{1},ct.dvf{i},'linear',0);
+ end
+
+ % warp cst
+ for j = 1:size(cst,1)
+ tmp = zeros(ct.cubeDim);
+ tmp(cst{j,4}{1}) = 1;
+ tmpWarp = displaceOctave(tmp, ct.dvf{i},'linear',0);
+
+ cst{j,4}{i} = find(tmpWarp > .5);
+ end
+ otherwise
+ end
+
+ % convert dvfs to [mm]
+ ct.dvf{i}(:,:,:,1) = ct.dvf{i}(:,:,:,1).* ct.resolution.x;
+ ct.dvf{i}(:,:,:,2) = ct.dvf{i}(:,:,:,2).*ct.resolution.y;
+ ct.dvf{i}(:,:,:,3) = ct.dvf{i}(:,:,:,3).* ct.resolution.z;
+
+ ct.dvf{i} = permute(ct.dvf{i}, [4,1,2,3]);
+end
+
+
+
+if p.Results.visBool
+ slice = round(ct.cubeDim(3)/2);
+ figure,
+ for i = 1:numOfCtScen
+ clf,
+ imagesc(ct.cubeHU{i}(:,:,slice))
+ pause(.5);
+ end
+end
+
+end
+
+function newCube = displaceOctave(cube,vectorfield,interpMethod,fillValue)
+x = 1:size(cube,1);
+y = 1:size(cube,2);
+z = 1:size(cube,3);
+
+[X,Y,Z] = meshgrid(x,y,z);
+Xnew = X + vectorfield(:,:,:,1);
+Ynew = Y + vectorfield(:,:,:,2);
+Znew = Z + vectorfield(:,:,:,3);
+
+newCube = interp3(X,Y,Z,cube,Xnew,Ynew,Znew,interpMethod,fillValue);
+end
+
+
+
+
+
diff --git a/matRad/4D/matRad_calc4dDose.m b/matRad/4D/matRad_calc4dDose.m
new file mode 100644
index 000000000..152729597
--- /dev/null
+++ b/matRad/4D/matRad_calc4dDose.m
@@ -0,0 +1,109 @@
+function [resultGUI, timeSequence] = matRad_calc4dDose(ct, pln, dij, stf, cst, resultGUI, totalPhaseMatrix,accType)
+% wrapper for the whole 4D dose calculation pipeline and calculated dose
+% accumulation
+%
+% call
+% ct = matRad_calc4dDose(ct, pln, dij, stf, cst, resultGUI)
+%
+% input
+% ct : ct cube
+% pln: matRad plan meta information struct
+% dij: matRad dij struct
+% stf: matRad steering information struct
+% cst: matRad cst struct
+% resultGUI: struct containing optimized fluence vector
+% totalPhaseMatrix optional intput for totalPhaseMatrix
+% accType: witch algorithim for dose accumulation
+% output
+% resultGUI: structure containing phase dose, RBE weighted dose, etc
+% timeSequence: timing information about the irradiation
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if ~exist('accType','var')
+ accType = 'DDM';
+end
+
+if ~exist('totalPhaseMatrix','var')
+ % make a time sequence for when each bixel is irradiated, the sequence
+ % follows the backforth spot scanning
+ timeSequence = matRad_makeBixelTimeSeq(stf, resultGUI);
+
+ % prepare a phase matrix
+ motion = 'linear'; % the assumed motion type
+ timeSequence = matRad_makePhaseMatrix(timeSequence, ct.numOfCtScen, ct.motionPeriod, motion);
+
+ resultGUI.bioParam = pln.bioParam;
+
+ % the total phase matrix determines what beamlet will be administered in what ct phase
+ totalPhaseMatrix = vertcat(timeSequence.phaseMatrix);
+else
+ timeSequence = [];
+
+end
+
+if any(strcmp(pln.bioParam.model,{'MCN','LEM','WED','HEL'}))
+ [ax,bx] = matRad_getPhotonLQMParameters(cst,numel(resultGUI.physicalDose));
+end
+
+% compute all phases
+for i = 1:ct.numOfCtScen
+
+ tmpResultGUI = matRad_calcCubes(totalPhaseMatrix(:,i),dij,i);
+
+ % compute physical dose for physical opt
+ if strcmp(pln.bioParam.model,'none')
+ resultGUI.phaseDose{i} = tmpResultGUI.physicalDose;
+ % compute RBExD with const RBE
+ elseif strcmp(pln.bioParam.model,'constRBE')
+ resultGUI.phaseRBExD{i} = tmpResultGUI.RBExD;
+ % compute all fields
+ elseif any(strcmp(pln.bioParam.model,{'MCN','LEM','WED','HEL'}))
+ resultGUI.phaseAlphaDose{i} = tmpResultGUI.alpha .* tmpResultGUI.physicalDose;
+ resultGUI.phaseSqrtBetaDose{i} = sqrt(tmpResultGUI.beta) .* tmpResultGUI.physicalDose;
+ ix = ax{i} ~=0;
+ resultGUI.phaseEffect{i} = resultGUI.phaseAlphaDose{i} + resultGUI.phaseSqrtBetaDose{i}.^2;
+ resultGUI.phaseRBExD{i} = zeros(ct.cubeDim);
+ resultGUI.phaseRBExD{i}(ix) = ((sqrt(ax{i}(ix).^2 + 4 .* bx{i}(ix) .* resultGUI.phaseEffect{i}(ix)) - ax{i}(ix))./(2.*bx{i}(ix)));
+ end
+
+end
+
+% accumulation
+if strcmp(pln.bioParam.model,'none')
+
+ resultGUI.accPhysicalDose = matRad_doseAcc(ct,resultGUI.phaseDose, cst, accType);
+
+elseif strcmp(pln.bioParam.model,'constRBE')
+
+ resultGUI.accRBExD = matRad_doseAcc(ct,resultGUI.phaseRBExD, cst, accType);
+
+elseif any(strcmp(pln.bioParam.model,{'MCN','LEM','WED','HEL'}))
+
+ resultGUI.accAlphaDose = matRad_doseAcc(ct,resultGUI.phaseAlphaDose, cst,accType);
+ resultGUI.accSqrtBetaDose = matRad_doseAcc(ct,resultGUI.phaseSqrtBetaDose, cst, accType);
+
+ % only compute where we have biologically defined tissue
+ ix = (ax{1} ~= 0);
+
+ resultGUI.accEffect = resultGUI.accAlphaDose + resultGUI.accSqrtBetaDose.^2;
+
+ resultGUI.accRBExD = zeros(ct.cubeDim);
+ resultGUI.accRBExD(ix) = ((sqrt(ax{1}(ix).^2 + 4 .* bx{1}(ix) .* resultGUI.accEffect(ix)) - ax{1}(ix))./(2.*bx{1}(ix)));
+
+end
+
diff --git a/matRad/4D/matRad_doseAcc.m b/matRad/4D/matRad_doseAcc.m
new file mode 100644
index 000000000..c6d96c2fa
--- /dev/null
+++ b/matRad/4D/matRad_doseAcc.m
@@ -0,0 +1,265 @@
+function dAcc = matRad_doseAcc(ct, phaseCubes, cst, accMethod)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad dose accumulation function
+%
+% call
+% dAcc = matRad_doseAcc(d,dvf)
+%
+% input
+% ct: matRad ct struct inclduing 4d ct, deformation vector
+% fields, and meta information
+% phaseCubes: cell array of cubes to be accumulated
+% cst: matRad cst struct
+% accMethod: method used for accumulation, either direct dose mapping
+% (DDM), energy mass transfer method (EMT), or divergent dose
+% mapping method (DDMP)
+%
+% +++ Attention +++ the deformation vector fields are in [mm]
+%
+% output
+% dAcc: accumulated dose cube
+%
+% References
+% [1] http://iopscience.iop.org/0031-9155/59/21/6401/
+% [2] http://iopscience.iop.org/0031-9155/59/1/173/
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% DDM: direct dose mapping
+% DDMM: divergent dose mapping
+% EMT: energy mass transfer algorithm
+if nargin < 3 % set default accumulation method
+ accMethod = 'DDM';
+end
+
+% book keeping
+ct.cubeDim = ct.cubeDim;
+
+% helper variables
+xGridVec = 1:ct.cubeDim(2);
+yGridVec = 1:ct.cubeDim(1);
+zGridVec = 1:ct.cubeDim(3);
+
+% result container
+dAcc = zeros(ct.cubeDim);
+
+if strcmp(accMethod,'DDM')
+
+ if ~strcmp(ct.dvfMetadata.dvfType,'pull')
+ error('dose accumulation via direct dose mapping (DDM) requires pull dvfs');
+ end
+
+ [X,Y,Z] = meshgrid(xGridVec,yGridVec,zGridVec);
+
+ % TODODODODODOD %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ ix = [];
+ for i = 1:size(cst,1)
+ ix = unique([ix; cst{i,4}{1}]);
+ end
+
+ for i = 1:ct.numOfCtScen
+
+ dvf_x_i = squeeze(ct.dvf{1,i}(1,:,:,:))/ct.resolution.x;
+ dvf_y_i = squeeze(ct.dvf{1,i}(2,:,:,:))/ct.resolution.y;
+ dvf_z_i = squeeze(ct.dvf{1,i}(3,:,:,:))/ct.resolution.z;
+
+ d_ref = matRad_interp3(X, Y, Z,...
+ phaseCubes{i}, X-dvf_x_i,Y-dvf_y_i,Z-dvf_z_i,'linear',0);
+
+ dAcc(ix) = dAcc(ix) + d_ref(ix);
+
+ end
+
+elseif strcmp(accMethod,'EMT') % funktioniert nicht wenn Dosis in einer Phase = 0 ist...
+
+ if ~strcmp(ct.dvfMetadata.dvfType,'push')
+ error('dose accumulation via interpolation requires push dvfs');
+ end
+
+ [X,Y,Z] = ndgrid(xGridVec,yGridVec,zGridVec);
+
+ for i = 1:ct.numOfCtScen
+
+ dvf_x_i = squeeze(ct.dvf{1,i}(1,:,:,:))/ct.resolution.x;
+ dvf_y_i = squeeze(ct.dvf{1,i}(2,:,:,:))/ct.resolution.y;
+ dvf_z_i = squeeze(ct.dvf{1,i}(3,:,:,:))/ct.resolution.z;
+
+ m_i = ct.cube{i};
+ m_i = permute(m_i,[2 1 3]);
+ e_i = phaseCubes{i}.*ct.cube{i};
+ e_i = permute(e_i,[2 1 3]);
+
+ ix = e_i>0;
+
+ m_ref = zeros(ct.cubeDim);
+ e_ref = zeros(ct.cubeDim);
+
+ X_i = X(ix) + dvf_x_i(ix);
+ Y_i = Y(ix) + dvf_y_i(ix);
+ Z_i = Z(ix) + dvf_z_i(ix);
+
+ ix_i = sub2ind(ct.cubeDim,floor(X_i) ,floor(Y_i)+1,floor(Z_i) );
+ overlap = (floor(X_i)-X_i+1) .* (Y_i-floor(Y_i)) .* (floor(Z_i)-Z_i+1);
+
+ m_ref(ix_i) = m_ref(ix_i) + overlap .* m_i(ix);
+ e_ref(ix_i) = e_ref(ix_i) + overlap .* e_i(ix);
+
+ ix_i = sub2ind(ct.cubeDim,floor(X_i) ,floor(Y_i)+1,floor(Z_i)+1);
+ overlap = (floor(X_i)-X_i+1) .* (Y_i-floor(Y_i)) .* (Z_i-floor(Z_i));
+
+ m_ref(ix_i) = m_ref(ix_i) + overlap .* m_i(ix);
+ e_ref(ix_i) = e_ref(ix_i) + overlap .* e_i(ix);
+
+ ix_i = sub2ind(ct.cubeDim,floor(X_i) ,floor(Y_i) ,floor(Z_i)+1);
+ overlap = (floor(X_i)-X_i+1) .* (floor(Y_i)-Y_i+1) .* (Z_i-floor(Z_i));
+
+ m_ref(ix_i) = m_ref(ix_i) + overlap .* m_i(ix);
+ e_ref(ix_i) = e_ref(ix_i) + overlap .* e_i(ix);
+
+ ix_i = sub2ind(ct.cubeDim,floor(X_i) ,floor(Y_i) ,floor(Z_i) );
+ overlap = (floor(X_i)-X_i+1) .* (floor(Y_i)-Y_i+1) .* (floor(Z_i)-Z_i+1);
+
+ m_ref(ix_i) = m_ref(ix_i) + overlap .* m_i(ix);
+ e_ref(ix_i) = e_ref(ix_i) + overlap .* e_i(ix);
+
+ ix_i = sub2ind(ct.cubeDim,floor(X_i)+1,floor(Y_i)+1,floor(Z_i) );
+ overlap = (X_i-floor(X_i)) .* (Y_i-floor(Y_i)) .* (floor(Z_i)-Z_i+1);
+
+ m_ref(ix_i) = m_ref(ix_i) + overlap .* m_i(ix);
+ e_ref(ix_i) = e_ref(ix_i) + overlap .* e_i(ix);
+
+ ix_i = sub2ind(ct.cubeDim,floor(X_i)+1,floor(Y_i)+1,floor(Z_i)+1);
+ overlap = (X_i-floor(X_i)) .* (Y_i-floor(Y_i)) .* (Z_i-floor(Z_i));
+
+ m_ref(ix_i) = m_ref(ix_i) + overlap .* m_i(ix);
+ e_ref(ix_i) = e_ref(ix_i) + overlap .* e_i(ix);
+
+ ix_i = sub2ind(ct.cubeDim,floor(X_i)+1,floor(Y_i) ,floor(Z_i)+1);
+ overlap = (X_i-floor(X_i)) .* (floor(Y_i)-Y_i+1) .* (Z_i-floor(Z_i));
+
+ m_ref(ix_i) = m_ref(ix_i) + overlap .* m_i(ix);
+ e_ref(ix_i) = e_ref(ix_i) + overlap .* e_i(ix);
+
+ ix_i = sub2ind(ct.cubeDim,floor(X_i)+1,floor(Y_i) ,floor(Z_i) );
+ overlap = (X_i-floor(X_i)) .* (floor(Y_i)-Y_i+1) .* (floor(Z_i)-Z_i+1);
+
+ m_ref(ix_i) = m_ref(ix_i) + overlap .* m_i(ix);
+ e_ref(ix_i) = e_ref(ix_i) + overlap .* e_i(ix);
+
+ % if m_ref != 0
+ k = find(m_ref);
+ dAcc(k) = dAcc(k) + e_ref(k)./m_ref(k);
+
+ dAcc = permute(dAcc,[2 1 3]);
+ end
+
+% elseif strcmp(accMethod,'DDMM')
+%
+% % this implementation is experimental
+% warning('The implementation of divergent dose mapping (DDMM) has never been debugged. Use with utmost care...');
+%
+% if ~strcmp(ct.dvfType,'pull')
+% error('dose accumulation via divergent dose mapping requires pull dvfs');
+% end
+%
+% massWeightedDose = d.physicalDose .* ct.cube;
+%
+% l_x = 2; % number of sub samples along x dir
+% l_y = 3; % number of sub samples along y dir
+% l_z = 4; % number of sub samples along z dir
+% l = l_x*l_y*l_z;
+%
+% x_steps = round(linspace(0,ct.cubeDim(1),l_x+1));
+% y_steps = round(linspace(0,ct.cubeDim(2),l_y+1));
+% z_steps = round(linspace(0,ct.cubeDim(3),l_z+1));
+%
+% counter = 0;
+%
+% for i = 1:ct.numOfCtScen
+%
+% curr_dm = massWeightedDose(:,:,:,i);
+% curr_ct = ct.cube(:,:,:,i);
+%
+% dm_i = zeros(ct.cubeDim);
+% m_i = zeros(ct.cubeDim);
+%
+%
+% for x = 1:l_x
+%
+% xStart = xGridVec(x_steps(x)+1) - 1/2 + 1/2/l_x;
+% xEnd = xGridVec(x_steps(x+1)) + 1/2 - 1/2/l_x;
+%
+% highRes_xGridVec = xStart:1/l_x:xEnd;
+%
+% for y = 1:l_y
+%
+% yStart = yGridVec(y_steps(y)+1) - 1/2 + 1/2/l_y;
+% yEnd = yGridVec(y_steps(y+1)) + 1/2 - 1/2/l_y;
+%
+% highRes_yGridVec = yStart:1/l_y:yEnd;
+%
+% for z = 1:l_z
+%
+% zStart = zGridVec(z_steps(z)+1) - 1/2 + 1/2/l_z;
+% zEnd = zGridVec(z_steps(z+1)) + 1/2 - 1/2/l_z;
+%
+% highRes_zGridVec = zStart:1/l_z:zEnd;
+%
+% % interpolation of sub cube of dose to high res
+% %highRes_dose_jkl = interp3(xGridVec,yGridVec',zGridVec, ...
+% % d.physicalDose(:,:,:,i), ...
+% % xGridVec_j,yGridVec_k',zGridVec_l,'linear',0);
+%
+% % interpolation of sub cube of deformation vector fields to high res
+% highRes_dvf_x = interp3(yGridVec,xGridVec',zGridVec, ...
+% squeeze( ct.dvf(1,:,:,:,i) ), ...
+% highRes_yGridVec,highRes_xGridVec',highRes_zGridVec,'linear',0);
+%
+% highRes_dvf_y = interp3(yGridVec,xGridVec',zGridVec, ...
+% squeeze( ct.dvf(2,:,:,:,i) ), ...
+% highRes_yGridVec,highRes_xGridVec',highRes_zGridVec,'linear',0);
+%
+% highRes_dvf_z = interp3(yGridVec,xGridVec',zGridVec, ...
+% squeeze( ct.dvf(3,:,:,:,i) ), ...
+% highRes_yGridVec,highRes_xGridVec',highRes_zGridVec,'linear',0);
+%
+% % conversion to indices
+% [highRes_X,highRes_Y,highRes_Z] = ndgrid(highRes_xGridVec,highRes_yGridVec',highRes_zGridVec);
+%
+% origin_ix = sub2ind(ct.cubeDim,round(highRes_X(:)),round(highRes_Y(:)),round(highRes_Z(:)));
+%
+% highRes_dvf_x = round(highRes_X+highRes_dvf_x/ct.resolution.x);
+% highRes_dvf_y = round(highRes_Y+highRes_dvf_y/ct.resolution.y);
+% highRes_dvf_z = round(highRes_Z+highRes_dvf_z/ct.resolution.z);
+%
+% target_ix = sub2ind(ct.cubeDim,highRes_dvf_x(:),highRes_dvf_y(:),highRes_dvf_z(:));
+%
+%
+% for j = 1:numel(target_ix)
+%
+% dm_i(target_ix(j)) = dm_i(target_ix(j)) + curr_dm(origin_ix(j));
+% m_i (target_ix(j)) = m_i (target_ix(j)) + curr_ct(origin_ix(j));
+%
+% end
+%
+% counter = counter + 1;
+%
+% end
+% end
+% end
+%
+% end
+
+end
diff --git a/matRad/4D/matRad_makeBixelTimeSeq.m b/matRad/4D/matRad_makeBixelTimeSeq.m
new file mode 100644
index 000000000..70036cf20
--- /dev/null
+++ b/matRad/4D/matRad_makeBixelTimeSeq.m
@@ -0,0 +1,208 @@
+function timeSequence = matRad_makeBixelTimeSeq(stf, resultGUI)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% using the steering information of matRad, makes a time sequenced order
+% according to the irradiation scheme in spot scanning
+%
+% call
+% timeSequence = matRad_makeBixelTimeSeq(stf, resultGUI)
+%
+% input
+% stf: matRad steering information struct
+% resultGUI: struct containing optimized fluence vector
+%
+% output
+% timeSequence: struct containing bixel ordering information and the
+% time sequence of the spot scanning
+%
+% References
+% spill structure and timing informations:
+% http://cdsweb.cern.ch/record/1182954
+% http://iopscience.iop.org/article/10.1088/0031-9155/56/20/003/meta
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% defining the constant parameters
+%
+% time required for synchrotron to change energy
+
+es_time = 3 * 10^6; % [\mu s]
+% time required for synchrotron to recharge it' spill
+spill_recharge_time = 2 * 10^6; % [\mu s]
+% number of particles generated in each spill
+spill_size = 4 * 10 ^ 10;
+% speed of synchrotron's lateral scanning in an IES
+scan_speed = 10; % m/s
+% number of particles per second
+spill_intensity = 4 * 10 ^ 8;
+
+
+steerTime = [stf.bixelWidth] * (10 ^ 3)/ scan_speed; % [\mu s]
+
+timeSequence = struct;
+
+% first loop loops over all bixels to store their position and ray number
+% in each IES
+wOffset = 0;
+for i = 1:length(stf) % looping over all beams
+
+ usedEnergies = unique([stf(i).ray(:).energy]);
+ usedEnergiesSorted = sort(usedEnergies, 'descend');
+
+ timeSequence(i).orderToSTF = zeros(stf(i).totalNumOfBixels, 1);
+ timeSequence(i).orderToSS = zeros(stf(i).totalNumOfBixels, 1);
+ timeSequence(i).time = zeros(stf(i).totalNumOfBixels, 1);
+ timeSequence(i).e = zeros(stf(i).totalNumOfBixels, 1);
+
+
+ for e = 1:length(usedEnergies) % looping over IES's
+
+ s = 1;
+
+ for j = 1:stf(i).numOfRays % looping over all rays
+
+ % find the rays which are active in current IES
+ if(any(stf(i).ray(j).energy == usedEnergiesSorted(e)))
+
+ x = stf(i).ray(j).rayPos_bev(1);
+ y = stf(i).ray(j).rayPos_bev(3);
+ %
+ timeSequence(i).IES(e).x(s) = x; % store x position
+ timeSequence(i).IES(e).y(s) = y; % store y position
+ timeSequence(i).IES(e).w_index(s) = wOffset + ...
+ sum(stf(i).numOfBixelsPerRay(1:(j-1))) + ...
+ find(stf(i).ray(j).energy == usedEnergiesSorted(e)); % store index
+
+ s = s + 1;
+
+ end
+ end
+ end
+
+ wOffset = wOffset + sum(stf(i).numOfBixelsPerRay);
+
+end
+
+% after storing all the required information,
+% same loop over all bixels will put each bixel in it's order
+
+spill_usage = 0;
+offset = 0;
+
+for i = 1:length(stf)
+
+ usedEnergies = unique([stf(i).ray(:).energy]);
+
+ t = 0;
+ order_count = 1;
+
+ for e = 1: length(usedEnergies)
+
+ % sort the y positions from high to low (backforth is up do down)
+ y_sorted = sort(unique(timeSequence(i).IES(e).y), 'descend');
+ x_sorted = sort(timeSequence(i).IES(e).x, 'ascend');
+
+ for k = 1:length(y_sorted)
+
+ y = y_sorted(k);
+ % find indexes corresponding to current y position
+ % in other words, number of bixels in the current row
+ ind_y = find(timeSequence(i).IES(e).y == y);
+
+ % since backforth fasion is zig zag like, flip the order every
+ % second row
+ if ~rem(k,2)
+ ind_y = fliplr(ind_y);
+ end
+
+ % loop over all the bixels in the row
+ for is = 1:length(ind_y)
+
+ s = ind_y(is);
+
+ x = x_sorted(s);
+
+ w_index = timeSequence(i).IES(e).w_index(s);
+
+ % in case there were holes inside the plan "multi"
+ % multiplies the steertime to take it into account:
+ if(k == 1 && is == 1)
+ x_prev = x;
+ y_prev = y;
+ end
+ % x direction
+ multi = abs(x_prev - x)/stf(i).bixelWidth;
+ % y direction
+ multi = multi + abs(y_prev - y)/stf(i).bixelWidth;
+ %
+ x_prev = x;
+ y_prev = y;
+
+ % calculating the time:
+
+ % required spot fluence
+ numOfParticles = resultGUI.w(w_index)* 10^6;
+ % time spent to spill the required spot fluence
+ spillTime = numOfParticles * 10^6 / spill_intensity;
+
+ % spotTime:time spent to steer scan along IES per bixel
+ t = t + multi * steerTime(i) + spillTime;
+
+ % taking account of the time to recharge the spill in case
+ % the required fluence was more than spill size
+ if(spill_usage + numOfParticles > spill_size)
+ t = t + spill_recharge_time;
+ spill_usage = 0;
+ end
+
+ % used amount of fluence from current spill
+ spill_usage = spill_usage + numOfParticles;
+
+ % storing the time and the order of bixels
+
+ % make the both counter and index 'per beam' - help index
+ w_ind = w_index - offset;
+
+ % timeline according to the spot scanning order
+ timeSequence(i).time(order_count) = t;
+ % IES of bixels according to the spot scanning order
+ timeSequence(i).e(order_count) = e;
+ % according to spot scanning order, sorts w index of all
+ % bixels, use this order to transfer STF order to Spot
+ % Scanning order
+ timeSequence(i).orderToSS(order_count) = w_ind;
+
+ % according to STF order, gives us order of irradiation of
+ % each bixel, use this order to transfer Spot Scanning
+ % order to STF order
+ % orderToSTF(orderToSS) = orderToSS(orderToSTF) = 1:#bixels
+ timeSequence(i).orderToSTF(w_ind) = order_count;
+
+ order_count = order_count + 1;
+
+ end
+ end
+
+ t = t + es_time;
+
+ end
+
+ % storing the fluence per beam
+ timeSequence(i).w = resultGUI.w(offset + 1: offset + stf(i).totalNumOfBixels);
+
+ offset = offset + stf(i).totalNumOfBixels;
+
+end
+
+end
\ No newline at end of file
diff --git a/matRad/4D/matRad_makePhaseMatrix.m b/matRad/4D/matRad_makePhaseMatrix.m
new file mode 100644
index 000000000..bfc89476c
--- /dev/null
+++ b/matRad/4D/matRad_makePhaseMatrix.m
@@ -0,0 +1,78 @@
+function timeSequence = matRad_makePhaseMatrix(timeSequence, numOfPhases, motionPeriod, motion)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% using the time sequence and the ordering of the bixel iradiation, and
+% number of scenarios, makes a phase matrix of size number of bixels *
+% number of scenarios
+%
+%
+% call
+% timeSequence = matRad_makePhaseMatrix(timeSequence, numOfPhases, motionPeriod, motion)
+%
+% input
+% timeSequence: struct containing bixel ordering information and the
+% time sequence of the spot scanning
+% numOfCtScen: number of the desired phases
+% motionPeriod: the extent of a whole breathing cycle (in seconds)
+% motion: motion scenario: 'linear'(default), 'sampled_period'
+%
+% output
+% timeSequence: phase matrix field added
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% time of each phase [/mu s]
+phaseTime = motionPeriod * 10 ^ 6/numOfPhases;
+
+for i = 1:length(timeSequence)
+
+ realTime = phaseTime;
+ timeSequence(i).phaseMatrix = zeros(length(timeSequence(i).time),numOfPhases);
+
+ iPhase = 1;
+ iTime = 1;
+
+ while (iTime <= length(timeSequence(i).time))
+ if(timeSequence(i).time(iTime) < realTime)
+
+ while(iTime <= length(timeSequence(i).time) && timeSequence(i).time(iTime) < realTime)
+ timeSequence(i).phaseMatrix(iTime, iPhase) = 1;
+ iTime = iTime + 1;
+ end
+
+ else
+
+
+ iPhase = iPhase + 1;
+
+ % back to 1 after going over all phases
+ if(iPhase > numOfPhases)
+ iPhase = 1;
+ end
+
+ realTime = realTime + phaseTime;
+
+ end
+ end
+
+ % permuatation of phaseMatrix from SS order to STF order
+ timeSequence(i).phaseMatrix = timeSequence(i).phaseMatrix(timeSequence(i).orderToSTF,:);
+
+ % inserting the fluence in phaseMatrix
+ timeSequence(i).phaseMatrix = timeSequence(i).phaseMatrix .* timeSequence(i).w;
+
+end
+
diff --git a/matRad/4D/matRad_postprocessing.m b/matRad/4D/matRad_postprocessing.m
new file mode 100644
index 000000000..fb3bec039
--- /dev/null
+++ b/matRad/4D/matRad_postprocessing.m
@@ -0,0 +1,136 @@
+function resultGUI = matRad_postprocessing(resultGUI, dij, pln, cst, stf)
+% matRad postprosseing function accounting for
+% minimum number of particles per spot
+% minimum number of particles per iso-energy slice
+%
+% call
+% resultGUI = matRad_postprocessing(resultGUI, dij, pln, cst, stf)
+
+% input
+% resultGUI struct containing optimized fluence vector
+% dij: matRad dij struct
+% pln: matRad pln struct
+% cst: matRad cst struct
+% stf: matRad stf struct
+%
+% output
+% resultGUI: new w and doses in resultGUI
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+round2 = @(a,b)round(a*10^b)/10^b;
+
+if strcmp(pln.radiationMode,'protons')
+ Imin = 500000/1e6; % intensity per spot
+ minNrParticlesIES = 25000000; % intensity per energy slice
+elseif strcmp(pln.radiationMode,'carbon')
+ Imin = 15000/1e6;
+ minNrParticlesIES = 0;
+else
+ matRad_cfg.dispError('postprocessing only implemented for proton and carbon ion therapy')
+end
+
+% remember old solution
+resultGUI.optW = resultGUI.w;
+if isequal(pln.bioParam.model,'none')
+ resultGUI.optDose = resultGUI.physicalDose;
+else
+ resultGUI.optRBExD = resultGUI.RBExD;
+end
+
+% manipulate fluence vector
+% set intensities to zero if below threshold/2
+resultGUI.w(resultGUI.w=Imin/2) = Imin;
+
+% recalculate cubes!
+calcCubes = matRad_calcCubes(resultGUI.w,dij,1);
+
+% compare dose
+if isequal(pln.bioParam.model,'none')
+ resultGUI.physicalDose = calcCubes.physicalDose;
+ relIntDoseDif = (1-sum(resultGUI.physicalDose(:))/sum(resultGUI.optDose(:)))*100;
+else
+ resultGUI.RBExD = calcCubes.RBExD;
+ relIntDoseDif = (1-sum(resultGUI.RBExD(:))/sum(resultGUI.optRBExD(:)))*100;
+end
+
+if relIntDoseDif ~= 0
+ matRad_cfg.dispInfo('Relative difference in integral dose after deleting spots: %f %%\n',relIntDoseDif);
+end
+
+%% delete IES with less than XXX particles
+if(minNrParticlesIES ~= 0)
+
+ % Find IES values
+ for i = 1:pln.propStf.numOfBeams
+ iesArray = [];
+ for j = 1:stf(i).numOfRays
+ iesArray = unique([iesArray stf(i).ray(j).energy]);
+ end
+
+ for iesArrayIx = 1:length(iesArray)
+
+ iesEnergy = iesArray(iesArrayIx);
+ numParticlesIES = 0;
+ bixelsIES = [];
+
+ for j = 1:stf(i).numOfRays
+ % find index of used energy (round to keV for numerical reasons
+ bixelNb = find(round2(stf(i).ray(j).energy,4) == round2(iesEnergy,4));
+
+ if length(bixelNb)==1 % one IES found
+
+ bixelIndex = find(dij.beamNum==i & dij.rayNum==j & dij.bixelNum==bixelNb);
+
+ numParticles = round(1e6*resultGUI.w(bixelIndex));
+
+ % check whether there are (enough) particles for beam delivery
+ if (numParticles >= Imin)
+ numParticlesIES = numParticlesIES + numParticles;
+ bixelsIES = [bixelsIES bixelIndex];
+ end
+ end
+ end % ray
+
+ if(numParticlesIES < minNrParticlesIES && ~isempty(bixelsIES)) % not enough particles in IES, all spots are deleted
+ matRad_cfg.dispInfo("IES %f in beam %d deleted\n", iesEnergy, i);
+ resultGUI.w(bixelsIES) = 0;
+ end
+
+ end % IES
+ end % beam
+
+ % recalculate cubes!
+ calcCubes = matRad_calcCubes(resultGUI.w,dij,1);
+
+ % compare dose
+ if isequal(pln.bioParam.model,'none')
+ resultGUI.physicalDose = calcCubes.physicalDose;
+ relIntDoseDif = (1-sum(resultGUI.physicalDose(:))/sum(resultGUI.optDose(:)))*100;
+ else
+ resultGUI.RBExD = calcCubes.RBExD;
+ relIntDoseDif = (1-sum(resultGUI.RBExD(:))/sum(resultGUI.optRBExD(:)))*100;
+ end
+
+ if relIntDoseDif ~= 0
+ matRad_cfg.dispInfo('Relative difference in integral dose after deleting IES: %f %%\n',relIntDoseDif);
+ end
+
+end
+
diff --git a/matRad/IO/matRad_exportDij.m b/matRad/IO/matRad_exportDij.m
new file mode 100644
index 000000000..38e578556
--- /dev/null
+++ b/matRad/IO/matRad_exportDij.m
@@ -0,0 +1,232 @@
+function matRad_exportDij(filename,dij,stf,metadata)
+% matRad physical dose writer
+%
+% call
+% matRad_exportDij(filename,dij,stf,...
+% additionalFields,additionalKeyValuePairs)
+%
+% input
+% filename: full output path, including the file extension
+% dij: matRad dij struct
+% stf: matRad stf struct
+% metadata: struct of metadata
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+if nargin<4
+ metadata = struct();
+end
+
+
+%% Prepare Metadata
+
+if ~isfield(metadata,'delimiter')
+ metadata.delimiter = '\t'; %Default delimiter
+end
+
+if ~isfield(metadata,'numScen')
+ metadata.numScen = 1; %Default scenario
+end
+
+if ~isfield(metadata,'individualFiles')
+ metadata.individualFiles = false; %Default individual files option
+end
+
+if ~isfield(metadata,'extension')
+
+ lastdot_pos = find(filename == '.', 1, 'last');
+ extension = filename(lastdot_pos+1:end);
+
+ if strcmp(extension,'txt') || strcmp(extension,'bin')
+ metadata.extension = extension; %Default fileType
+ else
+ metadata.extension = 'txt'; %Default fileType
+ end
+
+end
+
+%% Setup Header
+
+header = sprintf('# %s %s\n',metadata.extension,'file');
+
+%add matRad specific comment
+header = header_addComment(header,'Created With matRad - An open source multi-modality radiation treatment planning sytem');
+
+%% Write File
+try
+
+ %Set up parent export folder and full file path
+ if ~(isfolder('dijExport'))
+ mkdir(matRad_cfg.matRadRoot, 'dijExport');
+ end
+
+ folderPath = [matRad_cfg.matRadRoot filesep 'dijExport' filesep];
+
+ if metadata.individualFiles
+
+ totalNumOfBixels = 1;
+
+ %Create a file for each beam
+ for i = 1:dij.numOfBeams
+
+ %Set a filename for i-th beam file
+ lastdot_pos = find(filename == '.', 1, 'last');
+
+ filename_ith = filename(1:lastdot_pos-1);
+ filename_ith = [filename_ith '_' num2str(i)];
+
+ %Add gantryAngle field to i-th beam header
+ header_ith = header_addIntField(header,'gantry angle',stf(i).gantryAngle);
+ %Add couchAngle field to i-th beam header
+ header_ith = header_addIntField(header_ith,'couch angle', stf(i).couchAngle);
+ %Add totalNumOfBixels field to i-th beam header
+ header_ith = header_addIntField(header_ith,'total number of bixels', stf(i).totalNumOfBixels);
+ %Add dimensions of dose grid field to i-th beam header
+ dimensions = strcat(num2str(dij.doseGrid.dimensions(1)),'|',num2str(dij.doseGrid.dimensions(2)),'|',num2str(dij.doseGrid.dimensions(3)));
+ header_ith = header_addStringField(header_ith,'dose grid dimensions', dimensions);
+ %Add column headers
+ header_ith = header_addComment(header_ith,'voxelID bixelID physicalDose[Gy]');
+
+ %
+ numOfBixels = stf(i).totalNumOfBixels;
+
+ %Read physical dose from non zeros dij
+ [ix,iy,vals] = find(dij.physicalDose{metadata.numScen}(:,totalNumOfBixels:totalNumOfBixels+numOfBixels-1));
+ data=zeros(nnz(vals),3);
+ data(:,1) = ix;
+ data(:,2) = iy+totalNumOfBixels-1;
+ data(:,3) = vals;
+
+ if strcmp(metadata.extension,'txt')
+
+ %Write Header to file with the separating blank line to i-th beam
+ fileHandle = fopen([folderPath filename_ith '.' metadata.extension],'w');
+ fprintf(fileHandle,'%s\n',header_ith);
+
+ %Append data to file to i-th beam
+ %writematrix(data,filename_tmp,'Delimiter',metadata.delimiter,'-append'); % If you use r2019b matlab version
+ dlmwrite([filename_ith '.' metadata.extension],data,'delimiter',metadata.delimiter,'-append');
+
+ fclose(fileHandle);
+
+ elseif strcmp(metadata.extension,'bin')
+
+ %Append data to file to i-th beam
+ fileHandle = fopen([folderPath filename_ith '.' metadata.extension],'w');
+ fwrite(fileHandle,uint32(ix),'uint32');
+ fwrite(fileHandle,uint32(iy),'uint32');
+ fwrite(fileHandle,vals,'double');
+ fclose(fileHandle);
+
+ %Write an additional header file
+ headerHandle = fopen([folderPath filename_ith '_header.txt'],'w');
+ fprintf(headerHandle,'%s\n',header_ith);
+ fclose(headerHandle);
+
+ end
+
+ totalNumOfBixels=totalNumOfBixels+numOfBixels;
+
+ end
+
+ else
+
+ %Add info about each beam
+ for i = 1:dij.numOfBeams
+ %Add info about i-th beam
+ header = header_addIntField(header,'Beam',i);
+ %Add gantryAngle field to i-th beam header
+ header = header_addIntField(header,'gantry angle',stf(i).gantryAngle);
+ %Add couchAngle field to i-th beam header
+ header = header_addIntField(header,'couch angle', stf(i).couchAngle);
+ %Add totalNumOfBixels field to i-th beam header
+ header = header_addIntField(header,'total number of bixels', stf(i).totalNumOfBixels);
+ %Add dimensions of dose grid field to header
+ dimensions = strcat(num2str(dij.doseGrid.dimensions(1)),'|',num2str(dij.doseGrid.dimensions(2)),'|',num2str(dij.doseGrid.dimensions(3)));
+ header = header_addStringField(header,'dose grid dimensions', dimensions);
+ end
+
+ %Set a filename
+ filename = filename(1:lastdot_pos-1);
+
+ %Add column headers
+ header = header_addComment(header,'voxelID bixelID physicalDose[Gy]');
+
+ %Read physical dose from non zeros dij
+ [ix,iy,vals] = find(dij.physicalDose{metadata.numScen});
+ data(:,1) = ix;
+ data(:,2) = iy;
+ data(:,3) = vals;
+
+ if strcmp(metadata.extension,'txt')
+
+ %Write Header to file with the separating blank line to i-th beam
+ fileHandle = fopen([folderPath filename '.' metadata.extension],'w');
+ fprintf(fileHandle,'%s\n',header);
+
+ %Append data to file
+ %writematrix(data,filename,'Delimiter',metadata.delimiter,'-append'); % If you use r2019b matlab version
+ dlmwrite([folderPath filename '.' metadata.extension],data,'delimiter',metadata.delimiter,'-append');
+
+ fclose(fileHandle);
+
+ elseif strcmp(metadata.extension,'bin')
+
+ %Append data to file
+ fileHandle = fopen([folderPath filename '.' metadata.extension],'w');
+ fwrite(fileHandle,uint32(ix),'uint32');
+ fwrite(fileHandle,uint32(iy),'uint32');
+ fwrite(fileHandle,vals,'double');
+ fclose(fileHandle);
+
+ %Write an additional header file
+ headerHandle = fopen([folderPath filename '_header.txt'],'w');
+ fprintf(headerHandle,'%s\n',header);
+ fclose(headerHandle);
+
+ end
+
+ end
+
+catch MExc
+ %if something failed while writing, close all files and display error
+ fclose('all');
+ fprintf(2,'File %s could not be written!\n',filename);
+ if(matRad_cfg.isOctave)
+ error(MExc);
+ else
+ throw(MExc);
+ end
+end
+
+matRad_cfg.dispInfo('Dij exported successfully into %s.\n',strcat(folderPath,filename,'.',metadata.extension));
+
+%Used to add comments to the header
+ function newHeader = header_addComment(header,comment)
+ newHeader = sprintf('%s# %s\n',header,comment);
+ end
+
+%Used to add int fields to the header
+ function newHeader = header_addIntField(header,fieldName,fieldValue)
+ newHeader = sprintf('%s# %s: %d\n',header,fieldName,fieldValue);
+ end
+
+%Used to add string fields to the header
+ function newHeader = header_addStringField(header,fieldName,fieldValue)
+ newHeader = sprintf('%s# %s: %s\n',header,fieldName,fieldValue);
+ end
+
+end
diff --git a/IO/matRad_importPatient.m b/matRad/IO/matRad_importPatient.m
similarity index 74%
rename from IO/matRad_importPatient.m
rename to matRad/IO/matRad_importPatient.m
index 9123e6a15..7404885fe 100644
--- a/IO/matRad_importPatient.m
+++ b/matRad/IO/matRad_importPatient.m
@@ -24,7 +24,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -53,18 +53,18 @@
ct.numOfCtScen = 1;
maskId = 1;
-hGlobalWaitbar = waitbar(0,'Importing Segmentations');
-set(findall(hGlobalWaitbar,'type','text'),'Interpreter','none');
-
-
+matRad_cfg = MatRad_Config.instance();
+hGlobalWaitbar = waitbar(0,'Importing Segmentations','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor);
+matRad_applyThemeToWaitbar(hGlobalWaitbar);
for f=1:numel(maskFiles)
maskFile = maskFiles{f};
waitbar(f/numel(maskFiles),hGlobalWaitbar,['Importing Segmentations: ' maskFiles{f}]);
if exist(maskFile,'dir')
contents = dir(maskFile);
- hFolderWaitbar = waitbar(0,'Importing Folder');
- set(findall(hFolderWaitbar,'type','text'),'Interpreter','none');
+ hFolderWaitbar = waitbar(0,'Importing Folder','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor);
+ matRad_applyThemeToWaitbar(hFolderWaitbar);
+
for s=1:numel(contents)
waitbar(s/numel(contents),hFolderWaitbar,['Importing Folder: ' contents(s).name]);
if(~contents(s).isdir)
@@ -100,24 +100,41 @@
cstLine = cell(1,6);
cstLine{1} = maskId - 1;
cstLine{2} = maskMeta.name;
- cstLine{3} = tryToGetVoiTypeByName(maskMeta.name);
+ [type,priority] = tryToGetVoiTypeByName(maskMeta.name);
+ cstLine{3} = type;
cstLine{4}{1} = find(mask > 0);
- cstLine{5}.Priority = maskId;
+ cstLine{5}.Priority = priority;
cstLine{5}.alphaX = 0.1;
cstLine{5}.betaX = 0.05;
cstLine{5}.Visible = 1;
end
-function type = tryToGetVoiTypeByName(voiName)
- targetNames = {'target'; 'ptv'; 'ctv'};
+function [type,priority] = tryToGetVoiTypeByName(voiName)
+ targetNames = {'target'; 'gtv'; 'ctv'; 'ptv'};
+ targetPriorities = [1 1 2 3];
+ oarPriority = 4;
+ externalPriorities = [5 5 5];
+ externalNames = {'body','external','skin'};
for n=1:numel(targetNames)
found = strfind(lower(voiName),lower(targetNames{n}));
if ~isempty(found)
type = 'TARGET';
+ priority = targetPriorities(n);
return;
end
end
+
+ for n=1:numel(externalNames)
+ found = strfind(lower(voiName),lower(externalNames{n}));
+ if ~isempty(found)
+ type = 'EXTERNAL';
+ priority = externalPriorities(n);
+ return;
+ end
+ end
+
type = 'OAR';
+ priority = oarPriority;
end
diff --git a/matRad/IO/matRad_readBinData.m b/matRad/IO/matRad_readBinData.m
new file mode 100644
index 000000000..3e7cd66ec
--- /dev/null
+++ b/matRad/IO/matRad_readBinData.m
@@ -0,0 +1,26 @@
+function dataOut = matRad_readBinData(binFile,cubeDim)
+
+% Instance of MatRad_Config class
+matRad_cfg = MatRad_Config.instance();
+
+% Read in bin file
+fID = fopen(binFile);
+data = fread(fID,inf,'double');
+fclose(fID);
+
+% Check if consistent with cubeDim
+if rem(numel(data),prod(cubeDim))==0
+ % this is the number of ReportQuantities contained in that file
+ numOfReportQuantities = numel(data)/prod(cubeDim);
+
+ % Save all scored quantities as cell array and reshape to cubeDim
+ dataOut = cell(1,numOfReportQuantities);
+ for i = 1:numOfReportQuantities
+ dataOut{i} = reshape(data(i:numOfReportQuantities:end),cubeDim(2),cubeDim(1),cubeDim(3));
+ dataOut{i} = permute(dataOut{i},[2 1 3]);
+ end
+else
+ matRad_cfg.dispError('bin data contains an odd number of entries.')
+end
+
+end
diff --git a/IO/matRad_readCube.m b/matRad/IO/matRad_readCube.m
similarity index 57%
rename from IO/matRad_readCube.m
rename to matRad/IO/matRad_readCube.m
index f3e8228dc..24173192e 100644
--- a/IO/matRad_readCube.m
+++ b/matRad/IO/matRad_readCube.m
@@ -15,27 +15,38 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%Instantiate matrad config
+matRad_cfg = MatRad_Config.instance();
+
if ~exist(filename,'file')
error(['File ' filename ' does not exist!']);
end
[pathstr,name,ext] = fileparts(filename);
+if strcmp(ext,'.gz')
+ [~,name,subext] = fileparts(name);
+ ext = strcat(subext,ext);
+end
-switch ext
- case {'.nrrd','.NRRD'}
- disp(['Reading NRRD: ' filename '...']);
- [cube, metadata] = matRad_readNRRD(filename);
- disp('Done!');
- otherwise
- error(['Extension ' ext ' not (yet) supported!']);
+[readers] = matRad_supportedBinaryFormats();
+
+readerIx = find(~cellfun(@isempty,strfind({readers.fileFilter},lower(ext))));
+if ~isempty(readerIx) && isscalar(readerIx)
+ readerHandle = readers(readerIx).handle;
+ matRad_cfg.dispInfo('Reading %s: "%s" ...',readers(readerIx).name,filename);
+ [cube,metadata] = readerHandle(filename);
+ matRad_cfg.dispInfo('Done!\n');
+else
+ matRad_cfg.dispError('Extension %s not (yet) supported!',ext);
end
+
metadata.name = name;
metadata.path = pathstr;
diff --git a/IO/matRad_readHLUT.m b/matRad/IO/matRad_readHLUT.m
similarity index 94%
rename from IO/matRad_readHLUT.m
rename to matRad/IO/matRad_readHLUT.m
index 7394b1a52..bffb3d8d9 100644
--- a/IO/matRad_readHLUT.m
+++ b/matRad/IO/matRad_readHLUT.m
@@ -19,7 +19,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/IO/matRad_readMHD.m b/matRad/IO/matRad_readMHD.m
new file mode 100644
index 000000000..02f630dae
--- /dev/null
+++ b/matRad/IO/matRad_readMHD.m
@@ -0,0 +1,131 @@
+function [cube, metadata] = matRad_readMHD(filename)
+% matRad NRRD reader
+%
+% call
+% [cube, metadata] = matRad_readMHD(filename)
+%
+% input
+% filename: full path to mhd or mha file
+%
+% output
+% cube: the read cube
+% metadata: metadata from header information
+%
+% References
+% [1] https://itk.org/Wiki/MetaIO/Documentation
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+%% read header
+headerFileHandle = fopen(filename,'r');
+
+s = textscan(headerFileHandle, '%s', 'delimiter', '\n');
+
+% read dimensions
+idx = find(~cellfun(@isempty,strfind(s{1}, 'DimSize')),1,'first');
+dimensions = cell2mat(textscan(s{1}{idx},'DimSize = %f %f %f'));
+
+% Read resolution
+idx = find(~cellfun(@isempty,strfind(s{1}, 'ElementSpacing')),1,'first');
+tmp = textscan(s{1}{idx},'ElementSpacing = %f %f %f');
+resolution = cell2mat(tmp);
+
+%Endian:
+idx = find(~cellfun(@isempty,strfind(s{1}, 'BinaryDataByteOrderMSB')),1,'first');
+tmp = textscan(s{1}{idx},'BinaryDataByteOrderMSB = %s');
+isLittleEndian = cell2mat(tmp{1});
+switch isLittleEndian
+ case 'True'
+ endian = 'b';
+ case 'False'
+ endian = 'l';
+ otherwise
+ matRad_cfg.dispError('Machine format/endian could not be read!');
+end
+
+
+% read filename of data
+idx = find(~cellfun(@isempty,strfind(s{1}, 'ElementDataFile')),1,'first');
+tmp = textscan(s{1}{idx},'ElementDataFile = %s');
+dataFilename = cell2mat(tmp{1});
+
+% Transform Matrix
+idx = find(~cellfun(@isempty,strfind(s{1}, 'TransformMatrix')),1,'first');
+tmp = textscan(s{1}{idx},'TransformMatrix = %f %f %f %f %f %f %f %f %f');
+T = zeros(3);
+T(:) = cell2mat(tmp);
+
+% Apply Matlab permutation
+Tmatlab = [0 1 0; 1 0 0; 0 0 1];
+%T = T * [0 1 0; 1 0 0; 0 0 1];
+
+% get data type
+idx = find(~cellfun(@isempty,strfind(s{1}, 'ElementType')),1,'first');
+tmp = textscan(s{1}{idx},'ElementType = %s');
+type = MHAtypeToMatlab(cell2mat(tmp{1}));
+
+if strcmpi(dataFilename,'LOCAL')
+ %read raw block
+ test = cast(1,type);
+ S = whos('test');
+ fseek(headerFileHandle,-S.bytes*prod(dimensions),'eof');
+ cube = fread(headerFileHandle,prod(dimensions),type,endian);
+ cube = reshape(cube,dimensions);
+ cube = permute(cube,[2 1 3]);
+ %matRad_cfg.dispError('MHA not implemented!');
+else
+ %% read data
+ [filepath,~,~] = fileparts(filename);
+ dataFileHandle = fopen(fullfile(filepath,dataFilename),'r');
+ cube = reshape(fread(dataFileHandle,inf,type,endian),dimensions);
+ cube = permute(cube,[2 1 3]);
+ %cube = flip(cube,1);
+ fclose(dataFileHandle);
+end
+fclose(headerFileHandle);
+metadata.resolution = resolution;
+metadata.cubeDim = dimensions * Tmatlab;
+
+end
+
+function newType = MHAtypeToMatlab(datatype)
+switch datatype
+ case 'MET_FLOAT'
+ newType = 'single';
+ case 'MET_DOUBLE'
+ newType = 'double';
+ case 'MET_UCHAR'
+ newType = 'uint8';
+ case 'MET_CHAR'
+ newType = 'char';
+ case 'MET_SHORT'
+ newType = 'int16';
+ case 'MET_USHORT'
+ newType = 'uint16';
+ case 'MET_INT'
+ newType = 'int32';
+ case 'MET_LONG'
+ newType = 'int64';
+ case 'MET_UINT'
+ newType = 'uint32';
+ case 'MET_ULONG'
+ newType = 'uint64';
+ otherwise
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError(['Datatype ' datatype ' not supported by MHD/MHA importer!']);
+end
+end
+
diff --git a/IO/matRad_readNRRD.m b/matRad/IO/matRad_readNRRD.m
similarity index 94%
rename from IO/matRad_readNRRD.m
rename to matRad/IO/matRad_readNRRD.m
index 8b132f04a..85c55c7df 100644
--- a/IO/matRad_readNRRD.m
+++ b/matRad/IO/matRad_readNRRD.m
@@ -20,7 +20,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -130,7 +130,7 @@
if numel(sizes{1}) ~= metadata.dimension || ~all(sizes{1} > 0)
matRad_cfg.dispError('Incorrect size definition!');
end
- metadata.cubeDim = sizes{1}';
+ metadata.cubeDim = double(sizes{1}');
else
matRad_cfg.dispError('Could not find required "dimension" field!');
end
@@ -198,7 +198,24 @@
originFieldIx = find(ismember(nrrdMetaData.fields(:,1), 'space'));
if ~isempty(originFieldIx)
metadata.coordinateSystem = nrrdMetaData.fields{originFieldIx,2};
+
+ switch metadata.coordinateSystem
+ case {'LPS','left-posterior-superior'}
+ metadata.coordinateSystem = 'LPS';
+ systemTransform = [0 -1 0; -1 0 0; 0 0 1];
+ case {'RAS','right-anterior-superior'}
+ metadata.coordinateSystem = 'RAS';
+ systemTransform = [1 0 0; 0 1 0; 0 0 1];
+ case {'LAS','left-anterior-superior'}
+ metadata.coordinateSystem = 'LAS';
+ systemTransform = [0 1 0; 1 0 0; 0 0 1];
+
+ otherwise
+ systemTransform = eye(3);
+ end
end
+
+
%Check for separate file
data_fileFieldIx = find(ismember(nrrdMetaData.fields(:,1), 'data file'));
diff --git a/matRad/IO/matRad_readNifTI.m b/matRad/IO/matRad_readNifTI.m
new file mode 100644
index 000000000..dfeb3ed9e
--- /dev/null
+++ b/matRad/IO/matRad_readNifTI.m
@@ -0,0 +1,65 @@
+function [cube, metadata] = matRad_readNifTI(filename)
+% matRad NifTI reader
+%
+% call
+% [cube, metadata] = matRad_readNifTI(filename)
+%
+% input
+% filename: full path to .nii(.gz) file
+%
+% output
+% cube: the read cube
+% metadata: metadata from header information
+%
+% References
+% [1] http://teem.sourceforge.net/nrrd/format.html5
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%Instantiate matrad config
+matRad_cfg = MatRad_Config.instance();
+
+%Check for Image Processing toolbox
+avail = license('test', 'image_toolbox');
+if ~avail
+ matRad_cfg.dispError('Image Processing Toolbox is required for reading NifTI files!\n');
+else
+ success = license('checkout', 'image_toolbox');
+ if ~success
+ matRad_cfg.dispError('Image Processing Toolbox license could not be checked out!\n');
+ end
+end
+
+%Obtain NifTI info strcuture
+try
+ info = niftiinfo(filename);
+catch ME
+ matRad_cfg.dispError('Error reading NifTI file: %s\n', ME.message);
+end
+
+% Read the cube
+try
+ cube = niftiread(info);
+catch ME
+ matRad_cfg.dispError('Error reading NifTI file: %s\n', ME.message);
+end
+
+metadata = struct();
+metadata.datatype = info.Datatype;
+metadata.axisPermutation = [2 1 3];
+metadata.cubeDim = info.ImageSize(metadata.axisPermutation);
+cube = permute(cube,metadata.axisPermutation);
+metadata.transform = info.Transform.T;
+metadata.resolution = info.PixelDimensions;
+
diff --git a/matRad/IO/matRad_supportedBinaryFormats.m b/matRad/IO/matRad_supportedBinaryFormats.m
new file mode 100644
index 000000000..ec5354088
--- /dev/null
+++ b/matRad/IO/matRad_supportedBinaryFormats.m
@@ -0,0 +1,66 @@
+function [readers,writers] = matRad_supportedBinaryFormats()
+% matRad function to obtain supported binary formats
+%
+% call
+% [read,write] = matRad_supportedBinaryFormats()
+%
+% input
+%
+% output
+% read cell array with file filter in first column, name in second
+% column, and handle to read function in third column
+% write cell array with file filter in first column, name in second
+% column, and handle to write function in third column
+%
+% References
+% [1] http://teem.sourceforge.net/nrrd/format.html5
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%available readers
+readers(1).fileFilter = '*.nrrd';
+readers(1).name = 'NRRD';
+readers(1).handle = @matRad_readNRRD;
+
+readers(2).fileFilter = '*.nii;*.nii.gz';
+readers(2).name = 'NifTI';
+readers(2).handle = @matRad_readNifTI;
+
+readers(3).fileFilter = '*.mha;*.mhd';
+readers(3).name = 'MHA/MHD';
+readers(3).handle = @matRad_readMHD;
+
+%available writers
+writers(1).fileFilter = '*.nrrd';
+writers(1).name = 'NRRD';
+writers(1).handle = @matRad_writeNRRD;
+
+writers(2).fileFilter = '*.nii';
+writers(2).name = 'NifTI';
+writers(2).handle = @matRad_writeNifTI;
+
+writers(3).fileFilter = '*.vtk';
+writers(3).name = 'VTK';
+writers(3).handle = @matRad_writeVTK;
+
+writers(4).fileFilter = '*.mha';
+writers(4).name = 'MHA';
+writers(4).handle = @matRad_writeMHA;
+
+writers(5).fileFilter = '*.mhd';
+writers(5).name = 'MHD';
+writers(5).handle = @matRad_writeMHD;
+
+end
+
diff --git a/IO/matRad_writeCube.m b/matRad/IO/matRad_writeCube.m
similarity index 74%
rename from IO/matRad_writeCube.m
rename to matRad/IO/matRad_writeCube.m
index acc6f5499..7bdadd419 100644
--- a/IO/matRad_writeCube.m
+++ b/matRad/IO/matRad_writeCube.m
@@ -28,24 +28,26 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+
%% Sanity checks
[filedir,filename,ext] = fileparts(filepath);
if ~exist(filedir,'dir')
- error(['Directory ' filedir ' does not exist!']);
+ matRad_cfg.dispError('Directory %s does not exist!', filedir);
end
%No Special characters in filename (allow only _ and alphanumeric
%characters
robustFileName = filename(isstrprop(filename,'alphanum') | filename == '_');
if ~strcmp(robustFileName,filename)
- warning(['Changing filename from ''' filename ''' to ''' robustFileName ''' to get rid of special characters!']);
+ matRad_cfg.dispWarning('Changing filename from ''%s'' to ''%s'' to get rid of special characters!',filename,robustFileName);
filepath = fullfile(filedir,[robustFileName ext]);
end
@@ -60,6 +62,16 @@
if ~isfield(metadata,'coordinateSystem')
metadata.coordinateSystem = 'LPS'; %Matlab coordinate system
end
+
+% Ensure resolution is given
+if ~isfield(metadata,'resolution')
+ matRad_cfg.dispError('metadata.resolution is required!');
+end
+
+if isstruct(metadata.resolution) && all(isfield(metadata.resolution,{'x','y','z'}))
+ metadata.resolution = [metadata.resolution.x metadata.resolution.y metadata.resolution.z];
+end
+
%If there is no image origin set, center the image
imageExtent = metadata.resolution .* size(cube);
if ~isfield(metadata,'imageOrigin')
@@ -75,18 +87,18 @@
%% Choose writer
%So far we only have an nrrd writer
-switch ext
- case '.nrrd'
- matRad_writeNRRD(filepath,cube,metadata);
- case '.vtk'
- matRad_writeVTK(filepath,cube,metadata);
- case '.mha'
- matRad_writeMHA(filepath,cube,metadata);
- otherwise
- errordlg(['No writer found for extension "' ext '"']);
+[~,writers] = matRad_supportedBinaryFormats();
+
+writerIx = find(~cellfun(@isempty,strfind({writers.fileFilter},ext)));
+
+if ~isempty(writerIx) && isscalar(writerIx)
+ writerHandle = writers(writerIx).handle;
+ writerHandle(filepath,cube,metadata);
+else
+ matRad_cfg.dispError('No unique writer found for extension "%s"',ext);
end
-fprintf('File written to %s...\n',filepath);
+matRad_cfg.dispInfo('File written to %s...\n',filepath);
saved_metadata = metadata;
diff --git a/matRad/IO/matRad_writeMHA.m b/matRad/IO/matRad_writeMHA.m
new file mode 100644
index 000000000..095641183
--- /dev/null
+++ b/matRad/IO/matRad_writeMHA.m
@@ -0,0 +1,135 @@
+function matRad_writeMHA(filepath,cube,metadata)
+% matRad function to write mha files
+%
+% call
+% matRad_writeMHA(filepath,cube,metadata)
+%
+% input
+% filepath: full filename (with extension)
+% cube: 3D array to be written into file
+% metadata: struct of metadata. Writer will wrap the existing metadata
+% to MHA standard-specific fields
+% Necessary fieldnames are:
+% - resolution: [x y z]
+% - datatype: numeric MATLAB-Datatype
+%
+% output
+% file will be written to disk
+%
+% References
+% https://itk.org/Wiki/MetaIO/Documentation
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+matRad_cfg = MatRad_Config.instance();
+
+%Sanity checks and restrictions
+dimensions = size(cube);
+if numel(dimensions) ~= 3
+ matRad_cfg.dispError('Sorry! matRad only supports 3-dimensional MHA output');
+end
+
+fid = fopen(filepath, 'wb');
+if fid <= 0
+ matRad_cfg.dispError('Could not open MHA destination file!');
+end
+%cleaner = onCleanup(@() fclose(fid));
+
+%We perform the permutation
+if isfield(metadata,'axisPermutation')
+ cube = permute(cube,metadata.axisPermutation);
+end
+
+%Set up Transform Matrix
+T=zeros(4);
+ixOnes = sub2ind([4 4],metadata.axisPermutation,[1 2 3]);
+T(ixOnes) = 1;
+T(4,4) = 1;
+
+%Correct for coordinate system
+switch metadata.coordinateSystem
+ case 'LPS'
+ %tmpT = eye(4,4);
+ %tmpT(1:2,1:2) = 1*tmpT(1:2,1:2);
+ %T = T*tmpT;
+ otherwise
+ matRad_cfg.dispError('Only LPS currently supported for export!');
+end
+
+%Now add Translation
+%The transformation matrix is now the unit matrix
+%transformMatrix = diag(ones(1,numel(dimensions)));
+%tmString = sprintf(' %d',transformMatrix(:));
+
+tmString = sprintf(' %d',T(1:3,1:3));
+
+%Determine the endian
+[~,~,endian] = computer;
+switch endian
+ case 'L'
+ byteOrderMSB = 'False';
+ case 'B'
+ byteOrderMSB = 'True';
+ otherwise
+ error('Unknown endian!');
+end
+
+fprintf(fid, 'ObjectType = Image\n');
+fprintf(fid, 'NDims = %d\n',numel(dimensions));
+fprintf(fid, 'BinaryData = True\n');
+fprintf(fid, 'BinaryDataByteOrderMSB = %s\n',byteOrderMSB); %Not sure about this field
+%fprintf(fid, 'ElementByteOrderMSB = %s\n',byteOrderMSB); %Not sure about this field
+fprintf(fid, 'TransformMatrix =%s\n',tmString);
+fprintf(fid, 'Offset = %f %f %f\n',metadata.imageOrigin(1),metadata.imageOrigin(2),metadata.imageOrigin(3));
+fprintf(fid, 'AnatomicalOrientation = RAI\n'); %Did not double check this line
+fprintf(fid, 'ElementSpacing = %f %f %f\n',metadata.resolution(1),metadata.resolution(2),metadata.resolution(3));
+fprintf(fid, 'DimSize = %d %d %d\n',dimensions(1),dimensions(2),dimensions(3));
+fprintf(fid, 'ElementType = %s\n',matlabTypeToMHAtype(metadata.datatype));
+fprintf(fid, 'ElementDataFile = LOCAL\n');
+fwrite(fid,cube,metadata.datatype,lower(endian));
+fclose(fid);
+
+end
+
+function newType = matlabTypeToMHAtype(datatype)
+switch datatype
+ case {'single','float'}
+ newType = 'MET_FLOAT';
+ case 'double'
+ newType = 'MET_DOUBLE';
+ case {'uchar','uint8'}
+ newType = 'MET_UCHAR';
+ case {'logical','int8','char'}
+ newType = 'MET_CHAR';
+ case 'int16'
+ newType = 'MET_SHORT';
+ case 'uint16'
+ newType = 'MET_USHORT';
+ case 'int32'
+ newType = 'MET_INT';
+ case 'int64'
+ newType = 'MET_LONG';
+ case 'uint32'
+ newType = 'MET_UINT';
+ case 'uint64'
+ newType = 'MET_ULONG';
+ otherwise
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError(['Datatype ' datatype ' not supported by MHA exporter!']);
+end
+end
+
+
+
diff --git a/IO/matRad_writeMHA.m b/matRad/IO/matRad_writeMHD.m
similarity index 78%
rename from IO/matRad_writeMHA.m
rename to matRad/IO/matRad_writeMHD.m
index 92164dc3e..7075d8ae8 100644
--- a/IO/matRad_writeMHA.m
+++ b/matRad/IO/matRad_writeMHD.m
@@ -1,4 +1,4 @@
-function matRad_writeMHA(filepath,cube,metadata)
+function matRad_writeMHD(filepath,cube,metadata)
% matRad function to write mha files
%
% call
@@ -25,24 +25,26 @@ function matRad_writeMHA(filepath,cube,metadata)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
%Sanity checks and restrictions
dimensions = size(cube);
if numel(dimensions) ~= 3
- error('Sorry! matRad only supports 3-dimensional MHA output');
+ matRad_cfg.dispError('Sorry! matRad only supports 3-dimensional MHD output');
end
fid = fopen(filepath, 'wb');
if fid <= 0
- error('Could not open MHA destination file!');
+ matRad_cfg.dispError('Could not open MHD destination file!');
end
-cleaner = onCleanup(@() fclose(fid));
%We perform the permutation
if isfield(metadata,'axisPermutation')
@@ -56,28 +58,35 @@ function matRad_writeMHA(filepath,cube,metadata)
[~,~,endian] = computer;
switch endian
case 'L'
- byteOrderMSB = 'True';
- case 'B'
byteOrderMSB = 'False';
+ case 'B'
+ byteOrderMSB = 'True';
otherwise
error('Unknown endian!');
end
+[path,name,ext] = fileparts(filepath);
+filenameRaw = [name '.raw'];
+
fprintf(fid, 'ObjectType = Image\n');
fprintf(fid, 'NDims = %d\n',numel(dimensions));
fprintf(fid, 'BinaryData = True\n');
fprintf(fid, 'BinaryDataByteOrderMSB = %s\n',byteOrderMSB); %Not sure about this field
-fprintf(fid, 'ElementByteOrderMSB = %s\n',byteOrderMSB); %Not sure about this field
+%fprintf(fid, 'ElementByteOrderMSB = %s\n',byteOrderMSB); %Not sure about this field
fprintf(fid, 'TransformMatrix =%s\n',tmString);
fprintf(fid, 'Offset = %f %f %f\n',metadata.imageOrigin(1),metadata.imageOrigin(2),metadata.imageOrigin(3));
fprintf(fid, 'AnatomicalOrientation = RAI\n'); %Did not double check this line
fprintf(fid, 'ElementSpacing = %f %f %f\n',metadata.resolution(1),metadata.resolution(2),metadata.resolution(3));
fprintf(fid, 'DimSize = %d %d %d\n',dimensions(1),dimensions(2),dimensions(3));
fprintf(fid, 'ElementType = %s\n',matlabTypeToMHAtype(metadata.datatype));
-fprintf(fid, 'ElementDataFile = LOCAL\n');
-fwrite(fid,cube,metadata.datatype,'b');
+fprintf(fid, sprintf('ElementDataFile = %s\n',filenameRaw));
fclose(fid);
+%% write data file
+dataFileHandle = fopen(fullfile(path,filenameRaw),'w');
+fwrite(dataFileHandle,cube(:),metadata.datatype,lower(endian));
+fclose(dataFileHandle);
+
end
function newType = matlabTypeToMHAtype(datatype)
@@ -102,9 +111,11 @@ function matRad_writeMHA(filepath,cube,metadata)
newType = 'MET_UINT';
case 'uint64'
newType = 'MET_ULONG';
- otherwise
- error(['Datatype ' datatype ' not supported by MHA exporter!']);
+ otherwise
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError(['Datatype ' datatype ' not supported by MHD exporter!']);
end
end
+
diff --git a/IO/matRad_writeNRRD.m b/matRad/IO/matRad_writeNRRD.m
similarity index 98%
rename from IO/matRad_writeNRRD.m
rename to matRad/IO/matRad_writeNRRD.m
index af2055326..d676fcdc0 100644
--- a/IO/matRad_writeNRRD.m
+++ b/matRad/IO/matRad_writeNRRD.m
@@ -20,7 +20,7 @@ function matRad_writeNRRD(filename,cube,metadata)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/IO/matRad_writeNifTI.m b/matRad/IO/matRad_writeNifTI.m
new file mode 100644
index 000000000..53972e66c
--- /dev/null
+++ b/matRad/IO/matRad_writeNifTI.m
@@ -0,0 +1,113 @@
+function [cube, metadata] = matRad_writeNifTI(filepath,cube,metadata)
+% matRad NifTI reader
+%
+% call
+% [cube, metadata] = matRad_writeNifTI(filename)
+%
+% input
+% filename: full path to .nii(.gz) file
+%
+% output
+% file will be written to disk
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%Instantiate matrad config
+matRad_cfg = MatRad_Config.instance();
+
+%Check for Image Processing toolbox
+avail = license('test', 'image_toolbox');
+if ~avail
+ matRad_cfg.dispError('Image Processing Toolbox is required for reading NifTI files!\n');
+else
+ success = license('checkout', 'image_toolbox');
+ if ~success
+ matRad_cfg.dispError('Image Processing Toolbox license could not be checked out!\n');
+ end
+end
+
+info = struct();
+
+[~,~,endian] = computer;
+switch endian
+ case 'L'
+ endian = 'little';
+ case 'B'
+ endian = 'big';
+ otherwise
+ matRad_cfg.dispError('Unknown endian!');
+end
+
+%Check if compression was defined
+if ~isfield(metadata,'compress') || ~islogical(metadata.compress)
+ metadata.compress = true;
+end
+
+%Default Matlab Axis Permuation
+metadata.axisPermutation = [2 1 3];
+cube = permute(cube,metadata.axisPermutation);
+
+info.PixelDimensions = [metadata.resolution(metadata.axisPermutation)];
+info.Datatype = metadata.datatype;
+info.ImageSize = size(cube);
+info.Description = sprintf('Exported from matRad %s',matRad_version());
+
+if max(info.ImageSize) > 32767
+ info.Version = 'NIfTI2';
+else
+ info.Version = 'NIfTI1';
+end
+
+info.Qfactor = 1;
+info.SpaceUnits = 'Millimeter';
+info.TimeUnits = 'None';
+info.SliceCode = 'Unknown';
+info.FrequencyDimension = 0;
+info.PhaseDimension = 0;
+info.SpatialDimension = 0;
+
+%Set up Transform Matrix
+info.TransformName = 'Sform';
+
+T=eye(4);
+
+
+%Correct for coordinate system
+switch metadata.coordinateSystem
+ case 'LPS'
+ tmpT = eye(4,4);
+ tmpT(1:2,1:2) = -1*tmpT(1:2,1:2);
+ T = T*tmpT;
+ otherwise
+ matRad_cfg.dispError('Only LPS currently supported for export!');
+end
+
+%Add resolution
+T(:,1) = T(:,1) * metadata.resolution(metadata.axisPermutation(1));
+T(:,2) = T(:,2) * metadata.resolution(metadata.axisPermutation(2));
+T(:,3) = T(:,3) * metadata.resolution(metadata.axisPermutation(3));
+
+%Now add Translation
+T(4,1) = metadata.imageOrigin(metadata.axisPermutation(1));
+T(4,2) = metadata.imageOrigin(metadata.axisPermutation(2));
+T(4,3) = metadata.imageOrigin(metadata.axisPermutation(3));
+
+info.Transform = affine3d(T);
+cube = cast(cube,metadata.datatype);
+
+niftiwrite(cube,filepath,info,'Endian',endian,'Compressed',metadata.compress);
+
+
+
diff --git a/IO/matRad_writeVTK.m b/matRad/IO/matRad_writeVTK.m
similarity index 96%
rename from IO/matRad_writeVTK.m
rename to matRad/IO/matRad_writeVTK.m
index 7dd5a549c..70b5467d8 100644
--- a/IO/matRad_writeVTK.m
+++ b/matRad/IO/matRad_writeVTK.m
@@ -25,7 +25,7 @@ function matRad_writeVTK(filepath,cube,metadata)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/MatRad_Config.m b/matRad/MatRad_Config.m
new file mode 100644
index 000000000..37c839430
--- /dev/null
+++ b/matRad/MatRad_Config.m
@@ -0,0 +1,658 @@
+classdef MatRad_Config < handle
+ % MatRad_Config MatRad Configuration class
+ % This class is used globally through Matlab to handle default values and
+ % logging and is declared as global matRad_cfg.
+ % Usage:
+ % matRad_cfg = MatRad_Config.instance();
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2019 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ properties
+
+ %Logging
+ logLevel = 4; %1 = only Errors, 2 = with Warnings, 3 = Info output, 4 = deprecation warnings, 5 = debug information
+ keepLog = false; %Stores the full log in memory
+ writeLog = false; %Writes the log to a file on-the-fly
+
+ defaults;
+
+ %Disable GUI
+ disableGUI = false;
+
+ devMode = false;
+ eduMode = false;
+
+ gui;
+
+ %User folders
+ userfolders; %Cell array of user folders containing machines, patients, hluts. Default contains the userdata folder in the matRad root directory
+ end
+
+ %Deprecated properties referencing a newer one
+ properties (Dependent,SetAccess = private)
+ %Default Properties
+ propDoseCalc;
+ propOpt;
+ propStf;
+ end
+
+ properties (SetAccess = private)
+ messageLog = {};
+ logFileHandle;
+
+ %For storing the Environment & its version
+ env;
+ envVersion;
+ isOctave; %Helper bool to check for Octave
+ isMatlab; %Helper bool to check for Matlab
+ matRad_version; %MatRad version string
+
+ matRadRoot; %Path to matRadRoot
+ end
+
+ properties (SetAccess = private, Dependent)
+ matRadSrcRoot; %Path to matRadSrcRoot ("matRad" subfolder of matRadRoot)
+ primaryUserFolder; %Points to the first entry in userfolders
+ exampleFolder; %Contains examples
+ thirdPartyFolder; %Contains third party tools/libraries used in matRad
+ end
+
+ methods (Access = private)
+ function obj = MatRad_Config()
+ %MatRad_Config Constructs an instance of this class.
+ % The configuration is implemented as a singleton and used globally
+ % Therefore its constructor is private
+ % For instantiation, use the static MatRad_Config.instance();
+
+ %Set Path
+ if isdeployed
+ obj.matRadRoot = [ctfroot filesep 'matRad'];
+
+ if ispc
+ userdir= getenv('USERPROFILE');
+ else
+ userdir= getenv('HOME');
+ end
+
+ userfolderInHomeDir = [userdir filesep 'matRad'];
+
+ obj.userfolders = {userfolderInHomeDir};
+ else
+ obj.matRadRoot = fileparts(fileparts(mfilename('fullpath')));
+ addpath(genpath(obj.matRadSrcRoot));
+ addpath(obj.exampleFolder);
+ addpath(genpath(obj.thirdPartyFolder));
+ obj.userfolders = {[obj.matRadRoot filesep 'userdata' filesep]};
+ end
+
+
+
+ %Set Version
+ obj.getEnvironment();
+ obj.matRad_version = matRad_version(obj.matRadRoot);
+
+ %Configure Environment
+ obj.configureEnvironment();
+
+ %Just to catch people messing with the properties in the file
+ if ~isempty(obj.writeLog) && obj.writeLog
+ logFile = [obj.matRadRoot filesep 'matRad.log'];
+ obj.logFileHandle = fopen(logFile,'a');
+ end
+
+ %Call the reset function for remaining inatialization
+ obj.reset();
+ end
+
+ function delete(~)
+ %might not be desired by users
+ %rmpath(genpath(matRad_cfg.matRadRoot));
+ end
+
+ function displayToConsole(obj,type,formatSpec,varargin)
+ %displayToConsole lowest-level logging function for matRad.
+ % Display to console will be called from the public wrapper
+ % functions dispError, dispWarning, dispInfo, dispDebug
+ %
+ % input
+ % type: type of the log information.
+ % Needs to be one of 'error', 'warning', 'info' or 'debug'.
+ % formatSpec: string to print using format specifications similar to fprintf
+ % varargin: variables according to formatSpec
+
+ if nargin < 4
+ forwardArgs = {formatSpec};
+ else
+ forwardArgs = [{formatSpec},varargin(:)'];
+ end
+
+ if obj.keepLog
+ obj.messageLog{end+1,1} = upper(type);
+ obj.messageLog{end,2} = sprintf(forwardArgs{:});
+ end
+
+ switch type
+ case{'info'}
+ if obj.logLevel >= 3
+ fprintf(forwardArgs{:});
+ end
+ case{'debug'}
+ if obj.logLevel >= 5
+ forwardArgs{1} = ['DEBUG: ' forwardArgs{1}];
+ fprintf(forwardArgs{:});
+ end
+ case{'dep'}
+ if obj.logLevel >= 4
+ forwardArgs{1} = ['DEPRECATION WARNING: ' forwardArgs{1}];
+ warning('matRad:Deprecated',forwardArgs{:});
+ end
+ case{'warning'}
+ if obj.logLevel >= 2
+ warning('matRad:Warning',forwardArgs{:});
+ end
+ case {'error'}
+ if obj.logLevel >= 1
+ %We create an error structure to later clean the
+ %stack trace from the last two files/lines (i.e.,
+ %this function / file)
+
+ err.message = sprintf(forwardArgs{:});
+ err.identifier = 'matRad:Error';
+ %err.stack = dbstack(2);
+ error(err);
+
+ end
+ otherwise
+ error('Log type %s not defined!',type);
+ end
+
+ if obj.writeLog
+ fprintf(obj.logFileHandle,forwardArgs{:});
+ end
+ end
+ end
+
+ methods
+ function reset(obj)
+ %Set all default properties for matRad's computations
+ obj.setDefaultProperties();
+ obj.setDefaultGUIProperties();
+ end
+
+ function setDefaultProperties(obj)
+ %setDefaultProperties set matRad's default computation
+ % properties
+ % input
+
+ %Default Steering/Geometry Properties
+ obj.defaults.propStf.generator = {'PhotonIMRT','ParticleIMPT','SimpleBrachy'};
+ obj.defaults.propStf.longitudinalSpotSpacing = 2;
+ obj.defaults.propStf.addMargin = true; %expand target for beamlet finding
+ obj.defaults.propStf.bixelWidth = 5;
+
+ %Dose Calculation Options
+ obj.defaults.propDoseCalc.engine = {'SVDPB','HongPB'}; %Names for default engines used when no other is given
+ obj.defaults.propDoseCalc.doseGrid.resolution = struct('x',3,'y',3,'z',3); %[mm]
+ obj.defaults.propDoseCalc.dosimetricLateralCutOff = 0.995; %[rel.]
+ obj.defaults.propDoseCalc.geometricLateralCutOff = 50; %[mm]
+ obj.defaults.propDoseCalc.kernelCutOff = Inf; %[mm]
+ obj.defaults.propDoseCalc.ssdDensityThreshold = 0.05; %[rel.]
+ obj.defaults.propDoseCalc.useGivenEqDensityCube = false; %Use the given density cube ct.cube and omit conversion from cubeHU.
+ obj.defaults.propDoseCalc.ignoreOutsideDensities = true; %Ignore densities outside of cst contours
+ obj.defaults.propDoseCalc.useCustomPrimaryPhotonFluence = false; %Use a custom primary photon fluence
+ obj.defaults.propDoseCalc.calcLET = true; %calculate LETs for particles
+ obj.defaults.propDoseCalc.selectVoxelsInScenarios = 'all';
+ obj.defaults.propDoseCalc.airOffsetCorrection = true;
+ % default properties for fine sampling calculation
+ obj.defaults.propDoseCalc.fineSampling.sigmaSub = 1;
+ obj.defaults.propDoseCalc.fineSampling.N = 2;
+ obj.defaults.propDoseCalc.fineSampling.method = 'fitCircle';
+ %Monte Carlo options
+ obj.defaults.propDoseCalc.numHistoriesPerBeamlet = 2e4;
+ obj.defaults.propDoseCalc.numHistoriesDirect = 1e6;
+ obj.defaults.propDoseCalc.outputMCvariance = true;
+
+ %Optimization Options
+ obj.defaults.propOpt.optimizer = 'IPOPT';
+ obj.defaults.propOpt.maxIter = 500;
+ obj.defaults.propOpt.runDAO = 0;
+ obj.defaults.propOpt.clearUnusedVoxels = false;
+
+ %Sequencing Options
+ obj.defaults.propSeq.sequencer = 'siochi';
+
+
+
+ obj.disableGUI = false;
+
+ obj.defaults.samplingScenarios = 25;
+
+ obj.devMode = false;
+ obj.eduMode = false;
+
+ end
+
+ %%For testing
+ function setDefaultPropertiesForTesting(obj)
+ %setDefaultPropertiesForTesting sets matRad's default
+ %properties during testing to reduce computational load
+
+ obj.setDefaultProperties();
+
+ obj.logLevel = 1; %Omit output except errors
+
+ %Default Steering/Geometry Properties
+ obj.defaults.propStf.longitudinalSpotSpacing = 20;
+ obj.defaults.propStf.bixelWidth = 20;
+
+ %Dose Calculation Options
+ obj.defaults.propDoseCalc.doseGrid.resolution = struct('x',5,'y',6,'z',7); %[mm]
+ obj.defaults.propDoseCalc.geometricLateralCutOff = 20;
+ obj.defaults.propDoseCalc.dosimetricLateralCutOff = 0.8;
+ obj.defaults.propDoseCalc.kernelCutOff = 20; %[mm]
+
+ %Monte Carlo options
+ obj.defaults.propDoseCalc.numHistoriesPerBeamlet = 100;
+ obj.defaults.propDoseCalc.numHistoriesDirect = 100;
+
+ %Optimization Options
+ obj.defaults.propOpt.maxIter = 10;
+
+ obj.defaults.samplingScenarios = 2;
+
+ obj.disableGUI = true;
+
+ obj.devMode = true;
+ obj.eduMode = false;
+ end
+
+ %%for edu mode
+ function setDefaultPropertiesForEduMode(obj)
+ obj.setDefaultProperties();
+
+ obj.logLevel = 2;
+
+ %Default Steering/Geometry Properties
+ obj.defaults.propStf.longitudinalSpotSpacing = 3;
+
+ %Dose calculation options
+ obj.defaults.propDoseCalc.resolution = struct('x',4,'y',4,'z',4); %[mm]
+ obj.defaults.propDoseCalc.lateralCutOff = 0.975; %[rel.]
+
+ %Optimization Options
+ obj.defaults.propOpt.maxIter = 500;
+
+ obj.disableGUI = false;
+
+ obj.devMode = false;
+ obj.eduMode = true;
+ end
+
+ function setDefaultGUIProperties(obj)
+ %Detect current theme
+ light = false;
+ try
+ if ispc
+ light = logical(winqueryreg('HKEY_CURRENT_USER','Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize','AppsUseLightTheme'));
+ elseif ismac
+ out = system('defaults read -g AppleInterfaceStyle');
+ if ~strcmp(out,'Dark')
+ light = true;
+ end
+ else
+ out = system('gsettings get org.gnome.desktop.interface color-scheme');
+ if strcmp(out,'prefer-light')
+ light = true;
+ end
+ end
+ catch
+ light = false;
+ end
+
+ if light
+ theme = matRad_ThemeLight();
+ else
+ theme = matRad_ThemeDark();
+ end
+
+ obj.gui = struct(theme);
+ end
+
+ function dispDebug(obj,formatSpec,varargin)
+ %dispDebug print debug messages (log level >= 4)
+ % input
+ % formatSpec: string to print using format specifications similar to fprintf
+ % varargin: variables according to formatSpec
+
+ obj.displayToConsole('debug',formatSpec,varargin{:});
+ end
+
+ function dispInfo(obj,formatSpec,varargin)
+ %dispInfo print information console output (log level >= 3)
+ % input
+ % formatSpec: string to print using format specifications similar to fprintf
+ % varargin: variables according to formatSpec
+ obj.displayToConsole('info',formatSpec,varargin{:});
+ end
+
+ function dispError(obj,formatSpec,varargin)
+ %dispError print errors (forwarded to "error" that will stop the program) (log level >= 1)
+ % input
+ % formatSpec: string to print using format specifications
+ % similar to 'error'
+ % varargin: variables according to formatSpec
+
+ try
+ obj.displayToConsole('error',formatSpec,varargin{:});
+ catch ME
+ if obj.isOctave
+ ME.stack = ME.stack(3:end); % Removes the dispError and dispToConsole from the stack
+ error(ME);
+ else
+ throwAsCaller(ME);
+ end
+ end
+ end
+
+ function dispWarning(obj,formatSpec,varargin)
+ %dispError print warning (forwarded to 'warning') (log level >= 2)
+ % input
+ % formatSpec: string to print using format specifications
+ % similar to 'warning'
+ % varargin: variables according to formatSpec
+ obj.displayToConsole('warning',formatSpec,varargin{:});
+ end
+
+ function dispDeprecationWarning(obj,formatSpec,varargin)
+ %dispDeprecationWarning wrapper for deprecation warnings forwarded to displayToConsole
+ obj.displayToConsole('dep',formatSpec,varargin{:});
+ end
+
+ function obj = writeLogToFile(obj,filename)
+ %writeLogToFile writes the log kept in MatRad_Config to file.
+ % Note that the switch keepLog must be enabled for MatRad_Config to store all logging output.
+
+ singleString = '%s: %s\n';
+ fID = fopen(filename,'w');
+ fprintf(fID,repmat(singleString,1,size(obj.messageLog,1)),obj.messageLog{:});
+ fclose(fID);
+ end
+
+ function set.logLevel(obj,newLogLevel)
+ %%Property set methods for logLevel
+ minLevel = 1;
+ maxLevel = 5;
+ if newLogLevel >= minLevel && newLogLevel <= maxLevel
+ obj.logLevel = newLogLevel;
+ else
+ obj.dispError('Invalid log level. Value must be between %d and %d',minLevel,maxLevel);
+ end
+ end
+
+ function set.userfolders(obj,userfolders)
+ oldFolders = obj.userfolders;
+
+ %Check if folders need to be created
+ for f = 1:numel(userfolders)
+ if ~isfolder(userfolders{f})
+ [status, msg] = mkdir(userfolders{f});
+ if status == 0
+ obj.dispWarning('Userfolder %s not added beacuse it could not be created: %s',userfolders{f},msg);
+ else
+ subfolders = {'hluts','machines','patients','scripts'};
+ [status,msgs] = cellfun(@(sub) mkdir([userfolders{f} filesep sub]),subfolders,'UniformOutput',false);
+ if any(cell2mat(status) ~= 1)
+ obj.dispWarning('Problem when creating subfolder in Userfolder %s!',userfolders{f})
+ end
+ end
+ end
+ end
+
+ %We do this to verify folders
+ nonWorkingFolders = cellfun(@isempty,userfolders);
+ userfolders(nonWorkingFolders) = [];
+
+ allNewFolders = cellfun(@dir, userfolders,'UniformOutput',false);
+ if isempty(allNewFolders)
+ obj.dispWarning('No user folders specified. Defaulting to userdata folder in matRad root directory.');
+ if ~isdeployed
+ allNewFolders = {[fileparts(mfilename('fullpath')) filesep 'userdata' filesep]}; %We don't access obj.matRadRoot here because of Matlab's weird behavior with properties
+ else
+ allNewFolders = {[ctfroot filesep 'userdata' filesep]}; %We don't access obj.matRadRoot here because of Matlab's weird behavior with properties
+ end
+ end
+
+ cleanedNewFolders = cellfun(@(x) x(1).folder,allNewFolders,'UniformOutput',false);
+
+ % Identify newly added folder paths and add them to path
+ if ~isdeployed
+ if ~isempty(oldFolders) %if statement for octave compatibility
+ addedFolders = setdiff(cleanedNewFolders, oldFolders);
+ else
+ addedFolders = cleanedNewFolders;
+ end
+ addedFolders = cellfun(@genpath,addedFolders,'UniformOutput',false);
+ addedFolders = strjoin(addedFolders,pathsep);
+ addpath(addedFolders);
+ end
+
+ % Identify removed folder paths
+ if ~isempty(oldFolders) %if statement for octave compatibility
+ removedFolders = setdiff(oldFolders, cleanedNewFolders);
+ removedFolders = cellfun(@genpath,removedFolders,'UniformOutput',false);
+ removedFolders = strjoin(removedFolders,pathsep);
+ if ~isdeployed
+ rmpath(removedFolders);
+ end
+ end
+
+ obj.userfolders = cleanedNewFolders;
+ end
+
+ function srcRoot = get.matRadSrcRoot(obj)
+ srcRoot = [obj.matRadRoot filesep 'matRad' filesep];
+ end
+
+ function primaryUserFolder = get.primaryUserFolder(obj)
+ primaryUserFolder = obj.userfolders{1};
+ end
+
+ function exampleFolder = get.exampleFolder(obj)
+ exampleFolder = [obj.matRadRoot filesep 'examples' filesep];
+ end
+
+ function thirdPartyFolder = get.thirdPartyFolder(obj)
+ thirdPartyFolder = [obj.matRadRoot filesep 'thirdParty' filesep];
+ end
+
+ function propDoseCalc = get.propDoseCalc(obj)
+ obj.dispWarning('Property ''propDoseCalc'' is deprecated. Use ''defaults.propDoseCalc'' instead!');
+
+ fNames = fieldnames(obj.defaults.propDoseCalc);
+ for i = 1:numel(fNames)
+ fNewName = ['default' upper(fNames{i}(1)) fNames{i}(2:end)];
+ propDoseCalc.(fNewName) = obj.defaults.propDoseCalc.(fNames{i});
+ end
+ end
+
+ function propStf = get.propStf(obj)
+ obj.dispWarning('Property ''propStf'' is deprecated. Use ''defaults.propStf'' instead!');
+
+ fNames = fieldnames(obj.defaults.propStf);
+ for i = 1:numel(fNames)
+ fNewName = ['default' upper(fNames{i}(1)) fNames{i}(2:end)];
+ propStf.(fNewName) = obj.defaults.propStf.(fNames{i});
+ end
+ end
+
+ function propOpt = get.propOpt(obj)
+ obj.dispWarning('Property ''propOpt'' is deprecated. Use ''defaults.propStf'' instead!');
+
+ fNames = fieldnames(obj.defaults.propOpt);
+ for i = 1:numel(fNames)
+ fNewName = ['default' upper(fNames{i}(1)) fNames{i}(2:end)];
+ propOpt.(fNewName) = obj.defaults.propOpt.(fNames{i});
+ end
+ end
+
+ function set.writeLog(obj,writeLog)
+ if writeLog
+ logFile = [obj.matRadRoot filesep 'matRad.log'];
+ obj.logFileHandle = fopen(logFile,'a');
+ obj.writeLog = true;
+ else
+ fclose(obj.logFileHandle);
+ obj.writeLog = false;
+ end
+ end
+
+ function getEnvironment(obj)
+ % getEnvironment function to get the software environment
+ % matRad is running on
+
+ obj.isOctave = exist('OCTAVE_VERSION', 'builtin') ~= 0;
+ obj.isMatlab = ~obj.isOctave;
+
+ if obj.isOctave
+ obj.env = 'OCTAVE';
+ obj.envVersion = OCTAVE_VERSION;
+ else
+ obj.env = 'MATLAB';
+ vData = ver(obj.env);
+ obj.envVersion = vData.Version;
+
+ end
+ end
+
+ function pln = getDefaultProperties(obj,pln,fields)
+ % Function to load all non-set parameters into pln struct
+ standardFields = {'propDoseCalc','propOpt','propStf'};
+
+ % Check if only one argument was given
+ if ~iscell(fields)
+ fields = cellstr(fields);
+ end
+
+ for i = 1:length(fields)
+ currField = fields{i};
+
+ if ismember(currField,standardFields)
+ % Get defaults for standard fields that can easily be read from set default values
+ if ~isfield(pln,currField)
+ pln.(currField) = obj.defaults.(currField);
+ else
+ pln.(currField) = matRad_recursiveFieldAssignment(pln.(currField),obj.defaults.(currField),false);
+ end
+ end
+ end
+ end
+
+ function configureEnvironment(obj)
+ if obj.isOctave
+ struct_levels_to_print(0); %Disables full printing of struct array fields
+ warning("off","Octave:data-file-in-path"); %Disables warning of loading patients from the data folder
+ end
+ end
+ end
+
+ %methods (Access = private)
+ % function renameFields(obj)
+ %end
+
+ methods (Static)
+
+ function obj = instance()
+ %instance creates a singleton instance of MatRad_Config
+ % In MatRad_Config, the constructor is private to make sure only on global instance exists.
+ % Call this static functino to get or create an instance of the matRad configuration class
+ persistent uniqueInstance;
+
+ if isempty(uniqueInstance)
+ obj = MatRad_Config();
+ uniqueInstance = obj;
+ else
+ obj = uniqueInstance;
+ end
+ end
+
+ function obj = loadobj(sobj)
+ % Overload the loadobj function to allow downward compatibility
+ % with workspaces which where saved as an older version of this class
+
+ function basic_struct = mergeStructs(basic_struct, changed_struct)
+ % nested function for merging the properties of the loaded
+ % obj into a new obj.
+ % Merges two structs, including nestes structs, by overwriting
+ % the properties of basic_struct with the changed properties in changed_struct
+ fields = fieldnames(basic_struct);
+ for k = 1:length(fields)
+ disp(fields{k});
+ if(isfield(changed_struct, fields{k}))
+ if isstruct(changed_struct.(fields{k})) && isstruct(basic_struct.(fields{i}))
+ basic_struct.(fields{k}) = mergeStructs(basic_struct.(fields{k}), changed_struct.(fields{i}));
+ else
+ basic_struct.(fields{k}) = changed_struct.(fields{k});
+ end
+ end
+ end
+ end
+
+ % If the saved object is loaded as a struct there was a problem
+ % with the generic loading process most likly a version-conflict
+ % regarding the structs, in order to fix this, do a custom
+ % loading process including recursivly copying the conflicting structs
+ if isstruct(sobj)
+ warning('The loaded object differs from the current MatRad_Config class, resuming the loading process with the overloaded loadobj function!');
+ obj = MatRad_Config();
+ % Use a metaclass object to get the properties because
+ % Octave <= 5.2 doesn't have a properties function
+ meta = metaclass(obj);
+ props = {meta.PropertyList.Name};
+ % Throw warning if the version differs and remove the
+ % matRad_version field from the loaded struct, in order to
+ % not overwrite the version later
+ if (isfield(sobj, 'matRad_version') && ~(strcmp(obj.matRad_version, sobj.matRad_version)))
+ warning('MatRad version or git Branch of the loaded object differs from the curret version!');
+ sobj = rmfield(sobj, 'matRad_version');
+ end
+ % Itterate over the properties of the newly created MatRad_Config object
+ for i = 1:length(props)
+ % check if the field exists in the loaded object
+ if(isfield(sobj,props{i}))
+ objField = obj.(props{i});
+ sobjField = sobj.(props{i});
+ % If field from loaded object and from the newly
+ % created object are equal skip it, else copy the
+ % value of the loaded object and if it's a struct
+ % check it's field recursively
+ if ~(isequal(sobjField, objField))
+ if (isstruct(sobjField) && isstruct(objField))
+ retStruct = mergeStructs(objField,sobjField);
+ obj.(props{i}) = retStruct;
+ else
+ obj.(props{i}) = sobjField;
+ end
+ end
+ end
+ end
+ else
+ obj = sobj;
+ end
+ end
+
+
+ end
+end
+
diff --git a/matRad/basedata/brachy_HDR.mat b/matRad/basedata/brachy_HDR.mat
new file mode 100644
index 000000000..a1f403e0d
Binary files /dev/null and b/matRad/basedata/brachy_HDR.mat differ
diff --git a/matRad/basedata/brachy_LDR.mat b/matRad/basedata/brachy_LDR.mat
new file mode 100644
index 000000000..15a6c531c
Binary files /dev/null and b/matRad/basedata/brachy_LDR.mat differ
diff --git a/matRad/basedata/carbon_Generic.mat b/matRad/basedata/carbon_Generic.mat
new file mode 100644
index 000000000..615ea244b
Binary files /dev/null and b/matRad/basedata/carbon_Generic.mat differ
diff --git a/matRad/basedata/helium_Generic.mat b/matRad/basedata/helium_Generic.mat
new file mode 100644
index 000000000..cc749590a
Binary files /dev/null and b/matRad/basedata/helium_Generic.mat differ
diff --git a/matRad/basedata/matRad_MCemittanceBaseData.m b/matRad/basedata/matRad_MCemittanceBaseData.m
new file mode 100644
index 000000000..d8871bfaf
--- /dev/null
+++ b/matRad/basedata/matRad_MCemittanceBaseData.m
@@ -0,0 +1,696 @@
+classdef matRad_MCemittanceBaseData
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % matRad_MCemmitanceBaseData This is the superclass for MonteCarlo base
+ % data calculation
+ %
+ %
+ %
+ %
+ %
+ % References
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ machine %matRad base data machine struct
+ bdl_path = '' %stores path to generated file
+ nozzleToIso %Nozzle to Isocenter Distance
+ smx %Scanning magnet X to isocenter Distance
+ smy %Scanning magnet y to isocenter Distance
+ monteCarloData %MC Phase space data struct
+ selectedFocus %array containing selected focus indices per energy
+ defaultRelativeEnergySpread = 0; %default energy spread
+ matRad_cfg %matRad config
+ rangeShifters %Stores range shifters
+
+ % air correction in beam optics approximation
+ fitWithSpotSizeAirCorrection = true;
+ fitCorrectDoubleGaussian = true;
+
+ %To force the phase space approximation even if we have the data
+ forceSpectrumApproximation = false;
+ forceEmittanceApproximation = false;
+ forceFixedMU = true;
+ end
+
+ properties (SetAccess = private)
+ stfCompressed %measure whether function has additional info about
+ %the stf
+ problemSigma % = 1, when there was a problem calculating sigma
+ energyIndex %Indices of calculated energies
+ end
+
+ methods
+ function obj = matRad_MCemittanceBaseData(machine,stf)
+ %matRad_MCsquareBaseData construct an instance of the MCsquare
+ %Base data format using a focus index
+
+ %stfCompressed states whether monteCarloData are calculated for
+ %all energies (false) or only for energies which exist in given
+ %stf. If function is called without stf stfCompressed = false.
+ if nargin < 2 || isempty(stf)
+ obj.stfCompressed = false;
+ else
+ obj.stfCompressed = true;
+ obj = obj.getRangeShiftersFromStf(stf);
+ end
+
+ obj.matRad_cfg = MatRad_Config.instance();
+
+ obj.machine = machine;
+ obj.problemSigma = false;
+ obj.selectedFocus = ones(numel(machine.data),1) * NaN;
+
+ if isfield(machine.meta,'BAMStoIsoDist')
+ obj.nozzleToIso = machine.meta.BAMStoIsoDist;
+ else
+ obj.matRad_cfg.dispWarning('No information on BAMS to isocenter distance. Using generic value of 500mm');
+ obj.nozzleToIso = 500;
+ obj.machine.meta.BAMStoIsoDist = 500;
+ end
+
+ if all(isfield(machine.meta,{'SAD_x','SAD_y'}))
+ obj.smx = machine.meta.SAD_x;
+ obj.smy = machine.meta.SAD_y;
+ elseif isfield(machine.meta,'SAD')
+ SAD = machine.meta.SAD;
+ obj.smx = SAD;
+ obj.smy = SAD;
+ else
+ obj.matRad_cfg.dispError('No SAD found!');
+ end
+
+ obj.monteCarloData = [];
+
+ %select needed energies and according focus indices by using stf
+ if obj.stfCompressed
+ tmp = [stf(:).ray];
+ plannedEnergies = [tmp.energy];
+ focusIndex = [tmp.focusIx];
+ [~, ind] = unique(plannedEnergies);
+ plannedEnergies = plannedEnergies(ind);
+ focusIndex = focusIndex(ind);
+ [~ ,obj.energyIndex, ~] = intersect([machine.data(:).energy],plannedEnergies);
+
+ %if no stf was refered all energies are chosen, while setting
+ %the focus index for all energies to preliminary 1
+ else
+ plannedEnergies = [machine.data(:).energy];
+ focusIndex = ones(size(plannedEnergies));
+ [~ ,obj.energyIndex, ~] = intersect([machine.data(:).energy],plannedEnergies);
+ end
+
+ obj.selectedFocus(obj.energyIndex) = focusIndex;
+
+ count = 1;
+ for i = 1:length(obj.energyIndex)
+ ixE = obj.energyIndex(i);
+
+ %look up whether MonteCarlo data are already present in
+ %machine file , if so do not recalculate
+ if isfield(machine.data(ixE), 'energySpectrum') && ~obj.forceSpectrumApproximation
+ energySpectrum = machine.data(ixE).energySpectrum;
+ if isfield(energySpectrum,'type') && strcmp(energySpectrum.type,'gaussian')
+ energyData.NominalEnergy = ones(1,4) * machine.data(ixE).energy(:);
+ energyData.MeanEnergy = machine.data(ixE).energySpectrum.mean(:);
+ energyData.EnergySpread = machine.data(ixE).energySpectrum.sigma(:);
+ else
+ energyData = obj.fitPhaseSpaceForEnergy(ixE);
+ end
+ else
+ energyData = obj.fitPhaseSpaceForEnergy(ixE);
+ end
+
+ if isfield(machine.data(ixE),'MUdata') && ~obj.forceFixedMU
+ energyData.ProtonsMU = machine.data(ixE).MUdata.numParticlesPerMU;
+ else
+ energyData.ProtonsMU = 1e6; %Standard w calibration
+ end
+
+ if isfield(machine.data(ixE).initFocus,'emittance') && ~obj.forceEmittanceApproximation
+ data = [];
+ focusIx = obj.selectedFocus(ixE);
+ emittance = machine.data(ixE).initFocus.emittance(focusIx);
+
+ if ~strcmpi(emittance.type,'bigaussian')
+ matRad_cfg.dispError('Can not handle emittance of type ''%S''!',emittance.type);
+ end
+
+ nGauss = 1;
+ if isfield(emittance,'weight')
+ nGauss = length(emittance.weight) + 1;
+ end
+
+ if nGauss > 2
+ matRad_cfg.dispError('Can not process more than two Gaussians in Emittance parameterization!');
+ end
+
+ opticsData.Weight1 = 1;
+ opticsData.SpotSize1x = emittance.sigmaX(1);
+ opticsData.Divergence1x = emittance.divX(1);
+ opticsData.Correlation1x = emittance.corrX(1);
+ opticsData.SpotSize1y = emittance.sigmaY(1);
+ opticsData.Divergence1y = emittance.divY(1);
+ opticsData.Correlation1y = emittance.corrY(1);
+
+ if nGauss == 1
+ opticsData.Weight2 = 0;
+ opticsData.SpotSize2x = 0;
+ opticsData.Divergence2x = 0;
+ opticsData.Correlation2x = 0;
+ opticsData.SpotSize2y = 0;
+ opticsData.Divergence2y = 0;
+ opticsData.Correlation2y = 0;
+ else
+ opticsData.Weight1 = 1 - emittance.weight(1);
+ opticsData.Weight2 = emittance.weight(1);
+ opticsData.SpotSize2x = emittance.sigmaX(2);
+ opticsData.Divergence2x = emittance.divX(2);
+ opticsData.Correlation2x = emittance.corrX(2);
+ opticsData.SpotSize2y = emittance.sigmaY(2);
+ opticsData.Divergence2y = emittance.divY(2);
+ opticsData.Correlation2y = emittance.corrY(2);
+ end
+
+ %opticsData.FWHMatIso = 2.355 * sigmaNull;
+ opticsData.FWHMatIso = machine.data(ixE).initFocus.SisFWHMAtIso;
+
+ tmp = energyData;
+ f = fieldnames(opticsData);
+ for a = 1:length(f)
+ tmp.(f{a}) = opticsData.(f{a});
+ end
+
+ data = [data; tmp];
+ else
+ data = [];
+ tmp = energyData;
+ for j = 1:size(machine.data(ixE).initFocus.sigma,1)
+
+ % tmp = energyData;
+ opticsData = obj.fitBeamOpticsForEnergy(ixE, j);
+
+ f = fieldnames(opticsData);
+ for a = 1:length(f)
+ if j == 1
+ tmp.(f{a}) = opticsData.(f{a});
+ else
+ tmp.(f{a}) = [tmp.(f{a}), opticsData.(f{a})];
+ end
+ end
+
+ data = tmp;
+ end
+
+ end
+
+
+ obj.monteCarloData = [obj.monteCarloData, data];
+ count = count + 1;
+ end
+
+ %throw out warning if there was a problem in calculating the
+ %width of the Bragg peak in obj.fitBeamOpticsForEnergy
+ if obj.problemSigma
+ obj.matRad_cfg.dispWarning('Calculation of FWHM of bragg peak in base data not possible! Using simple approximation for energy spread');
+ end
+ end
+
+
+ function mcDataEnergy = fitPhaseSpaceForEnergy(obj,energyIx)
+ %function to calculate mean energy and energy spread used by
+ %mcSquare for given energy
+
+ %Considers air distance from nozzle to phantom surface
+ %used in the machine data. 0 means fitted to vacuum simulations
+ %with surface at isocenter
+ if ~isfield(obj.machine.meta, 'fitAirOffset')
+ fitAirOffset = 0;
+ % warning('Could not find fitAirOffset. Using default value (no correction / fit in vacuum).');
+ else
+ fitAirOffset = obj.machine.meta.fitAirOffset;
+ end
+ dR = 0.0011 * (fitAirOffset);
+ %dR = 0;
+
+ i = energyIx;
+
+ %mcDataEnergy.NominalEnergy = ones(1, size(obj.machine.data(1).initFocus.dist,1)) * obj.machine.data(i).energy;
+ mcDataEnergy.NominalEnergy = obj.machine.data(i).energy;
+
+ newDepths = linspace(0,obj.machine.data(i).depths(end),numel(obj.machine.data(i).depths) * 100);
+ newDose = interp1(obj.machine.data(i).depths, obj.machine.data(i).Z, newDepths, 'spline');
+
+ %find FWHM w50 of bragg peak and range of 80% does fall off
+ [maxV, maxI] = max(newDose);
+ [~, r80ind] = min(abs(newDose(maxI:end) - 0.8 * maxV));
+ r80ind = r80ind - 1;
+ r80 = interp1(newDose(maxI + r80ind - 1:maxI + r80ind + 1), ...
+ newDepths(maxI + r80ind - 1:maxI + r80ind + 1), 0.8 * maxV);% ...
+ % + obj.machine.data(i).offset + dR;
+
+ %Correct r80 with dR
+ r80 = r80 + dR + obj.machine.data(i).offset;
+
+ [~, d50rInd] = min(abs(newDose(maxI:end) - 0.5 * maxV));
+ d50rInd = d50rInd - 1;
+ d50_r = interp1(newDose(maxI + d50rInd - 1:maxI + d50rInd + 1), ...
+ newDepths(maxI + d50rInd - 1:maxI + d50rInd + 1), 0.5 * maxV);
+
+ if (newDose(1) < 0.5 * maxV)
+ [~, d50lInd] = min(abs(newDose(1:maxI) - 0.5*maxV));
+ d50_l = interp1(newDose(d50lInd - 1:d50lInd + 1), ...
+ newDepths(d50lInd - 1:d50lInd + 1), 0.5 * maxV);
+ w50 = d50_r - d50_l;
+ %if width left of peak cannot be determined use r80 as width
+ else
+ % d50_l = newDepths(maxI);
+ w50 = r80;
+ obj.problemSigma = true;
+ end
+
+ %material dependent straggling factor for water (Bortfeld 1998
+ %Eq. (18) & (B2)
+ %We assume alpha to contain the A/Z^2 dependence across ions!
+ alphaStraggling = 0.086; %MeV^2/cm
+ fStragglingFactor = @(alpha,p) sqrt(alphaStraggling * p^3 * alpha^(2/p) / (3*p - 2)); %(sigma_straggling = 0.012*R^0.935), for calculations in cm
+ fEnergyFromRange = @(R,alpha,p) (R./(10*alpha)).^(1/p);
+
+
+ %calcualte mean energy used my mcSquare with a formula fitted
+ %to TOPAS data
+ switch obj.machine.meta.radiationMode
+ case 'protons'
+ %some constants according to Bortfeld (1993). Note that
+ %the paper usually works in [cm]
+ alpha = 2.2e-3;
+ p = 1.77;
+
+ stragglingFactor = fStragglingFactor(alpha,p);
+
+ %some functions describing range/energy relation and
+ %straggling
+
+ %polyfit to MC for energy from range (in mm)
+ fMeanEnergyFromRange = @(R) 5.762374661332111e-20 * R.^9 - 9.645413625310569e-17 * R.^8 + 7.073049219034644e-14 * R.^7 ...
+ - 2.992344292008054e-11 * R.^6 + 8.104111934547256e-09 * R.^5 - 1.477860913846939e-06 * R.^4 ...
+ + 1.873625800704108e-04 * R.^3 - 1.739424343114980e-02 * R.^2 + 1.743224692623838e+00 * R ...
+ + 1.827112816899668e+01;
+ %alternatively we can use range energy relationship
+ %Bortfeld (1993) Eq. (4) * 10 for [mm]
+ %meanEnergyFromRange = @(R) (R./(10*alpha)).^(1/p);
+
+ %polyfit to MC for straggling width
+ %sigmaRangeStragglingOnlySq = @(x) 2.713311945114106e-20 * x^9 - 4.267890251195303e-17 * x^8 + 2.879118523083018e-14 * x^7 ...
+ % - 1.084418008735459e-11 * x^6 + 2.491796224784373e-09 * x^5 - 3.591462823163767e-07 * x^4 ...
+ % + 3.232810400304542e-05 * x^3 - 1.584729282376364e-03 * x^2 + 5.228413840446568e-02 * x ...
+ % - 6.547482267336220e-01;
+ %
+
+ %straggling contribution according to Bortfeld Eq.
+ %(18), in [mm]
+ fStragglingSigmaFromRange = @(R) 10 * stragglingFactor * (R/10)^((3-2/p)/2);
+
+ %energy spectrum contribution to peak width according
+ %to Bortfeld Eq. 19, in mm
+ fEnergySpreadFromWidth = @(sigmaSq,E) sqrt(sigmaSq ./ ((10*alpha)^2 * p^2 * E^(2*p-2)));
+
+ mcDataEnergy.MeanEnergy = fMeanEnergyFromRange(r80);
+
+ %calculate energy straggling using formulae deducted from paper
+ %"An analytical approximation of the Bragg curve for therapeutic
+ %proton beams" by T. Bortfeld et al. After inversion of
+ %the formula to obtain the two values z_50 where
+ %d(z_50) = 0.5*dMax, we obtain that the width is 6.14 *
+ %the total (energy + range) straggling sigma
+ totalSigmaSq = ((w50) / 6.14)^2;
+
+ %Obtain estimate straggling component
+ sigmaRangeStragglingOnlySq = fStragglingSigmaFromRange(r80).^2;
+
+ %Squared difference to obtain residual width from
+ %energy spectrum
+ if totalSigmaSq > sigmaRangeStragglingOnlySq
+ sigmaEnergyContributionSq = totalSigmaSq - sigmaRangeStragglingOnlySq;
+ energySpreadInMeV = fEnergySpreadFromWidth(sigmaEnergyContributionSq,mcDataEnergy.MeanEnergy);
+ else
+ energySpreadInMeV = 1e-8; %monoenergetic, but let's not write 0 to avoid division by zero in some codes
+ end
+
+ energySpreadRelative = energySpreadInMeV ./ mcDataEnergy.MeanEnergy * 100;
+
+ mcDataEnergy.EnergySpread = energySpreadRelative;
+ case 'carbon'
+ %Constants
+ alpha = 1.3413e-3;
+ p = 1.658;
+
+ % Fit to Range-Energy relationship
+ % Data from "Update to ESTAR, PSTAR, and ASTAR Databases" - ICRU Report 90, 2014
+ % Normalized energy before fit (MeV/u)! Only used ranges [10 350] mm for fit
+ % https://www.nist.gov/system/files/documents/2017/04/26/newstar.pdf
+ %fMeanEnergyFromRange = @(R) 11.39 * R^0.628 + 11.24;
+ fMeanEnergyFromRange = @(R) fEnergyFromRange(R,alpha,p);
+ mcDataEnergy.MeanEnergy = fMeanEnergyFromRange(r80);
+ % reading in a potential given energyspread could go here directly. How would you parse the energyspread
+ % into the function? Through a field in the machine?
+
+ %Straggling factor:
+ stragglingFactor = fStragglingFactor(alpha,p);
+ fStragglingSigmaFromRange = @(R) 10 * stragglingFactor * (R/10)^((3-2/p)/2);
+ fEnergySpreadFromWidth = @(sigmaSq,E) sqrt(sigmaSq ./ ((10*alpha)^2 * p^2 * E^(2*p-2)));
+ totalSigmaSq = ((w50) / 6.3028)^2;
+ sigmaRangeStragglingOnlySq = fStragglingSigmaFromRange(r80).^2;
+
+ if totalSigmaSq > sigmaRangeStragglingOnlySq
+ sigmaEnergyContributionSq = totalSigmaSq - sigmaRangeStragglingOnlySq;
+ energySpreadInMeV = fEnergySpreadFromWidth(sigmaEnergyContributionSq,mcDataEnergy.MeanEnergy);
+ else
+ energySpreadInMeV = 1e-8; %monoenergetic, but let's not write 0 to avoid division by zero in some codes
+ end
+
+ energySpreadRelative = energySpreadInMeV ./ mcDataEnergy.MeanEnergy * 100;
+
+ %mcDataEnergy.EnergySpread = obj.defaultRelativeEnergySpread;
+ mcDataEnergy.EnergySpread = energySpreadRelative;
+ case 'helium'
+ alpha = 2.528e-3;
+ p = 1.746;
+ %alpha = 2.567e-3;
+ %p = 1.74;
+
+ % Fit to Range-Energy relationship
+ % Data from "Update to ESTAR, PSTAR, and ASTAR Databases" - ICRU Report 90, 2014
+ % Normalized energy before fit (MeV/u)! Only used ranges [10 350] mm for fit
+ % https://www.nist.gov/system/files/documents/2017/04/26/newstar.pdf
+ %meanEnergyFromRange = @(x) 7.57* x.^0.5848 + 3.063;
+
+ fMeanEnergyFromRange = @(R) fEnergyFromRange(R,alpha,p);
+ mcDataEnergy.MeanEnergy = fMeanEnergyFromRange(r80);
+
+
+ stragglingFactor = fStragglingFactor(alpha,p);
+ fStragglingSigmaFromRange = @(R) 10 * stragglingFactor * (R/10)^((3-2/p)/2);
+ fEnergySpreadFromWidth = @(sigmaSq,E) sqrt(sigmaSq ./ ((10*alpha)^2 * p^2 * E^(2*p-2)));
+ totalSigmaSq = ((w50) / 7.4617)^2;
+ sigmaRangeStragglingOnlySq = fStragglingSigmaFromRange(r80).^2;
+
+ if totalSigmaSq > sigmaRangeStragglingOnlySq
+ sigmaEnergyContributionSq = totalSigmaSq - sigmaRangeStragglingOnlySq;
+ energySpreadInMeV = fEnergySpreadFromWidth(sigmaEnergyContributionSq,mcDataEnergy.MeanEnergy);
+ else
+ energySpreadInMeV = 1e-8; %monoenergetic, but let's not write 0 to avoid division by zero in some codes
+ end
+
+ energySpreadRelative = energySpreadInMeV ./ mcDataEnergy.MeanEnergy * 100;
+
+ mcDataEnergy.EnergySpread = energySpreadRelative;
+
+ %mcDataEnergy.EnergySpread = obj.defaultRelativeEnergySpread;
+ otherwise
+ error('not implemented')
+ end
+ end
+
+
+
+
+ function mcDataOptics = fitBeamOpticsForEnergy(obj,energyIx, focusIndex)
+ %function to calculate beam optics used by mcSquare for given
+ %energy
+
+ i = energyIx;
+
+ %calculate geometric distances and extrapolate spot size at nozzle
+ SAD = obj.machine.meta.SAD;
+ z = -(obj.machine.data(i).initFocus.dist(focusIndex,:) - SAD);
+ sigma = obj.machine.data(i).initFocus.sigma(focusIndex,:);
+
+ %Double Gaussian data might have a non-zero wide Gaussian,
+ %adding width to the beam. We do a maximum correction here,
+ %which compromises the fwhm, but seems to work better in
+ %estimating the optics
+ if obj.fitCorrectDoubleGaussian && isfield(obj.machine.data(i),'sigma1')
+ sigmaSq_Narr = sigma.^2 + obj.machine.data(i).sigma1(1).^2;
+ sigmaSq_Bro = sigma.^2 + obj.machine.data(i).sigma2(1).^2;
+ dgWeight = obj.machine.data(i).weight(1);
+
+ %Maximum of double gaussian
+ maxL = (1-dgWeight) ./ (2*pi*sigmaSq_Narr) + dgWeight ./ (2*pi*sigmaSq_Bro );
+
+ %Find the sigma that corresponds to the maximum
+ sigma = sqrt(1 ./ (2*pi*maxL));
+ end
+
+ %correct for in-air scattering with polynomial or interpolation
+ if obj.fitWithSpotSizeAirCorrection
+ sigma = arrayfun(@(d,sigma) obj.spotSizeAirCorrection(obj.machine.meta.radiationMode,obj.machine.data(i).energy,d,sigma),-z+obj.machine.meta.BAMStoIsoDist,sigma);
+ end
+
+ %square and interpolate at isocenter
+ sigmaSq = sigma.^2;
+ sigmaSqIso = sqrt(interp1(z,sigmaSq,0));
+
+ %fit Courant-Synder equation to data using ipopt, formulae
+ %given in mcSquare documentation
+
+ %fit function
+ qRes = @(rho, sigmaT) (sigmaSq - (sigmaSqIso^2 - 2*sigmaSqIso*rho*sigmaT.*z + sigmaT^2.*z.^2));
+
+ % fitting for either matlab or octave
+ if ~obj.matRad_cfg.isOctave
+ funcs.objective = @(x) sum(qRes(x(1), x(2)).^2);
+ funcs.gradient = @(x) [ 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaSqIso * x(2) * z));
+ 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaSqIso * x(1) * z - 2 * x(2) * z.^2))];
+
+ options.lb = [-0.99, -Inf];
+ options.ub = [ 0.99, Inf];
+
+ options.ipopt.hessian_approximation = 'limited-memory';
+ options.ipopt.limited_memory_update_type = 'bfgs';
+
+ %Set Default Options
+ if obj.matRad_cfg.logLevel <= 1
+ lvl = 0;
+ else
+ lvl = 1;
+ end
+ options.ipopt.print_level = lvl;
+
+ start = [0.9; 0.1];
+ [result, ~] = ipopt (start, funcs, options);
+ rho = result(1);
+ sigmaT = result(2);
+
+ else
+ phi{1} = @(x) sum(qRes(x(1), x(2)).^2);
+ phi{2} = @(x) [ 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaSqIso * x(2) * z));
+ 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaSqIso * x(1) * z - 2 * x(2) * z.^2))];
+
+ lb = [-0.99, -Inf];
+ ub = [ 0.99, Inf];
+
+ start = [0.9; 0.1];
+ [result, ~] = sqp (start, phi, [], [], lb, ub);
+ rho = result(1);
+ sigmaT = result(2);
+ end
+
+ %calculate divergence, spotsize and correlation at nozzle
+ DivergenceAtNozzle = sigmaT;
+ SpotsizeAtNozzle = sqrt(sigmaSqIso^2 - 2 * rho * sigmaSqIso * sigmaT * obj.nozzleToIso + sigmaT^2 * obj.nozzleToIso^2);
+ CorrelationAtNozzle = (rho * sigmaSqIso - sigmaT * obj.nozzleToIso) / SpotsizeAtNozzle;
+
+
+ %save calcuated beam optics data in mcData
+ mcDataOptics.ProtonsMU = 1e6;
+
+ mcDataOptics.Weight1 = 1;
+ mcDataOptics.SpotSize1x = SpotsizeAtNozzle;
+ mcDataOptics.Divergence1x = DivergenceAtNozzle;
+ mcDataOptics.Correlation1x = CorrelationAtNozzle;
+ mcDataOptics.SpotSize1y = SpotsizeAtNozzle;
+ mcDataOptics.Divergence1y = DivergenceAtNozzle;
+ mcDataOptics.Correlation1y = CorrelationAtNozzle;
+
+ visBool = false;
+ if visBool
+ figure, plot(z,sigmaSq,'x');
+ zNew = linspace(z(1),z(end),100);
+ y = sigmaSqIso^2 - 2*rho*sigmaSqIso*sigmaT * zNew + sigmaT^2 * zNew.^2;
+ hold on; plot(zNew,y);
+ end
+
+
+ mcDataOptics.Weight2 = 0;
+ mcDataOptics.SpotSize2x = 0;
+ mcDataOptics.Divergence2x = 0;
+ mcDataOptics.Correlation2x = 0;
+ mcDataOptics.SpotSize2y = 0;
+ mcDataOptics.Divergence2y = 0;
+ mcDataOptics.Correlation2y = 0;
+ mcDataOptics.FWHMatIso = 2.355 * sigmaSqIso;
+ end
+
+
+
+ function obj = saveMatradMachine(obj,name)
+ %save previously calculated monteCarloData in new baseData file
+ %with given name
+
+ % [~ ,energyIndex, ~] = intersect([obj.machine.data(:).energy], [obj.monteCarloData(:).NominalEnergy]);
+
+ machineName = [obj.machine.meta.radiationMode, '_', name];
+
+ count = 1;
+ for i = 1:length(obj.energyIndex)
+
+ ixE = obj.energyIndex(i);
+
+ numFoci = numel(obj.monteCarloData(:,count).SpotSize1x);
+
+ for f = 1:numFoci
+ if obj.monteCarloData(:,count).Weight2 ~= 0
+ obj.machine.data(ixE).initFocus.emittance(f).type = 'bigaussian';
+ obj.machine.data(ixE).initFocus.emittance(f).sigmaX = [obj.monteCarloData(f,count).SpotSize1x obj.monteCarloData(:,count).SpotSize2x(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).sigmaY = [obj.monteCarloData(f,count).SpotSize1y obj.monteCarloData(:,count).SpotSize2y(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).divX = [obj.monteCarloData(f,count).Divergence1x obj.monteCarloData(:,count).Divergence2x(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).divY = [obj.monteCarloData(f,count).Divergence1y obj.monteCarloData(:,count).Divergence2y(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).corrX = [obj.monteCarloData(f,count).Correlation1x obj.monteCarloData(:,count).Correlation2x(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).corrY = [obj.monteCarloData(f,count).Correlation1y obj.monteCarloData(:,count).Correlation2y(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).weight = [obj.monteCarloData(f,count).Weight2]; %Weight one will not be stored explicitly due to normalization
+ else
+ obj.machine.data(ixE).initFocus.emittance(f).type = 'bigaussian';
+ obj.machine.data(ixE).initFocus.emittance(f).sigmaX = [obj.monteCarloData(:,count).SpotSize1x(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).sigmaY = [obj.monteCarloData(:,count).SpotSize1y(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).divX = [obj.monteCarloData(:,count).Divergence1x(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).divY = [obj.monteCarloData(:,count).Divergence1y(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).corrX = [obj.monteCarloData(:,count).Correlation1x(f)];
+ obj.machine.data(ixE).initFocus.emittance(f).corrY = [obj.monteCarloData(:,count).Correlation1y(f)];
+ end
+ end
+
+ %At the moment coded to only take the first energy because
+ %focus settings do not apply to the energy spectrum
+ obj.machine.data(ixE).energySpectrum.type = 'gaussian';
+ obj.machine.data(ixE).energySpectrum.mean = [obj.monteCarloData(:,count).MeanEnergy];
+ obj.machine.data(ixE).energySpectrum.sigma = [obj.monteCarloData(:,count).EnergySpread];
+
+
+ count = count + 1;
+ end
+ machine = obj.machine;
+ machineFilePath = fullfile(obj.matRad_cfg.primaryUserFolder,'machines',[machineName '.mat']);
+
+ save('-v7',machineFilePath,'machine');
+ obj.matRad_cfg.dispInfo('Saved Emittance to temporary matRad base data in %s\n',machineFilePath);
+ end
+ end
+
+ methods (Access = protected)
+ function obj = getRangeShiftersFromStf(obj,stf)
+ allRays = [stf.ray];
+ raShis = [allRays.rangeShifter];
+
+ [~,ix] = unique(cell2mat(squeeze(struct2cell(raShis))'),'rows');
+
+ raShis = raShis(ix);
+
+ ix = [raShis.ID] == 0;
+
+ obj.rangeShifters = raShis(~ix);
+ end
+ end
+
+ methods (Static)
+ function sigmaAirCorrected = spotSizeAirCorrection(radiationMode,E,d,sigma,method)
+ %performs a rudimentary correction for additional scattering in
+ %air not considered by the courant snyder equation
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 5
+ method = 'interp_linear';
+ end
+
+ switch radiationMode
+ case 'protons'
+ %Provide Look-up Table and fit for protons
+ sigmaLUT = [0 0.4581 2.7777 7.0684 12.6747; ...
+ 0 0.1105 0.7232 2.1119 4.2218; ...
+ 0 0.0754 0.5049 1.4151 2.8604; ...
+ 0 0.0638 0.3926 1.1196 2.2981; ...
+ 0 0.0466 0.3279 0.9440 1.9305; ...
+ 0 0.0414 0.2825 0.8294 1.7142; ...
+ 0 0.0381 0.2474 0.7336 1.5192; ...
+ 0 0.0335 0.2214 0.6696 1.3795; ...
+ 0 0.0287 0.2030 0.6018 1.2594; ...
+ 0 0.0280 0.1925 0.5674 1.1865; ...
+ 0 0.0257 0.1801 0.5314 1.0970; ...
+ 0 0.0244 0.1670 0.4966 1.0342];
+ energies = [31.7290 69.4389 95.2605 116.5270 135.1460 151.9670 167.4620 181.9230 195.5480 208.4780 220.8170 232.6480]';
+ depths = [0 500 1000 1500 2000];
+ polyFit = @(E,d) 0.001681*d - 0.0001178*E*d + 6.094e-6*d^2 + 1.764e-6*E^2*d - 1.016e-7*E*d^2 - 9.803e-09*E^3*d + 6.096e-10*E^2*d^2 + 1.835e-11*E^4*d - 1.209e-12*E^3*d^2;
+ otherwise
+ %No air correction because we don't have data yet
+ sigmaLUT = [0 0; 0 0];
+ energies = [0; realmax];
+ depths = [0; realmax];
+
+ polyFit = @(E,d) 0;
+ end
+
+ %make sure to not violate ranges!
+ %this is a little hardcoded, but helps us handle strange
+ %distances in the initFocus field
+ if d < min(depths)
+ d = min(depths);
+ matRad_cfg.dispWarning('Spot Size Air Correction problem, negative distance found!',method);
+ end
+
+ if d > max(depths)
+ d = max(depths);
+ matRad_cfg.dispWarning('Spot Size Air Correction problem, distance too large!',method);
+ end
+
+ if E > max(energies)
+ E = max(energies);
+ matRad_cfg.dispWarning('Spot Size Air Correction problem, energy too large!',method);
+ end
+
+ if E < min(energies)
+ E = min(energies);
+ matRad_cfg.dispWarning('Spot Size Air Correction problem, energy too small!',method);
+ end
+
+
+ switch method
+ case 'interp_linear'
+ sigmaAir = interp2(energies,depths,sigmaLUT',E,d,'linear');
+ case 'fit'
+ sigmaAir = polyFit(E,d);
+ otherwise
+ matRad_cfg.dispWarning('Air Correction Method ''%s'' not known, skipping!',method);
+ sigmaAir = 0;
+ end
+
+ if sigmaAir >= sigma
+ sigmaAirCorrected = sigma;
+ matRad_cfg.dispWarning('Spot Size Air Correction failed, too large!',method);
+ else
+ sigmaAirCorrected = sqrt(sigma.^2 - sigmaAir.^2);
+ end
+
+ end
+ end
+end
+
diff --git a/matRad/basedata/matRad_getAvailableMachines.m b/matRad/basedata/matRad_getAvailableMachines.m
new file mode 100644
index 000000000..245d330a6
--- /dev/null
+++ b/matRad/basedata/matRad_getAvailableMachines.m
@@ -0,0 +1,58 @@
+function machineList = matRad_getAvailableMachines(modalities)
+% matRad_loadMachine load a machine base data file from pln struct.
+% Looks for the machine fileuct. in the basedata folder and in the provided user folders.
+%
+% call
+% machine = matRad_loadMachine(pln)
+%
+% input
+% pln: matRad plan meta information struct
+%
+% output
+% machine: matRad machine struct
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if nargin < 1
+ modalities = {'photons','protons','helium','carbon'};
+end
+
+if ischar(modalities)
+ modalities = {modalities};
+end
+
+matRad_cfg = MatRad_Config.instance();
+
+machineList = containers.Map();
+
+for i = 1:length(modalities)
+ pattern = [modalities{i} '_*.mat'];
+ machineFiles = dir([matRad_cfg.matRadSrcRoot filesep 'basedata' filesep pattern]);
+ for f = 1:length(matRad_cfg.userfolders)
+ machineFiles = [machineFiles; dir([matRad_cfg.userfolders{f} filesep 'machines' filesep pattern])];
+ end
+
+ if ~isempty(machineFiles)
+ machineNames = cell(1,length(machineFiles));
+ for j = 1:length(machineFiles)
+ machineNames{j} = machineFiles(j).name(numel(modalities{i})+2:end-4);
+ end
+ machineList(modalities{i}) = machineNames;
+ else
+ machineList(modalities{i}) = {};
+ end
+end
\ No newline at end of file
diff --git a/matRad/basedata/matRad_loadMachine.m b/matRad/basedata/matRad_loadMachine.m
new file mode 100644
index 000000000..be7078f07
--- /dev/null
+++ b/matRad/basedata/matRad_loadMachine.m
@@ -0,0 +1,62 @@
+function machine = matRad_loadMachine(pln)
+% matRad_loadMachine load a machine base data file from pln struct.
+% Looks for the machine file from pln in the basedata folder and in the provided user folders.
+%
+% call
+% machine = matRad_loadMachine(pln)
+%
+% input
+% pln: matRad plan meta information struct
+%
+% output
+% machine: matRad machine struct
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+if isfield(pln, 'radiationMode')
+ if isfield(pln, 'machine')
+ fileName = [pln.radiationMode '_' pln.machine];
+ else
+ fileName = [pln.radiationMode '_Generic'];
+ matRad_cfg.dispWarning('No machine name given, loading generic machine');
+ end
+else
+ matRad_cfg.dispError('No radiation mode given in pln');
+end
+
+userfolders = cellfun(@(uf) strcat(uf,filesep,'machines',filesep),matRad_cfg.userfolders,'UniformOutput',false);
+
+folders = [{[matRad_cfg.matRadSrcRoot filesep 'basedata' filesep]} userfolders(:)'];
+
+foundData = cellfun(@(folder) exist(fullfile(folder, [fileName '.mat']), 'file'), folders);
+foundIx = find(foundData, 1, 'first');
+
+if isempty(foundIx)
+ matRad_cfg.dispError('Could not find the following machine file: %s',fileName);
+end
+
+filepath = fullfile(folders{foundIx}, [fileName '.mat']);
+
+try
+ m = load(filepath, 'machine');
+ machine = m.machine; % strip first layer of loaded struct for convenience
+catch
+ matRad_cfg.dispError('Could not load the following machine file: %s',fileName);
+end
+end
+
diff --git a/basedata/photons_Generic.mat b/matRad/basedata/photons_Generic.mat
similarity index 99%
rename from basedata/photons_Generic.mat
rename to matRad/basedata/photons_Generic.mat
index 24820d93f..7f0b7ee35 100644
Binary files a/basedata/photons_Generic.mat and b/matRad/basedata/photons_Generic.mat differ
diff --git a/basedata/protons_Generic.mat b/matRad/basedata/protons_Generic.mat
similarity index 100%
rename from basedata/protons_Generic.mat
rename to matRad/basedata/protons_Generic.mat
diff --git a/basedata/protons_generic_MCsquare.mat b/matRad/basedata/protons_generic_MCsquare.mat
similarity index 99%
rename from basedata/protons_generic_MCsquare.mat
rename to matRad/basedata/protons_generic_MCsquare.mat
index 1a9c3e846..c5db06310 100644
Binary files a/basedata/protons_generic_MCsquare.mat and b/matRad/basedata/protons_generic_MCsquare.mat differ
diff --git a/matRad/basedata/protons_generic_TOPAS.mat b/matRad/basedata/protons_generic_TOPAS.mat
new file mode 100644
index 000000000..b5c45124a
Binary files /dev/null and b/matRad/basedata/protons_generic_TOPAS.mat differ
diff --git a/matRad/bioModels/matRad_BiologicalModel.m b/matRad/bioModels/matRad_BiologicalModel.m
new file mode 100644
index 000000000..9ae7b4907
--- /dev/null
+++ b/matRad/bioModels/matRad_BiologicalModel.m
@@ -0,0 +1,635 @@
+classdef matRad_BiologicalModel
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % matRad_BiologicalModel
+ % This class creates all required biological model parameters according to
+ % a given radiation modatlity and a given bio model identifier string.
+ %
+ % constructor call
+ % pln.bioParam = matRad_BiologicalModel(sRadiationMode,sQuantityOpt, sModel)
+ %
+ % e.g. pln.bioParam = matRad_BiologicalModel('protons','constRBE','RBExD')
+ %
+ % input
+ % sRadiationMode: radiation modality 'photons' 'protons' 'carbon' 'brachy'
+ %
+ %
+ % sQuntityOpt: string to denote the quantity used for
+ % optimization 'physicalDose', 'RBExD',
+ % 'effect','BED'
+ % sModel: string to denote which biological model is used
+ % 'none': for photons, protons, carbon 'constRBE': constant RBE for photons and protons
+ % 'MCN': McNamara-variable RBE model for protons 'WED': Wedenberg-variable RBE model for protons
+ % 'LEM': Local Effect Model for carbon ions
+ %
+ % output
+ % bioParam: matRad's bioParam structure containing information
+ % about the choosen biological model
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2017 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ % public properties which can be changed outside this class
+ properties
+
+ end
+
+ % public properties which can only be changed inside this class
+ properties(SetAccess = private)
+
+ radiationMode; % radiation modality
+ identifier; % upper case short notation of the current model in combination with the quantity used for optimization (e.g. LEM_RBExD) probably not needed
+ bioOpt; % boolean indicating biological optimization (=true) or physical optimization (=false)
+ model; % upper case short notation of the current model (e.g. LEM)
+ quantityOpt; % quantity used for optimizaiton
+ quantityVis; % quantity used per default for visualization
+ description; % short description of the biological model
+ RBE; % constant RBE
+
+ % beamMixingModel = 'ZaiderRossi';
+ end
+
+ % constant public properties which are visible outside of this class
+ properties(Constant = true)
+ availableModels = {'none','constRBE','MCN','WED','LEM','HEL'}; % cell array determines available models - if cell is deleted then the corersponding model can not be generated
+
+ availableRadiationModalities = {'photons','protons','helium','carbon','brachy'};
+ availableQuantitiesForOpt = {'physicalDose','effect','RBExD','BED'};
+
+ AvailableAlphaXBetaX = {[0.036 0.024], 'prostate';
+ [0.089 0.287], 'rectum and normal tissue';
+ [0.55 0.05], 'head and neck MCN';
+ [0.0499 0.0238], 'brain tissue';
+ [0.1 0.05], 'default values';
+ [0.1 0.005], 'default values'; %
+ [0.0081 0.0033], 'LEM IV AB 2.45'; %
+ [0.0030 0.0015], 'LEM IV AB 2'}; %
+
+ end
+
+ % constant private properties which are only visible within this class
+ properties(Constant = true, Access = private)
+
+ constRBE_protons = 1.1;
+ constRBE_photons = 1;
+
+
+ %McNamara variable RBE model for protons
+ p0_MCN = 0.999064; % according to https://www.ncbi.nlm.nih.gov/pubmed/26459756
+ p1_MCN = 0.35605;
+ p2_MCN = 1.1012;
+ p3_MCN = -0.0038703;
+
+ %Carabe Fernandez variable RBE model for protons
+ p0_CAR = 0.843; % http://www.tandfonline.com/doi/abs/10.1080/09553000601087176?journalCode=irab20
+ p1_CAR = 0.154;
+ p2_CAR = 2.686;
+ p3_CAR = 1.09;
+ p4_CAR = 0.006;
+ p5_CAR = 2.686;
+
+ %Wedenberg variable RBE model for protons
+ p0_WED = 1; % https://www.ncbi.nlm.nih.gov/pubmed/22909391
+ p1_WED = 0.434;
+ p2_WED = 1;
+
+ % Linear Scaling model for protons
+ p_lamda_1_1 = 0.008; %0.008; % according to Malte Frese https://www.ncbi.nlm.nih.gov/pubmed/20382482 (FITTED for head and neck patients !)
+ p_corrFacEntranceRBE = 0.5; %[kev/mum]
+ p_upperLETThreshold = 30; %[kev/mum]
+ P_lowerLETThreshold = 0.3; %[kev/mum]
+
+ % data driven parametrization of helium ions https://iopscience.iop.org/article/10.1088/0031-9155/61/2/888
+ p0_HEL = 1.36938e-1; % values refer to the quadratic exponential fit f_QE
+ p1_HEL = 9.73154e-3;
+ p2_HEL = 1.51998e-2;
+
+ end
+
+ %% methods
+
+ % private methods go here
+ methods (Access = private)
+
+ function this = setBioModel(this)
+
+ matRad_cfg = MatRad_Config.instance();
+
+ this.RBE = NaN;
+ % check for valid combinations
+ boolCHECK = false;
+
+ while ~boolCHECK
+
+ switch this.radiationMode
+
+ case {'photons'}
+
+ setDefaultValues = false;
+ switch this.quantityOpt
+
+ case {'physicalDose'}
+ if strcmp(this.model,'none')
+ boolCHECK = true;
+ this.bioOpt = false;
+ this.quantityVis = 'physicalDose';
+ else
+ setDefaultValues = true;
+ end
+
+ case {'RBExD'}
+ if sum(strcmp(this.model,{'constRBE', 'none'})) == 1
+ this.RBE = this.constRBE_photons;
+ boolCHECK = true;
+ this.bioOpt = false;
+ this.quantityVis = 'RBExD';
+ else
+ setDefaultValues = true;
+ end
+
+ case {'effect','BED'}
+ if strcmp( this.model,'none')
+ boolCHECK = true;
+ this.bioOpt = true;
+ this.quantityVis = 'physicalDose';
+ else
+ setDefaultValues = true;
+ end
+
+ otherwise
+ matRad_cfg.dispWarning('matRad: Invalid biological optimization quantity: %s; using physical dose instead.',this.quantityOpt);
+ this.quantityOpt = 'physicalDose';
+ end
+
+ if setDefaultValues
+ matRad_cfg.dispWarning('matRad: Invalid biological model: %s; using "none" instead.',this.model);
+ this.model = 'none';
+ this.quantityVis = 'physicalDose';
+ this.quantityOpt = 'physicalDose';
+ end
+
+ case {'protons'}
+
+ switch this.quantityOpt
+
+ case {'physicalDose'}
+ if strcmp( this.model, 'none')
+ boolCHECK = true;
+ this.bioOpt = false;
+ this.quantityVis = 'physicalDose';
+ else
+ matRad_cfg.dispWarning('matRad: Invalid biological model: %s; using "none" instead.',this.model);
+ this.model = 'none';
+ end
+
+ case {'RBExD'}
+ if sum(strcmp( this.model, {'constRBE','MCN','WED'})) == 1
+ boolCHECK = true;
+ this.bioOpt = true;
+ this.quantityVis = 'RBExD';
+ if sum(strcmp( this.model, {'constRBE'})) == 1
+ this.RBE = this.constRBE_protons;
+ this.bioOpt = false;
+ end
+ else
+ matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using constant RBE instead.']);
+ this.model = 'constRBE';
+ end
+
+ case {'effect','BED'}
+ if sum(strcmp( this.model, {'MCN','WED'})) == 1
+ boolCHECK = true;
+ this.bioOpt = true;
+ this.quantityVis = 'RBExD';
+ else
+ matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using MCN Model instead.']);
+ this.model = 'MCN';
+ end
+
+ otherwise
+ matRad_cfg.dispWarning(['matRad: Invalid biological optimization quantity: ' this.quantityOpt '; using "none" instead.']);
+ this.quantityOpt = 'physicalDose';
+ end
+
+ case {'helium'}
+ switch this.quantityOpt
+
+ case {'physicalDose'}
+ if strcmp( this.model, 'none')
+ boolCHECK = true;
+ this.bioOpt = false;
+ this.quantityVis = 'physicalDose';
+ else
+ matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using "none" instead.']);
+ this.model = 'none';
+ end
+
+ case {'effect','RBExD','BED'}
+ if sum(strcmp(this.model,{'LEM','HEL'})) > 0
+ boolCHECK = true;
+ this.bioOpt = true;
+ this.quantityVis = 'RBExD';
+ else
+ matRad_cfg.dispWarning(['matRad: Invalid biological Model: ' this.model '; using "HEL" instead.']);
+ this.model = 'HEL';
+ end
+
+ otherwise
+ matRad_cfg.dispWarning(['matRad: Invalid biological optimization quantity: ' this.quantityOpt '; using "none" instead.']);
+ this.quantityOpt = 'physicalDose';
+ end
+
+ case {'carbon'}
+ switch this.quantityOpt
+
+ case {'physicalDose'}
+ if strcmp( this.model, 'none')
+ boolCHECK = true;
+ this.bioOpt = false;
+ this.quantityVis = 'physicalDose';
+ else
+ matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using "none" instead.']);
+ this.model = 'none';
+ end
+
+ case {'effect','RBExD','BED'}
+ if strcmp(this.model,'LEM')
+ boolCHECK = true;
+ this.bioOpt = true;
+ this.quantityVis = 'RBExD';
+ else
+ matRad_cfg.dispWarning(['matRad: Invalid biological Model: ' this.model '; using Local Effect Model instead.']);
+ this.model = 'LEM';
+ end
+
+ otherwise
+ matRad_cfg.dispWarning(['matRad: Invalid biological optimization quantity: ' this.quantityOpt '; using "none" instead.']);
+ this.quantityOpt = 'physicalDose';
+ end
+
+ case {'brachy'}
+
+ setDefaultValues = false;
+ switch this.quantityOpt
+
+ case {'physicalDose'}
+ if strcmp(this.model,'none')
+ boolCHECK = true;
+ this.bioOpt = false;
+ this.quantityVis = 'physicalDose';
+ else
+ setDefaultValues = true;
+ end
+
+ case {'RBExD'}
+ if sum(strcmp(this.model,{'constRBE', 'none'})) == 1
+ this.RBE = this.constRBE_photons;
+ boolCHECK = true;
+ this.bioOpt = false;
+ this.quantityVis = 'RBExD';
+ else
+ setDefaultValues = true;
+ end
+
+ case {'effect'}
+ if strcmp( this.model,'none')
+ boolCHECK = true;
+ this.bioOpt = true;
+ this.quantityVis = 'RBExD';
+ else
+ setDefaultValues = true;
+ end
+
+ otherwise
+ matRad_cfg.dispWarning('matRad: Invalid biological optimization quantity: %s; using physical dose instead.',this.quantityOpt);
+ this.quantityOpt = 'physicalDose';
+ end
+
+ otherwise
+ matRad_cfg.dispWarning(['matRad: Invalid biological radiation mode: ' this.radiationMode '; using photons instead.']);
+ this.radiationMode = 'photons';
+ end
+ end
+
+
+ % check quantity for optimization
+ if this.bioOpt
+ if sum(strcmp(this.quantityOpt,this.availableQuantitiesForOpt)) == 0
+ matRad_cfg.dispError(['matRad: Invalid quantity for optimization: ' this.quantityOpt ]);
+ end
+ else
+ if sum(strcmp(this.quantityOpt,{'physicalDose','RBExD'})) == 0
+ matRad_cfg.dispError(['matRad: Invalid quantity for optimization: ' this.quantityOpt ]);
+ end
+ end
+
+
+ % check quantity for visualization
+ if this.bioOpt
+ if sum(strcmp(this.quantityVis,this.availableQuantitiesForOpt)) == 0
+ matRad_cfg.dispError(['matRad: Invalid quantity for visualization: ' this.quantityVis ]);
+ end
+ else
+ if sum(strcmp(this.quantityVis,{'physicalDose','RBExD'})) == 0
+ matRad_cfg.dispError(['matRad: Invalid quantity for visualization: ' this.quantityVis ]);
+ end
+ end
+
+
+
+ end % end of this = setBioModel(this)
+ end % end off access methods private
+
+
+
+ % public methods go here
+
+ methods
+
+ % default constructor
+ function this = matRad_BiologicalModel(sRadiationMode,sQuantityOpt, sModel)
+ this.radiationMode = sRadiationMode;
+ this.quantityOpt = sQuantityOpt; % setter checks for valid strings but not for valid combinations (e.g. photons_LEM
+ this.model = sModel;
+ this = setBioModel(this);
+ end % end constructor
+
+
+ % setter functions
+ function this = set.radiationMode(this,value)
+ if ischar(value) && sum(strcmp(value,this.availableRadiationModalities)) == 1
+ this.radiationMode = value;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('matRad: Cannot set radiation modality')
+ end
+ end
+
+
+ function this = set.bioOpt(this,value)
+ if islogical(value)
+ this.bioOpt = value;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('matRad: Cannot set bioOpt option')
+ end
+ end
+
+
+ function this = set.model(this,value)
+ if ischar(value)
+ this.model = value;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('matRad: Cannot set model option')
+ end
+ end
+
+ function this = set.quantityOpt(this,value)
+ if ischar(value)
+ this.quantityOpt = value;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('matRad: Cannot set quantityOpt option')
+ end
+ end
+
+ function this = set.quantityVis(this,value)
+ if ischar(value)
+ this.quantityVis = value;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('matRad: Cannot set quantityVis option')
+ end
+ end
+
+ function this = set.description(this,value)
+ if ischar(value)
+ this.description = value;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('matRad: Cannot set description option')
+ end
+ end
+
+
+
+
+ function [bixelAlpha,bixelBeta] = calcLQParameter(this,vRadDepths,baseDataEntry,mTissueClass,vAlpha_x,vBeta_x,vABratio,LETin)
+ matRad_cfg = MatRad_Config.instance();
+
+ bixelAlpha = NaN*ones(numel(vRadDepths),1);
+ bixelBeta = NaN*ones(numel(vRadDepths),1);
+
+ % range shift
+ depths = baseDataEntry.depths + baseDataEntry.offset;
+
+ switch [this.radiationMode '_' this.model]
+
+ case {'protons_constRBE'}
+
+ case {'protons_LSM'}
+
+ bixelLET = matRad_interp1(depths,baseDataEntry.LET,vRadDepths);
+ bixelLET(isnan(bixelLET)) = 0;
+
+ ix = this.p_lowerLETThreshold < bixelLET < this.p_upperLETThreshold;
+
+ alpha_0 = vAlpha_x - (this.p_lamda_1_1 * this.p_corrFacEntranceRBE);
+ bixelAlpha(ixLSM) = alpha_0(ix) + this.p_lamda_1_1 * bixelLET;
+
+ if sum(ix) < length(bixelLET)
+ bixelAlpha(bixelLET > pln.bioParam.lowerLETThreshold) = alpha_0(bixelLET > this.p_upperLETThreshold) + this.p_lamda_1_1 * this.p_upperLETThreshold;
+ bixelAlpha(bixelLET < pln.bioParam.lowerLETThreshold) = alpha_0(bixelLET < this.p_lowerLETThreshold) + this.p_lamda_1_1 * this.p_lowerLETThreshold;
+ end
+
+ bixelBeta = vBeta_x;
+
+ % TODO: assign normal tissue an RBE of 1.1
+
+ case {'protons_MCN'}
+
+ if exist('LETin','var') && ~isempty(LETin)
+ bixelLET = LETin;
+ else
+ bixelLET = matRad_interp1(depths,baseDataEntry.LET,vRadDepths);
+ end
+ bixelLET(isnan(bixelLET)) = 0;
+
+ RBEmax = this.p0_MCN + ((this.p1_MCN * bixelLET )./ vABratio);
+ RBEmin = this.p2_MCN + (this.p3_MCN * sqrt(vABratio) .* bixelLET);
+ bixelAlpha = RBEmax .* vAlpha_x;
+ bixelBeta = RBEmin.^2 .* vBeta_x;
+
+ case {'protons_WED'}
+
+ if exist('LETin','var') && ~isempty(LETin)
+ bixelLET = LETin;
+ else
+ bixelLET = matRad_interp1(depths,baseDataEntry.LET,vRadDepths);
+ end
+ bixelLET(isnan(bixelLET)) = 0;
+
+ RBEmax = this.p0_WED + ((this.p1_WED * bixelLET )./ vABratio);
+ RBEmin = this.p2_WED;
+ bixelAlpha = RBEmax .* vAlpha_x;
+ bixelBeta = RBEmin.^2 .* vBeta_x;
+
+ case {'helium_HEL'}
+
+ % data-driven RBE parametrization of helium ions
+ % https://iopscience.iop.org/article/10.1088/0031-9155/61/2/888
+
+ bixelLET = matRad_interp1(depths,baseDataEntry.LET,vRadDepths);
+ bixelLET(isnan(bixelLET)) = 0;
+
+ % quadratic fit
+ %f_Q = 8.53959e-4 .* bixelLET.^2;
+ %RBEmax_Q = 1 + 2.145e-1 + vABratio.^-1 .* f_Q;
+ % linear quadratic fit
+ %f_LQ = 2.91783e-1*bixelLET - 9.525e-4*bixelLET.^2;
+ %RBEmax_LQ = 1 + ((1.42057e-1 + (vABratio.^-1)) .* f_LQ);
+ % linear exponential fit
+ %f_LE = (2.965e-1 * bixelLET) .* exp(-4.90821e-3 * bixelLET);
+ %RBEmax_LE = 1 + ((1.5384e-1 + (vABratio.^-1)) .* f_LE);
+
+ % quadratic exponential fit
+ f_QE = (this.p1_HEL * bixelLET.^2) .* exp(-this.p2_HEL * bixelLET);
+ RBEmax_QE = 1 + ((this.p0_HEL + (vABratio.^-1)) .* f_QE);
+
+ % the linear quadratic fit yielded the best fitting result
+ RBEmax = RBEmax_QE;
+
+ RBEmin = 1; % no gain in using fitted parameters over a constant value of 1
+
+ bixelAlpha = RBEmax .* vAlpha_x;
+ bixelBeta = RBEmin.^2 .* vBeta_x;
+
+ case {'carbon_LEM','helium_LEM'}
+
+ numOfTissueClass = size(baseDataEntry(1).alpha,2);
+
+ for i = 1:numOfTissueClass
+ bixelAlpha(mTissueClass==i) = matRad_interp1(depths,baseDataEntry.alpha(:,i),vRadDepths(mTissueClass==i));
+ bixelBeta(mTissueClass==i) = matRad_interp1(depths,baseDataEntry.beta(:,i), vRadDepths(mTissueClass==i));
+ end
+
+ otherwise
+
+ end
+
+ end
+
+ function [bixelAlpha,bixelBeta] = calcLQParameterForKernel(this,bixel,kernels)
+ matRad_cfg = MatRad_Config.instance();
+
+ bixelAlpha = NaN*ones(numel(bixel.radDepths),1);
+ bixelBeta = NaN*ones(numel(bixel.radDepths),1);
+
+ vAlpha_x = bixel.vAlphaX;
+ vBeta_x = bixel.vBetaX;
+ vABratio = vAlpha_x ./ vBeta_x;
+
+ bixelLET = kernels.LET;
+
+ switch [this.radiationMode '_' this.model]
+
+ case {'protons_constRBE'}
+
+ case {'protons_LSM'}
+
+
+ ix = this.p_lowerLETThreshold < bixelLET < this.p_upperLETThreshold;
+
+ alpha_0 = vAlpha_x - (this.p_lamda_1_1 * this.p_corrFacEntranceRBE);
+ bixelAlpha(ixLSM) = alpha_0(ix) + this.p_lamda_1_1 * bixelLET;
+
+ if sum(ix) < length(bixelLET)
+ bixelAlpha(bixelLET > pln.bioParam.lowerLETThreshold) = alpha_0(bixelLET > this.p_upperLETThreshold) + this.p_lamda_1_1 * this.p_upperLETThreshold;
+ bixelAlpha(bixelLET < pln.bioParam.lowerLETThreshold) = alpha_0(bixelLET < this.p_lowerLETThreshold) + this.p_lamda_1_1 * this.p_lowerLETThreshold;
+ end
+
+ bixelBeta = vBeta_x;
+
+ % TODO: assign normal tissue an RBE of 1.1
+
+ case {'protons_MCN'}
+
+ RBEmax = this.p0_MCN + ((this.p1_MCN * bixelLET )./ vABratio);
+ RBEmin = this.p2_MCN + (this.p3_MCN * sqrt(vABratio) .* bixelLET);
+ bixelAlpha = RBEmax .* vAlpha_x;
+ bixelBeta = RBEmin.^2 .* vBeta_x;
+
+ case {'protons_WED'}
+
+ RBEmax = this.p0_WED + ((this.p1_WED * bixelLET )./ vABratio);
+ RBEmin = this.p2_WED;
+ bixelAlpha = RBEmax .* vAlpha_x;
+ bixelBeta = RBEmin.^2 .* vBeta_x;
+
+ case {'helium_HEL'}
+
+ % data-driven RBE parametrization of helium ions
+ % https://iopscience.iop.org/article/10.1088/0031-9155/61/2/888
+
+
+ % quadratic fit
+ %f_Q = 8.53959e-4 .* bixelLET.^2;
+ %RBEmax_Q = 1 + 2.145e-1 + vABratio.^-1 .* f_Q;
+ % linear quadratic fit
+ %f_LQ = 2.91783e-1*bixelLET - 9.525e-4*bixelLET.^2;
+ %RBEmax_LQ = 1 + ((1.42057e-1 + (vABratio.^-1)) .* f_LQ);
+ % linear exponential fit
+ %f_LE = (2.965e-1 * bixelLET) .* exp(-4.90821e-3 * bixelLET);
+ %RBEmax_LE = 1 + ((1.5384e-1 + (vABratio.^-1)) .* f_LE);
+
+ % quadratic exponential fit
+ f_QE = (this.p1_HEL * bixelLET.^2) .* exp(-this.p2_HEL * bixelLET);
+ RBEmax_QE = 1 + ((this.p0_HEL + (vABratio.^-1)) .* f_QE);
+
+ % the linear quadratic fit yielded the best fitting result
+ RBEmax = RBEmax_QE;
+
+ RBEmin = 1; % no gain in using fitted parameters over a constant value of 1
+
+ bixelAlpha = RBEmax .* vAlpha_x;
+ bixelBeta = RBEmin.^2 .* vBeta_x;
+
+ case {'carbon_LEM','helium_LEM'}
+ %From matRad_calcLQParameter
+ numOfTissueClass = size(bixel.baseData.alpha,2);
+ for i = 1:numOfTissueClass
+ mask = bixel.vTissueIndex == i;
+ if any(mask)
+ bixelAlpha(mask) = kernels.alpha(mask);
+ bixelBeta(mask) = kernels.beta(mask);
+ end
+ end
+ otherwise
+
+ end
+
+ end
+
+
+
+ end % end public methods
+
+
+ methods(Static)
+
+ end % end static public methods
+
+
+end % end class definition
\ No newline at end of file
diff --git a/matRad/bioModels/matRad_bioModel.m b/matRad/bioModels/matRad_bioModel.m
new file mode 100644
index 000000000..082134684
--- /dev/null
+++ b/matRad/bioModels/matRad_bioModel.m
@@ -0,0 +1,42 @@
+function model = matRad_bioModel(sRadiationMode,sQuantityOpt, sModel)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad_bioModel
+% This is a helper function to instantiate a matRad_BiologicalModel. This
+% function currently exists for downwards compatability, as the new
+% Biological Models will follow a polymorphic software architecture
+%
+% call
+% matRad_bioModel(sRadiationMode,sQuantityOpt, sModel)
+%
+% e.g. pln.bioParam = matRad_bioModel('protons','constRBE','RBExD')
+%
+% input
+% sRadiationMode: radiation modality 'photons' 'protons' 'helium' 'carbon' 'brachy'
+% sQuantityOpt: string to denote the quantity used for
+% optimization 'physicalDose', 'RBExD', 'effect'
+% sModel: string to denote which biological model is used
+% 'none': for photons, protons, carbon 'constRBE': constant RBE for photons and protons
+% 'MCN': McNamara-variable RBE model for protons 'WED': Wedenberg-variable RBE model for protons
+% 'LEM': Local Effect Model for carbon ions
+%
+% output
+% model: instance of a biological model
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2023 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+model = matRad_BiologicalModel(sRadiationMode,sQuantityOpt,sModel);
+
+end % end class definition
\ No newline at end of file
diff --git a/matRad/bioModels/matRad_getPhotonLQMParameters.m b/matRad/bioModels/matRad_getPhotonLQMParameters.m
new file mode 100644
index 000000000..0b7dacc87
--- /dev/null
+++ b/matRad/bioModels/matRad_getPhotonLQMParameters.m
@@ -0,0 +1,65 @@
+function [ax,bx] = matRad_getPhotonLQMParameters(cst,numVoxel,VdoseGrid)
+% matRad function to receive the photon LQM reference parameter
+%
+% call
+% [ax,bx] = matRad_getPhotonLQMParameters(cst,numVoxel,ctScen,VdoseGrid)
+%
+% input
+% cst: matRad cst struct
+% numVoxel: number of voxels of the dose cube
+% VdoseGrid: optional linear index vector that allows to specify subindices
+% for which ax and bx will be computed
+%
+% output
+% ax: vector containing for each linear voxel index alpha_x
+% bx: vector containing for each linear voxel index beta_x
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+numOfCtScen = unique(cellfun(@numel,cst(:,4)));
+
+if numel(numOfCtScen) > 1
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Inconsinstent number of ct scnearios in cst!');
+end
+
+ax = cell(numOfCtScen,1);
+bx = cell(numOfCtScen,1);
+
+[ax{:}] = deal(zeros(numVoxel,1));
+[bx{:}] = deal(zeros(numVoxel,1));
+
+for i = 1:size(cst,1)
+ if isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET')
+ for s = 1:numOfCtScen
+
+ if exist('VdoseGrid','var')
+ if iscell(VdoseGrid)
+ isInVdoseGrid = ismember(VdoseGrid{s},cst{i,4}{s});
+ else
+ isInVdoseGrid = ismember(VdoseGrid,cst{i,4}{s});
+ end
+ ax{s}(VdoseGrid(isInVdoseGrid)) = cst{i,5}.alphaX;
+ bx{s}(VdoseGrid(isInVdoseGrid)) = cst{i,5}.betaX;
+ else
+ ax{s}(cst{i,4}{s}) = cst{i,5}.alphaX;
+ bx{s}(cst{i,4}{s}) = cst{i,5}.betaX;
+ end
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/matRad/brachytherapy/additionalScripts/matRad_plot2DFunc.m b/matRad/brachytherapy/additionalScripts/matRad_plot2DFunc.m
new file mode 100644
index 000000000..b86f86b06
--- /dev/null
+++ b/matRad/brachytherapy/additionalScripts/matRad_plot2DFunc.m
@@ -0,0 +1,47 @@
+%% plot matRad_getDoseRate2D_poly
+% Execute from brachytherapy folder
+% load brachy_HDR machine manually from matRad/basedata!
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2021 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% set resolution
+res = 3;
+maxRadius = 100;
+DoseCutoff = 500;
+
+% prepare grid
+x = [-maxRadius:res:maxRadius];
+y = [-maxRadius:res:maxRadius];
+[X,Y] = meshgrid(x,y);
+[thet_rad,r] = cart2pol(X,Y);
+thet = rad2deg(thet_rad);
+thet(thet<=0) = -thet(thet<=0);
+
+% load basedata
+load brachy_HDR
+HDRmachine = machine;
+load brachy_LDR
+LDRmachine = machine;
+clear machine
+
+% call and plot function
+DoseRate = matRad_getDoseRate2D_poly(HDRmachine,r,thet);
+DoseRate(DoseRate>DoseCutoff) = DoseCutoff;
+DoseRate(DoseRate<0) = 0;
+
+% surf(X,Y,DoseRate,'LineStyle','none')
+% xlabel('x[mm]')
+% ylabel('y[mm]')
+% zlabel('2D approx DoseRate')
+image(DoseRate,'CDataMapping','scaled')
+colorbar
\ No newline at end of file
diff --git a/matRad/brachytherapy/matRad_getDistanceMatrix.m b/matRad/brachytherapy/matRad_getDistanceMatrix.m
new file mode 100644
index 000000000..73c15c79b
--- /dev/null
+++ b/matRad/brachytherapy/matRad_getDistanceMatrix.m
@@ -0,0 +1,49 @@
+function [DistanceMatrix,DistanceVector] = matRad_getDistanceMatrix(seedPoints,dosePoints)
+% matRad_getDistanceMatrix gets (seedpoint x dosepoint) matrix of relative
+% distances
+%
+% call
+% [DistanceMatrix,DistanceVector] = getDistanceMatrix(seedPoints,...
+% dosePoints)
+% normally called within matRad_getBrachyDose
+%
+% input
+% seedPoints: struct with fields x,y,z
+% dosePoints: struct with fields x,y,z
+%
+% output
+% distance matrix: rows: index of dosepoint
+% columns: index of deedpoint
+% entry: distance of seedpoints and dosepoint in mm
+% |
+% | DistanceMatrix.x/y/z: x/y/z component of
+% distance(needed for theta calc)
+% | DistanceMatrix.dist: eucledian distance
+% distance vector: column vector of DistanceMatrix.dist entries
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2021 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+
+DistanceMatrix.x = dosePoints.x'*ones(1,length(seedPoints.x)) - ones(length(dosePoints.x),1)*seedPoints.x;
+DistanceMatrix.y = dosePoints.y'*ones(1,length(seedPoints.y)) - ones(length(dosePoints.y),1)*seedPoints.y;
+DistanceMatrix.z = dosePoints.z'*ones(1,length(seedPoints.z)) - ones(length(dosePoints.z),1)*seedPoints.z;
+DistanceMatrix.dist = sqrt(DistanceMatrix.x.^2+DistanceMatrix.y.^2+DistanceMatrix.z.^2);
+
+if nargout == 2
+DistanceVector = reshape(DistanceMatrix.dist,[],1);
+end
+
+end
+
diff --git a/matRad/brachytherapy/matRad_getTemplateRoot.m b/matRad/brachytherapy/matRad_getTemplateRoot.m
new file mode 100644
index 000000000..6eb7470c3
--- /dev/null
+++ b/matRad/brachytherapy/matRad_getTemplateRoot.m
@@ -0,0 +1,59 @@
+function templateRoot = matRad_getTemplateRoot(ct,cst)
+%matRad_getTemplateRoot calculates origin position for template
+%
+% call
+% matRad_getTemplateRoot(ct,cst)
+%
+% input
+% ct: ct cube
+% cst: matRad cst struct
+%
+% output
+% templateRoot: 1x3 column vector with root position
+% x,y : center \\ z : bottom of target VOI
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2021 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Initializes V variable.
+V = [];
+
+%Check if any constraints/Objectives have been defined yet
+noObjOrConst = all(cellfun(@isempty,cst(:,6)));
+
+% Save target indices in V variable.
+for i = 1:size(cst,1)
+ % We only let a target contribute if it has an objective/constraint or
+ % if we do not have specified objectives/constraints at all so far
+ if isequal(cst{i,3},'TARGET') && (~isempty(cst{i,6}) || noObjOrConst)
+ V = [V; cst{i,4}{1}];
+ end
+end
+
+% Delete repeated indices, one voxel can belong to two VOIs, because
+% VOIs can be overlapping.
+V = unique(V);
+
+% throw error message if no target is found
+if isempty(V)
+ error('Could not find target');
+end
+
+% Transform to [mm]
+coord = matRad_cubeIndex2worldCoords(V, ct); %idx2worldcoord
+
+% Calculated isocenter.
+templateRoot = [mean(coord(:,[1 2])) min(coord(:,3))];
+
+end
+
diff --git a/dicom/@matRad_DicomExporter/matRad_DicomExporter.m b/matRad/dicom/@matRad_DicomExporter/matRad_DicomExporter.m
similarity index 89%
rename from dicom/@matRad_DicomExporter/matRad_DicomExporter.m
rename to matRad/dicom/@matRad_DicomExporter/matRad_DicomExporter.m
index 50075ed05..6e7831789 100644
--- a/dicom/@matRad_DicomExporter/matRad_DicomExporter.m
+++ b/matRad/dicom/@matRad_DicomExporter/matRad_DicomExporter.m
@@ -18,7 +18,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
- % distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -30,6 +30,9 @@
%output folder
dicomDir = ['.' filesep];
+ %which scenario to export (no 4D data supported for now)
+ exportScenario = 1;
+
% matRad structures to export
ct = [];
cst = [];
@@ -69,11 +72,28 @@
rtDoseMetas
rtDoseExportStatus
+ %RTPlan
+ rtPlanFilePrefix = 'RTPlan'
+ rtPlanMeta
+ rtPlanExportStatus
+ rtPlanLabel = 'matRadPlan';
+ rtPlanName = 'matRadPlan'
+
% some dictionaries
externalContourDict = {'EXTERNAL','BODY','PATIENT'}; %Names to identify external contours
targetPtvDict = {'PTV','MARGIN'};
targetCtvDict = {'CTV'};
targetGtvDict = {'GTV','TUMOR'};
+
+ % enable RTPlan Export
+ enableRtPlanExport = false;
+ end
+
+ properties (Constant)
+ rtPlanClassUID = '1.2.840.10008.5.1.4.1.1.481.5';
+ rtStructClassUID = '1.2.840.10008.5.1.4.1.1.481.3';
+ rtDoseClassUID = '1.2.840.10008.5.1.4.1.1.481.2';
+ ctClassUID = '1.2.840.10008.5.1.4.1.1.2';
end
methods
@@ -82,7 +102,6 @@
% Can be called with the structures. If no argument is given,
% all structures will be read from the base workspace
-
matRad_cfg = MatRad_Config.instance();
env = matRad_getEnvironment();
@@ -158,7 +177,11 @@
obj.StudyInstanceUID = dicomuid;
% coordinates
- obj.FrameOfReferenceUID = dicomuid;
+ if isfield(obj.ct,'dicomMeta') && isfield(obj.ct.dicomMeta,'FrameOfReferenceUID')
+ obj.FrameOfReferenceUID = obj.ct.dicomMeta.FrameOfReferenceUID;
+ else
+ obj.FrameOfReferenceUID = dicomuid;
+ end
if isfield(obj.ct,'dicomInfo') && isfield(obj.ct.dicomInfo,'PatientPosition')
obj.PatientPosition = obj.ct.dicomInfo.PatientPosition;
@@ -172,11 +195,11 @@
obj = matRad_exportDicomCt(obj)
obj = matRad_exportDicomRTStruct(obj)
+
+ obj = matRad_exportDicomRTDoses(obj)
obj = matRad_exportDicomRTPlan(obj)
- obj = matRad_exportDicomRTDoses(obj)
-
end
methods (Static)
@@ -240,7 +263,8 @@
end
if displayBool
- fprintf('No value set for ''%s'', using default value ''%s''\n',fn,default);
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('No value set for ''%s'', using default value ''%s''\n',fn,default);
end
end
end
diff --git a/dicom/@matRad_DicomExporter/matRad_exportDicom.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicom.m
similarity index 61%
rename from dicom/@matRad_DicomExporter/matRad_exportDicom.m
rename to matRad/dicom/@matRad_DicomExporter/matRad_exportDicom.m
index 09c4e0356..69ceacbbc 100644
--- a/dicom/@matRad_DicomExporter/matRad_exportDicom.m
+++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicom.m
@@ -14,7 +14,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -25,6 +25,9 @@
mkdir(obj.dicomDir);
end
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispInfo('Exporting DICOM for scenario %d to %s:\n',obj.exportScenario,obj.dicomDir);
+
%Name of Patient & Study
%CT Series
obj = matRad_exportDicomCt(obj);
@@ -35,16 +38,19 @@
obj = matRad_exportDicomRTStruct(obj);
end
-%if ~isempty(obj.pln)
-% obj = matRad_exportDicomRTPlan(obj);
-%end
-
+%RT Dose Series (Before Plan to have dose reference ids)
if ~isempty(obj.resultGUI)
obj = matRad_exportDicomRTDoses(obj);
end
-%Pln Series
+%RT Plan at the end
+if ~isempty(obj.pln) && ~isempty(obj.resultGUI)
+ if obj.enableRtPlanExport
+ obj = matRad_exportDicomRTPlan(obj);
+ else
+ matRad_cfg.dispWarning('pln and resultGUI objects provided, but exporting of RTPlan is disabled by default. Enable it with setting the property enableRtPlanExport to ''true'' before calling the export.');
+ end
+end
-%Dose Series
end
diff --git a/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m
similarity index 85%
rename from dicom/@matRad_DicomExporter/matRad_exportDicomCt.m
rename to matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m
index a8203f6b3..2dc7841de 100644
--- a/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m
+++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m
@@ -15,7 +15,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -24,7 +24,7 @@
matRad_cfg = MatRad_Config.instance();
-matRad_cfg.dispInfo('Exporting DICOM CT...');
+matRad_cfg.dispInfo('Exporting DICOM CT for scenario %s into %s...\n',obj.exportScenario,obj.dicomDir);
%default meta
meta.PatientName = obj.PatientName;
@@ -38,7 +38,7 @@
meta.StudyInstanceUID = obj.StudyInstanceUID;
meta.FrameOfReferenceUID = obj.FrameOfReferenceUID;
-ClassUID = '1.2.840.10008.5.1.4.1.1.2'; %CT Image
+ClassUID = obj.ctClassUID; %CT Image
meta.MediaStorageSOPClassUID = ClassUID;
meta.SOPClassUID = ClassUID;
%TransferSyntaxUID = '1.2.840.10008.1.2';
@@ -69,11 +69,7 @@
nSlices = ct.cubeDim(3);
%Create X Y Z vectors if not present
if ~any(isfield(ct,{'x','y','z'}))
- %positionOffset = transpose(ct.cubeDim ./ 2);
- positionOffset = ct.cubeDim ./ 2;
- ct.x = ct.resolution.x*[0:ct.cubeDim(2)-1] - positionOffset(2);
- ct.y = ct.resolution.y*[0:ct.cubeDim(1)-1] - positionOffset(1);
- ct.z = ct.resolution.z*[0:ct.cubeDim(3)-1] - positionOffset(3);
+ ct = matRad_getWorldAxes(ct);
end
obj.ct = ct;
@@ -88,7 +84,7 @@
z = ct.z;
end
-ctCube = ct.cubeHU{1};
+ctCube = ct.cubeHU{obj.exportScenario};
ctMin = min(ctCube(:));
ctCube = ctCube - ctMin;
ctMax = max(ctCube(:));
@@ -128,16 +124,21 @@
else
status = dicomwrite(ctSlice,fullFileName,obj.ctSliceMetas(i),'ObjectType','CT Image Storage');
obj.ctExportStatus = obj.addStruct2StructArray(obj.ctExportStatus,status);
-
- %We need to get the info of the file just written because of Matlab's
+
+ matRad_cfg.dispDebug('\tWritten Slice %d to %s\n',i,fullFileName);
+
+ %We need to get the info of the file just written because of Matlab's
%hardcoded way of generating InstanceUIDs during writing
tmpInfo = dicominfo(fullFileName);
obj.ctSliceMetas(i).SOPInstanceUID = tmpInfo.SOPInstanceUID;
- obj.ctSliceMetas(i).MediaStorageSOPInstanceUID = tmpInfo.MediaStorageSOPInstanceUID;
- end
-
- matRad_progress(i,nSlices);
+ obj.ctSliceMetas(i).MediaStorageSOPInstanceUID = tmpInfo.MediaStorageSOPInstanceUID;
+
+ end
+ if matRad_cfg.logLevel > 2
+ matRad_progress(i,nSlices);
+ end
+
end
end
diff --git a/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m
similarity index 71%
rename from dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m
rename to matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m
index 65bd3e7cc..ed420dc0e 100644
--- a/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m
+++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m
@@ -14,39 +14,31 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- matRad_cfg = MatRad_Config.instance();
+matRad_cfg = MatRad_Config.instance();
matRad_cfg.dispInfo('Exporting DICOM RTDose...\n');
-env = matRad_getEnvironment();
-isOctave = strcmp(env,'OCTAVE');
-
-if isOctave
+if matRad_cfg.isOctave
matRad_cfg.dispWarning('RTDose export currently not supported by matRad running in Octave using the dicom package! Skipping...');
return;
end
%% CT check
ct = obj.ct;
-if ~any(isfield(ct,{'x','y','z'}))
- %positionOffset = transpose(ct.cubeDim ./ 2);
- positionOffset = ct.cubeDim ./ 2;
- ct.x = ct.resolution.x*[0:ct.cubeDim(2)-1] - positionOffset(2);
- ct.y = ct.resolution.y*[0:ct.cubeDim(1)-1] - positionOffset(1);
- ct.z = ct.resolution.z*[0:ct.cubeDim(3)-1] - positionOffset(3);
-end
+ct = matRad_getWorldAxes(ct);
+
%% Meta data
-storageClass = '1.2.840.10008.5.1.4.1.1.481.2';
+storageClass = obj.rtDoseClassUID;
meta.MediaStorageSOPClassUID = storageClass;
meta.SOPClassUID = storageClass;
-
+meta.FrameOfReferenceUID = obj.FrameOfReferenceUID;
%TransferSyntaxUID = '1.2.840.10008.1.2.1'; %Explicit VR little endian?
%meta.TransferSyntaxUID = TransferSyntaxUID;
@@ -70,7 +62,6 @@
meta.StudyDate = obj.StudyDate;
meta.StudyTime = obj.StudyTime;
-meta.FrameOfReferenceUID = obj.FrameOfReferenceUID;
meta.PositionReferenceIndicator = '';
@@ -91,7 +82,7 @@
%Now image meta
resolution = ct.resolution;
meta.PixelSpacing = [resolution.y; resolution.x];
-meta.SliceThickness = resolution.z;
+meta.SliceThickness = num2str(resolution.z);
meta.ImagePositionPatient = [ct.x(1); ct.y(1); ct.z(1)];
meta.ImageOrientationPatient = [1;0;0;0;1;0];
@@ -108,17 +99,23 @@
meta.GridFrameOffsetVector = transpose(ct.z - ct.z(1));
%Referenced Plan
+%This does currently not work due to how Matlab creates UIDs by itself,
+%we can not know the reference before it is written by the RTPlanExport,
+%which itself needs the RTDose UIDs
+%{
try
- rtPlanUID = obj.rtPlanMeta.SOPInstanceUID;
- rtPlanClassID = obj.rtPlanMeta.SOPClassUID;
- meta.ReferencedRTPlanSequence.Item_1.ReferencedSOPClassUID = rtPlanClassID;
- meta.ReferencedRTPlanSequence.Item_1.ReferencedSOPInstanceUID = rtPlanUID;
+ rtPlanUID = obj.rtPlanMeta.SOPInstanceUID;
catch
- rtPlanUID = '';
- rtPlanClassID = '';
+ obj.rtPlanMeta = struct();
+ obj.rtPlanMeta.SOPInstanceUID = dicomuid;
+ obj.rtPlanMeta.SOPClassUID = obj.rtPlanClassUID;
+ rtPlanUID = obj.rtPlanMeta.SOPInstanceUID;
end
-
+%}
+
+%meta.ReferencedRTPlanSequence.Item_1.ReferencedSOPClassUID = obj.rtPlanClassUID;
+%meta.ReferencedRTPlanSequence.Item_1.ReferencedSOPInstanceUID = rtPlanUID;
if nargin < 4 || isempty(doseFieldNames)
doseFieldNames = cell(0);
@@ -147,7 +144,7 @@
elseif strncmp(doseName,'RBExDose',8)
doseType = 'EFFECTIVE';
else
- fprintf('Dose Cube ''%s'' of unknown type for DICOM. Not exported!\n',doseName);
+ matRad_cfg.dispInfo('Dose Cube ''%s'' of unknown type for DICOM. Not exported!\n',doseName);
continue;
end
@@ -167,7 +164,7 @@
maxDose = max(doseCube(:));
if minDose < 0
- fprintf('Dose Cube ''%s'' has negative values. Not exported!\n',doseName);
+ matRad_cfg.dispInfo('Dose Cube ''%s'' has negative values. Not exported!\n',doseName);
continue;
end
@@ -179,32 +176,42 @@
metaCube.DoseSummationType = deliveryType;
%ID of the RTDose
- meta.SeriesInstanceUID = dicomuid;
+ metaCube.SeriesInstanceUID = dicomuid;
metaCube.SeriesNumber = i;
metaCube.InstanceNumber = 1;
metaCube.DoseGridScaling = doseCubeFac;
- meta.SOPInstanceUID = dicomuid;
- meta.MediaStorageSOPInstanceUID = meta.SOPInstanceUID;
+ metaCube.SOPInstanceUID = dicomuid;
+ metaCube.MediaStorageSOPInstanceUID = metaCube.SOPInstanceUID;
- fileName = [obj.rtDoseFilePrefix num2str(i) '_' doseName '.dcm'];
-
- env = matRad_getEnvironment();
- if strcmp(env,'OCTAVE')
- dicomwrite(doseCube,fullfile(obj.dicomDir,fileName),metaCube);
+ fileName = [obj.rtDoseFilePrefix num2str(i) '_' doseName '.dcm'];
+ fileName = fullfile(obj.dicomDir,fileName);
+
+ if matRad_cfg.isOctave
+ dicomwrite(doseCube,fileName,metaCube);
else
- status = dicomwrite(doseCube,fullfile(obj.dicomDir,fileName),metaCube,'CreateMode','copy');%,'TransferSyntax',TransferSyntaxUID);
+ status = dicomwrite(doseCube,fileName,metaCube,'CreateMode','copy');%,'TransferSyntax',TransferSyntaxUID);
if ~isempty(status)
obj.rtDoseExportStatus = obj.addStruct2StructArray(obj.rtDoseExportStatus,status,i);
end
end
+
+ %We need to get the info of the file just written because of Matlab's
+ %hardcoded way of generating InstanceUIDs during writing
+ tmpInfo = dicominfo(fileName);
+ metaCube.SOPInstanceUID = tmpInfo.SOPInstanceUID;
+ metaCube.MediaStorageSOPInstanceUID = tmpInfo.MediaStorageSOPInstanceUID;
obj.rtDoseMetas = obj.addStruct2StructArray(obj.rtDoseMetas,metaCube);
obj.rtDoseNames{i} = doseFieldNames{i};
- matRad_progress(i,numel(doseFieldNames));
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(i,numel(doseFieldNames));
+ end
+
end
diff --git a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTPlan.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTPlan.m
new file mode 100644
index 000000000..180cef941
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTPlan.m
@@ -0,0 +1,350 @@
+function obj = matRad_exportDicomRTPlan(obj)
+% matRad function to export pln to dicom.
+%
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispInfo('Exporting DICOM RTDose...\n');
+
+env = matRad_getEnvironment();
+isOctave = strcmp(env,'OCTAVE');
+
+%Safeguard Error
+matRad_cfg.dispError('You are trying to run the RTPlan Dicom Export! This error message is a safeguard such that this export is not used unconciously and that you aware that exported RTPlans MUST NOT be used cinically. If you want to use it (at your own responsibility), uncomment this error in %s!',mfilename('fulpath'));
+
+if isOctave
+ matRad_cfg.dispWarning('RTPlan export currently not supported by matRad running in Octave using the dicom package! Skipping...');
+ return;
+end
+
+%%
+matRad_cfg.dispInfo('Exporting RTPlan...\n');
+if isfield(obj.pln,'DicomInfo')
+ % if from an imported plan then use the existing dicom info
+ meta = pln.DicomInfo.Meta;
+
+else
+ meta = struct();
+
+ %Class UID
+ ClassUID = obj.rtPlanClassUID;
+ meta.MediaStorageSOPClassUID = ClassUID;
+ meta.SOPClassUID = ClassUID;
+ meta.FrameOfReferenceUID = obj.FrameOfReferenceUID;
+ %TransferSyntaxUID = '1.2.840.10008.1.2.1'; %Explicit VR Little Endian - correct?
+ %meta.TransferSyntaxUID = TransferSyntaxUID;
+
+ %Identifiers for this object
+ try
+ meta.SOPInstanceUID = obj.rtPlanMeta.SOPInstanceUID;
+ catch
+ meta.SOPInstanceUID = dicomuid;
+ end
+ meta.MediaStorageSOPInstanceUID = meta.SOPInstanceUID;
+ meta.SeriesInstanceUID = dicomuid;
+ meta.SeriesNumber = 1;
+ meta.InstanceNumber = 1;
+
+ currDate = now;
+ currDateStr = datestr(currDate,'yyyymmdd');
+ currTimeStr = datestr(currDate,'HHMMSS');
+ meta.InstanceCreationDate = currDateStr;
+ meta.InstanceCreationTime = currTimeStr;
+
+ %ID/Information about the Study
+ meta.StudyInstanceUID = obj.StudyInstanceUID;
+ meta.StudyID = obj.StudyID;
+ meta.StudyDate = obj.StudyDate;
+ meta.StudyTime = obj.StudyTime;
+
+ %Remaining Meta Data
+ meta.Modality = 'RTPLAN';
+ meta.Manufacturer = 'matRad';
+ meta.ReferringPhysicianName = obj.dicomName();
+ meta.OperatorsName = obj.OperatorsName;
+ meta.StationName = '';
+ meta.AccessionNumber = '';
+ meta = obj.assignDefaultMetaValue(meta,'ManufacturerModelName','matRad DicomExport');
+
+ meta.PatientName = obj.PatientName;
+ meta.PatientID = obj.PatientID;
+ meta.PatientBirthDate = obj.PatientBirthDate;
+ meta.PatientSex = obj.PatientSex;
+
+ meta.ApprovalStatus = 'UNAPPROVED';
+
+ meta.RTPlanLabel = obj.rtPlanLabel;
+ meta.RTPlanName = obj.rtPlanName;
+ meta.RTPlanDate = currDateStr;
+ meta.RTPlanTime = currTimeStr;
+ meta.RTPlanGeometry = 'PATIENT';
+
+ meta.SoftwareVersions = matRad_version();
+ meta.ManufacturerModelName = matRad_version();
+end
+
+%Correct Positioning Offset
+ct = obj.ct;
+if ~any(isfield(ct,{'x','y','z'}))
+ %positionOffset = transpose(ct.cubeDim ./ 2);
+ %positionOffset = ct.cubeDim .* [ct.resolution.y, ct.resolution.x, ct.resolution.z] ./ 2;
+ positionOffset = [ct.resolution.y, ct.resolution.x, ct.resolution.z] ./ 2;
+ ct.x = ct.resolution.x*[0:ct.cubeDim(2)-1] - positionOffset(2);
+ ct.y = ct.resolution.y*[0:ct.cubeDim(1)-1] - positionOffset(1);
+ ct.z = ct.resolution.z*[0:ct.cubeDim(3)-1] - positionOffset(3);
+end
+
+%Write references to image, RTStruct, RTDose
+%RTStruct
+meta.ReferencedStructureSetSequence.Item_1.ReferencedSOPClassUID = obj.rtStructClassUID;
+meta.ReferencedStructureSetSequence.Item_1.ReferencedSOPInstanceUID = obj.rtssMeta.SOPInstanceUID;
+
+%RTDose - %TODO the reference to the RTDose, also a little fishy because we
+%need cross-references to the rtplan in rtdose to. Maybe we need to set-up
+%IDs while constructing so we can set the references accordingly
+%meta.DoseReferenceSquence.Item_1. ...
+
+
+if obj.pln.propStf.numOfBeams ~= numel(obj.stf)
+ matRad_cfg.error('Inconsistency in stf! number of beams not matching!');
+end
+
+
+%Sequences
+%Sequences
+%ToleranceTableSequence - Optional, we do not write this
+
+% Fraction Sequence
+% meta.FractionGroupSequence.Item_1.
+meta.FractionGroupSequence.Item_1.FractionGroupNumber = 1;
+meta.FractionGroupSequence.Item_1.NumberOfFractionsPlanned = obj.pln.numOfFractions;
+meta.FractionGroupSequence.Item_1.NumberOfBeams = obj.pln.propStf.numOfBeams;
+meta.FractionGroupSequence.Item_1.NumberOfBrachyApplicationSetups = 0;
+% meta.FractionGroupSequence.Item_1.BeamDoseMeaning = 'FRACTION_LEVEL'; %TODO: This is probably no longer necessary.
+
+refDoseSeq = struct();
+
+%We need the doses to be exported already if we want to store the reference
+for i = 1:numel(obj.rtDoseMetas)
+ currItemStr = sprintf('Item_%d',i);
+ refDoseSeq.(currItemStr).ReferencedSOPInstanceUID = obj.rtDoseMetas(i).SOPInstanceUID;
+ refDoseSeq.(currItemStr).ReferencedSOPClassUID = obj.rtDoseMetas(i).SOPClassUID;
+end
+
+if ~isempty(refDoseSeq)
+ meta.ReferencedDoseSequence = refDoseSeq;
+ meta.FractionGroupSequence.Item_1.ReferencedDoseSequence = refDoseSeq;
+end
+
+
+for iBeam = 1:obj.pln.propStf.numOfBeams
+ matRad_cfg.dispInfo('\tBeam %d: ',iBeam);
+
+ %PatientSetupSequence - Required
+ currBeamItemStr = sprintf('Item_%d',iBeam);
+ meta.PatientSetupSequence.(currBeamItemStr) = struct('PatientPosition','HFS','PatientSetupNumber',iBeam,'SetupTechniqueDescription','');
+
+
+
+ % Write Photon or ion RTPLAN
+ if strcmp(obj.pln.radiationMode,'photons')
+ BeamParam = 'BeamSequence';
+ ControlParam = 'ControlPointSequence';
+ elseif strcmp(obj.pln.radiationmode, 'protons') ||strcmp(obj.pln.radiationmode, 'helium') || strcmp(obj.pln.radiationmode, 'carbon')
+ BeamParam = 'IonBeamSequence';
+ ControlParam = 'IonControlPointSequence';
+ else
+ matRad_cfg.DispError('Not supported radiation mode of DICOM RT plan file.')
+ end
+
+ if strcmp(obj.pln.radiationMode,'photons')
+
+ %seqItem references to the current BeamSequence
+ beamSeqItem.TreatmentMachineName = obj.pln.machine;
+ beamSeqItem.PrimaryDosimeterUnit = 'MU';
+ beamSeqItem.SourceAxisDistance = obj.stf(iBeam).SAD;
+ beamSeqItem.BeamNumber = iBeam;
+ beamSeqItem.BeamType = 'DYNAMIC';
+ beamSeqItem.RadiationType = 'PHOTON';
+ beamSeqItem.TreatmentDeliveryType = 'TREATMENT';
+ beamSeqItem.NumberOfWedges = 0;
+ beamSeqItem.NumberOfCompensators = 0;
+ beamSeqItem.NumberOfBoli = 0;
+ beamSeqItem.NumberOfBlocks = 0;
+
+ %TODO: seqItem.ReferencedDoseSequence (for the beam doses??)
+
+ %Meterset
+ % The Meterset at a given Control Point is equal to Beam
+ % Meterset (300A,0086) specified in the Referenced Beam
+ % Sequence (300C,0004) of the RT Fraction Scheme Module,
+ % multiplied by the Cumulative Meterset Weight (300A,0134)
+ % for the Control Point, divided by the Final Cumulative
+ % Meterset Weight (300A,010E).
+ % The Meterset is specified in units defined by Primary
+ % Dosimeter Unit (300A,00B3).
+
+ %Note also that if Final Cumulative Meterset Weight (300A,010E)
+ %is equal to 100, then Cumulative Meterset Weight (300A,0134)
+ %becomes equivalent to the percentage of Beam Meterset
+ %(300A,0086) delivered at each control point. If Final
+ %Cumulative Meterset Weight (300A,010E) is equal to Beam
+ %Meterset (300A,0086), then the Cumulative Meterset Weight
+ %(300A,0134) at each control point becomes equal to the
+ %cumulative Meterset delivered at that control point.
+
+ %This means that this can all be relative, when this value is
+ %100, but we need an absolute value somewhere else. So let's
+ %just use the shape weights and sum them up in the end
+ %beamSeqItem.FinalCumulativeMetersetWeight = 100;
+
+
+
+ % Take the information from the apertureInfo field
+ %TODO: Get info about jaws?
+ if ~isfield(obj.resultGUI,'apertureInfo')
+ matRad_cfg.dispError('Sequenced Apertures not found!')
+ end
+ apertureInfo = obj.resultGUI.apertureInfo;
+ currBeamApertures = apertureInfo.beam(iBeam);
+
+ %Get basic information about the collimator:
+ limitDeviceSeq = struct();
+ limitDeviceSeq.Item_1.RTBeamLimitingDeviceType = 'MLCX';
+ limitDeviceSeq.Item_1.NumberOfLeafJawPairs = apertureInfo.numOfMLCLeafPairs;
+
+ if ~isfield(apertureInfo,'leafBoundaries') %TODO Information should be added by the sequencers
+ matRad_cfg.dispWarning('Leaf Boundaries not specified in aperture info!');
+ centralLeafPair = currBeamApertures.centralLeafPair;
+ leafBoundaries = 0:apertureInfo.bixelWidth:(apertureInfo.numOfMLCLeafPairs)*apertureInfo.bixelWidth;
+ centralLeafOffset = leafBoundaries(centralLeafPair) + apertureInfo.bixelWidth/2;
+ leafBoundaries = leafBoundaries - centralLeafOffset;
+ else
+ leafBoundaries = apertureInfo.leafBoundaries;
+ end
+ limitDeviceSeq.Item_1.LeafPositionBoundaries = leafBoundaries;
+
+ if ~isfield(apertureInfo,'SCD') %TODO implement that sequencers add this information (from machine file?)
+ load(fullfile(matRad_cfg.matRadRoot,'basedata',['photons_' obj.pln.machine '.mat']),'machine');
+ SCD = machine.meta.SCD;
+ else
+ SCD = aperturmateInfo.SCD;
+ end
+ limitDeviceSeq.Item_1.SourceToBeamLimitingDeviceDistance = SCD;
+
+ beamSeqItem.BeamLimitingDeviceSequence = limitDeviceSeq;
+
+
+ beamSeqItem.NumberOfControlPoints = currBeamApertures.numOfShapes;
+
+ %Get IMRT shapes. Only IMRT support (DYNAMIC), no VMAT
+ cumulativeMetersetWeight = 0;
+ for iShape = 1:currBeamApertures.numOfShapes
+ currCtrlSeqItemStr = sprintf('Item_%d',iShape);
+ currCtrlSeqItem = struct();
+ currCtrlSeqItem.ControlPointIndex = iShape-1;
+
+ currShape = currBeamApertures.shape(iShape);
+
+ %Write static Attributes only required for first control point:
+ if iShape == 1
+ currCtrlSeqItem.GantryAngle = obj.stf(iBeam).gantryAngle;
+ currCtrlSeqItem.GantryRotationDirection = 'NONE';
+ currCtrlSeqItem.PatientSupportAngle = obj.stf(iBeam).couchAngle;
+ currCtrlSeqItem.PatientSupportRotationDirection = 'NONE';
+ currCtrlSeqItem.TableTopEccentricAngle = 0;
+ currCtrlSeqItem.TableTopEccentricRotationDirection = 'NONE';
+ currCtrlSeqItem.IsocenterPosition = obj.stf(iBeam).isoCenter';
+ end
+
+ %Check energy, currently can only be constant
+ energy = unique([obj.stf(iBeam).ray.energy]);
+ if numel(energy) > 1
+ matRad_cfg.dispError('Multiple Energies currently not supported!');
+ end
+ currCtrlSeqItem.NominalBeamEnergy = energy;
+
+ %Collimator (& Jaws, but we leave them out for now)
+ limitPosSeq = struct();
+ limitPosSeq.Item_1.RTBeamLimitingDeviceType = 'MLCX';
+
+ leftLeafPos = zeros(apertureInfo.numOfMLCLeafPairs,1);
+ rightLeafPos = leftLeafPos;
+
+ leftLeafPos(logical(currBeamApertures.isActiveLeafPair)) = currShape.leftLeafPos;
+ rightLeafPos(logical(currBeamApertures.isActiveLeafPair)) = currShape.rightLeafPos;
+
+ limitPosSeq.Item_1.LeafJawPositions = [leftLeafPos; rightLeafPos];
+
+ currCtrlSeqItem.BeamLimitingDevicePositionSequence = limitPosSeq;
+
+ %This always needs to be zero for the first Item. However,
+ %the last one should be the fully cumulated value. Does
+ %this mean that the last control point ?
+ currCtrlSeqItem.CumulativeMetersetWeight = cumulativeMetersetWeight;
+
+ shapeWeight = currBeamApertures.shape(iShape).weight; %Is this correct? It basically means that the next
+ cumulativeMetersetWeight = cumulativeMetersetWeight + shapeWeight;
+
+ beamSeqItem.ControlPointSequence.(currCtrlSeqItemStr) = currCtrlSeqItem;
+ end
+
+ %We need to add a final control point only containing the
+ %cumulative meterset
+ currCtrlSeqItemStr = sprintf('Item_%d',iShape+1);
+ beamSeqItem.ControlPointSequence.(currCtrlSeqItemStr) = struct();
+ beamSeqItem.ControlPointSequence.(currCtrlSeqItemStr).CumulativeMetersetWeight = cumulativeMetersetWeight;
+
+ %Add the final cumulative meterset weight (see comment above, we
+ %use absolute values and not 100, so we can easy add it to the Fraction sequence)
+ beamSeqItem.FinalCumulativeMetersetWeight = cumulativeMetersetWeight;
+
+ %TODO: Do we need anything else here?
+
+ matRad_cfg.dispInfo('exported %d Control Points.\n',iShape+1);
+ end
+
+ meta.BeamSequence.(currBeamItemStr) = beamSeqItem;
+
+ %TODO meta.FractionGroupSequence.Item_1.ReferencedBeamSequence
+ refBeamSeqItem = struct();
+ resultGUIbeamStr = sprintf('physicalDose_beam%d',iBeam);
+ if isfield(obj.resultGUI,resultGUIbeamStr)
+ refBeamSeqItem.BeamDose = mean(obj.resultGUI.physicalDose_beam1,"all"); %TODO: Probably no longer necessary according to standard
+ end
+ refBeamSeqItem.BeamMeterset = cumulativeMetersetWeight;
+ refBeamSeqItem.ReferencedBeamNumber = iBeam;
+ meta.FractionGroupSequence.Item_1.ReferencedBeamSequence.(currBeamItemStr) = refBeamSeqItem;
+end
+
+%%
+
+filename = 'RTplan.dcm';
+filepath = obj.dicomDir;
+filename = fullfile(filepath,filename);
+
+% write dicom file
+env = matRad_getEnvironment();
+if isOctave
+ dicomwrite(int16(zeros(2)),filename,meta);
+else
+ obj.rtPlanExportStatus = dicomwrite([],filename,meta,'CreateMode','copy');%,'TransferSyntax',TransferSyntaxUID);
+end
+obj.rtPlanMeta = meta;
+
+end
diff --git a/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m
similarity index 84%
rename from dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m
rename to matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m
index f32df8868..0de043444 100644
--- a/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m
+++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m
@@ -15,33 +15,31 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- matRad_cfg = MatRad_Config.instance();
+matRad_cfg = MatRad_Config.instance();
matRad_cfg.dispInfo('Exporting DICOM RTStruct...\n');
-env = matRad_getEnvironment();
-isOctave = strcmp(env,'OCTAVE');
-
-if isOctave
+if matRad_cfg.isOctave
matRad_cfg.dispWarning('RTStruct export currently not supported by matRad running in Octave due to crashing dicomwrite! Skipping...');
return;
end
%% Metadata
%Class UID
-ClassUID = '1.2.840.10008.5.1.4.1.1.481.3'; %RT Structure Set
+ClassUID = obj.rtStructClassUID; %RT Structure Set
meta.MediaStorageSOPClassUID = ClassUID;
meta.SOPClassUID = ClassUID;
%TransferSyntaxUID = '1.2.840.10008.1.2.1'; %Explicit VR Little Endian - correct?
%meta.TransferSyntaxUID = TransferSyntaxUID;
%Identifiers
+meta.FrameOfReferenceUID = obj.FrameOfReferenceUID;
meta.SOPInstanceUID = dicomuid;
meta.MediaStorageSOPInstanceUID = meta.SOPInstanceUID;
meta.SeriesInstanceUID = dicomuid;
@@ -93,19 +91,14 @@
ct = obj.ct;
%Create X Y Z vectors if not present
-if ~any(isfield(ct,{'x','y','z'}))
- %positionOffset = transpose(ct.cubeDim ./ 2);
- positionOffset = ct.cubeDim ./ 2;
- ct.x = ct.resolution.x*[0:ct.cubeDim(2)-1] - positionOffset(2);
- ct.y = ct.resolution.y*[0:ct.cubeDim(1)-1] - positionOffset(1);
- ct.z = ct.resolution.z*[0:ct.cubeDim(3)-1] - positionOffset(3);
-end
+ct = matRad_getWorldAxes(ct);
+
%Since we are exporting HU directly --> no rescaling in any case
-%meta.SliceThickness = ct.resolution.z;
-%meta.PixelSpacing = [ct.resolution.y; ct.resolution.x];
-%meta.ImageOrientationPatient = [1;0;0;0;1;0]; %lps
+meta.SliceThickness = num2str(ct.resolution.z);
+meta.PixelSpacing = [ct.resolution.y; ct.resolution.x];
+meta.ImageOrientationPatient = [1;0;0;0;1;0]; %lps
%meta.RescaleSlope = 1;
%meta.RescaleIntercept = 0;
@@ -114,9 +107,9 @@
for i = 1:size(obj.cst,1)
- fprintf('Processinging VOI ''%s''...',obj.cst{i,2});
+ matRad_cfg.dispInfo('Processinging VOI ''%s''...',obj.cst{i,2});
%Select contours in axial slices
- contours = obj.cst{i,7}(:,3);
+ contours = obj.cst{i,7}{obj.exportScenario}(:,3);
contourSliceIx = find(~cellfun(@isempty,contours));
contourSlicePos = ct.z(contourSliceIx);
contours = contours(contourSliceIx);
@@ -158,7 +151,7 @@
meta.StructureSetROISequence.(['Item_' num2str(i)]) = ROISequenceItem;
%Contour Sequence
- if ~isOctave
+ if ~ matRad_cfg.isOctave
ROIContourSequenceItem.ROIDisplayColor = int32(round(255 * obj.cst{i,5}.visibleColor));
end
@@ -198,22 +191,22 @@
if strcmp(obj.cst{i,3},'TARGET')
- if ~isempty(regexpi(obj.cst{i,2},['(' strjoin(obj.targetPtvDict) ')']))
+ if ~isempty(regexpi(obj.cst{i,2},['(' strjoin(obj.targetPtvDict) ')'], 'once'))
RTROIObservationsSequenceItem.RTROIInterpretedType = 'PTV';
- fprintf('identified target type as PTV...');
- elseif ~isempty(regexpi(obj.cst{i,2},['(' strjoin(obj.targetGtvDict) ')']))
+ matRad_cfg.dispInfo('identified target type as PTV...');
+ elseif ~isempty(regexpi(obj.cst{i,2},['(' strjoin(obj.targetGtvDict) ')'], 'once'))
RTROIObservationsSequenceItem.RTROIInterpretedType = 'GTV';
fprintf('identified target type as GTV...');
- elseif ~isempty(regexpi(obj.cst{i,2},['(' strjoin(obj.targetGtvDict) ')']))
+ elseif ~isempty(regexpi(obj.cst{i,2},['(' strjoin(obj.targetCtvDict) ')'], 'once'))
RTROIObservationsSequenceItem.RTROIInterpretedType = 'CTV';
- fprintf('identified target type as CTV...');
+ matRad_cfg.dispInfo('identified target type as CTV...');
else
RTROIObservationsSequenceItem.RTROIInterpretedType = 'CTV';
- fprintf('Defaulting target type to CTV...');
+ matRad_cfg.dispInfo('Defaulting target type to CTV...');
end
else
- if ~isempty(regexpi(obj.cst{i,2},['(' strjoin(obj.externalContourDict) ')']))
- fprintf('automatically identified as External Contour...');
+ if ~isempty(regexpi(obj.cst{i,2},['(' strjoin(obj.externalContourDict) ')'], 'once'))
+ matRad_cfg.dispInfo('automatically identified as External Contour...');
RTROIObservationsSequenceItem.RTROIInterpretedType = 'EXTERNAL';
else
RTROIObservationsSequenceItem.RTROIInterpretedType = 'AVOIDANCE';
@@ -224,7 +217,7 @@
meta.RTROIObservationsSequence.(['Item_' num2str(i)]) = RTROIObservationsSequenceItem;
- fprintf('Done!\n');
+ matRad_cfg.dispInfo('Done!\n');
%matRad_progress(i,size(obj.cst,1));
end
@@ -239,7 +232,7 @@
RTReferencedSeriesSequenceItem.SeriesInstanceUID = obj.ctSliceMetas(1).SeriesInstanceUID;
RTReferencedSeriesSequenceItem.ContourImageSequence = ContourImageSequence;
-RTReferencedStudySequenceItem.ReferencedSOPClassUID = '1.2.840.10008.3.1.2.3.2'; %Apparently this class UID is deprecated in DICOM standard - what to use instead?
+%RTReferencedStudySequenceItem.ReferencedSOPClassUID = '1.2.840.10008.3.1.2.3.2'; %Apparently this class UID is deprecated in DICOM standard - what to use instead?
RTReferencedStudySequenceItem.ReferencedSOPInstanceUID = obj.StudyInstanceUID;
RTReferencedStudySequenceItem.RTReferencedSeriesSequence.Item_1 = RTReferencedSeriesSequenceItem;
@@ -251,12 +244,18 @@
filename = fullfile(filepath,filename);
-if isOctave
+if matRad_cfg.isOctave
dicomwrite(int16(zeros(2)),filename,meta);
else
- obj.rtssExportStatus = dicomwrite([],filename,meta,'CreateMode','copy');%,'TransferSyntax',TransferSyntaxUID);
+ obj.rtssExportStatus = dicomwrite([],filename,meta,'CreateMode','copy');%,'TransferSyntax',TransferSyntaxUID);
end
+%We need to get the info of the file just written because of Matlab's
+%hardcoded way of generating InstanceUIDs during writing
+tmpInfo = dicominfo(filename);
+meta.SOPInstanceUID = tmpInfo.SOPInstanceUID;
+meta.MediaStorageSOPInstanceUID = tmpInfo.MediaStorageSOPInstanceUID;
+
obj.rtssMeta = meta;
end
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m
new file mode 100644
index 000000000..c870cb100
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m
@@ -0,0 +1,133 @@
+classdef matRad_DicomImporter < handle
+ % matRad_DicomImporter matRad class to handle a dicom import.
+ %
+ % Example on how to use the matRad_DicomImport class
+ %
+ % dcmImpObj = matRad_DicomImporter('pathToFolder'); % create instance of matRad_DicomImporter
+ % dcmImpObj.matRad_importDicom(dcmImpObj); % run the import
+ %
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+
+ % path to DICOM file
+ patDir;
+
+ % lists of all files
+ allfiles;
+ patient;
+ importFiles; % all the names (directories) of files, that will be imported
+
+ % properties with data for import functions
+ importCT;
+ importRtss;
+ importRTDose;
+
+ % structures for .mat file
+ ct = [];
+ cst = [];
+ stf = [];
+ pln = [];
+ resultGUI = [];
+
+ ImportGrid;
+
+ % bools
+ dicomMetaBool;
+ visBool;
+
+
+
+ end
+
+ methods
+
+ function obj = matRad_DicomImporter(pathToFolder)
+
+ %matRad_DicomImporter Construct an instance of this class
+ % Can be called with the structures. If no argument is given,
+ % all structures will be read from the base workspace
+
+ obj.patDir = pathToFolder;
+ matRad_cfg = MatRad_Config.instance();
+
+ if matRad_cfg.isOctave
+ %Octave needs the DICOM package
+ try
+ pkg load dicom;
+ catch
+ matRad_cfg.dispError('The DICOM export requires the octave-forge package "dicom"!\n');
+ end
+ end
+
+ obj.patDir = pathToFolder;
+
+ obj.matRad_scanDicomImportFolder();
+
+ % matRad_DicomImporter imports only one structure, to select
+ % patients and structures within a single patient the
+ % matRad_importDicomWidget is used
+
+ ctFiles = strcmp(obj.allfiles(:,2),'CT');
+ rtssFiles = strcmpi(obj.allfiles(:,2),'rtstruct'); %note we can have multiple RT structure sets, matRad will always import the firstit finds
+ rtPlanFiles = strcmpi(obj.allfiles(:,2),'rtplan');
+ rtDoseFiles = strcmpi(obj.allfiles(:,2),'rtdose');
+
+ obj.importFiles.ct = obj.allfiles(ctFiles,:);%All CT slice filepaths stored in a cell array like {'CTSlice1.dcm','CTSlice2.dcm'};
+ obj.importFiles.rtss = obj.allfiles(rtssFiles,:);
+ obj.importFiles.rtplan = obj.allfiles(rtPlanFiles,:);
+ obj.importFiles.rtdose = obj.allfiles(rtDoseFiles,:);
+
+ for i = numel(obj.allfiles(:,1)):-1:1
+ if strcmp(obj.allfiles(i,2),'CT')
+ obj.importFiles.resx = obj.allfiles{i,9};
+ obj.importFiles.resy = obj.allfiles{i,10};
+ obj.importFiles.resz = obj.allfiles{i,11}; %some CT dicoms do not follow the standard and use SpacingBetweenSlices
+ break
+ end
+ end
+
+ obj.importFiles.useImportGrid = false;
+
+
+ end
+
+ matRad_importDicom(obj)
+
+ obj = matRad_importDicomCt(obj)
+
+ obj = matRad_importDicomRTDose(obj)
+
+ obj = matRad_importDicomRTPlan(obj)
+
+ obj = matRad_importDicomRtss(obj)
+
+ obj = matRad_importDicomSteeringPhotons(obj)
+
+ obj = matRad_importDicomSteeringParticles(obj)
+
+ obj = matRad_scanDicomImportFolder(obj)
+
+ obj = matRad_calcHU(obj)
+
+ obj = matRad_createCst(obj)
+
+ obj = matRad_dummyCst(obj)
+
+ % matRad_saveImport(obj);
+
+ end
+
+end
+
diff --git a/dicom/matRad_calcHU.m b/matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m
similarity index 55%
rename from dicom/matRad_calcHU.m
rename to matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m
index 35cc826c8..ea651ee5d 100644
--- a/dicom/matRad_calcHU.m
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m
@@ -1,17 +1,17 @@
-function ct = matRad_calcHU(ct)
+function obj = matRad_calcHU(obj)
% matRad function to calculate Hounsfield units from a dicom ct
% that originally uses intensity values
%
-% call
-% ct = matRad_calcHU(ct)
+% In your object, there must be a property that contains unprocessed
+% dicom ct data which are stored as intensity values (IV)
+%
+% Output - ct structure with cube with HU
%
-% input
-% ct: unprocessed dicom ct data which are stored as intensity values (IV)
+% HU = IV * slope + intercept
%
-% HU = IV * slope + intercept
+% call
+% obj = matRad_calcHU(obj)
%
-% output
-% ct: ct struct with cube with HU
%
% References
% -
@@ -22,17 +22,17 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-for i = 1:ct.numOfCtScen
- ct.cubeHU{i} = double(ct.cubeIV{i}) * double(ct.dicomInfo.RescaleSlope) + double(ct.dicomInfo.RescaleIntercept);
+for i = 1:obj.ct.numOfCtScen
+ obj.ct.cubeHU{i} = double(obj.ct.cubeIV{i}) * double(obj.ct.dicomInfo.RescaleSlope) + double(obj.ct.dicomInfo.RescaleIntercept);
end
-ct = rmfield(ct,'cubeIV');
+obj.ct = rmfield(obj.ct,'cubeIV');
end
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m b/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m
new file mode 100644
index 000000000..bb5b202af
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m
@@ -0,0 +1,82 @@
+function obj = matRad_createCst(obj)
+% matRad function to create a cst struct upon dicom import
+%
+% In your object, there must be a property that contains matlab structure
+% containing information about rt structure set (generated with
+% matRad_importDicomRtss and matRad_convRtssContours2Indices)
+%
+% Output - matRad cst structure
+%
+% call
+% obj = matRad_createCst(obj)
+%
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+nStructures = size(obj.importRtss.structures,2);
+obj.cst = cell(nStructures,6);
+
+%Create set of default colors
+defaultColors = colorcube(nStructures);
+
+for i = 1:size(obj.importRtss.structures,2)
+ obj.cst{i,1} = i - 1; % first organ has number 0
+ obj.cst{i,2} = obj.importRtss.structures(i).structName;
+
+ if ~isempty(regexpi(obj.cst{i,2},'tv', 'once')) || ...
+ ~isempty(regexpi(obj.cst{i,2},'target', 'once')) || ...
+ ~isempty(regexpi(obj.cst{i,2},'gtv', 'once')) || ...
+ ~isempty(regexpi(obj.cst{i,2},'ctv', 'once')) || ...
+ ~isempty(regexpi(obj.cst{i,2},'ptv', 'once')) || ...
+ ~isempty(regexpi(obj.cst{i,2},'boost', 'once')) || ...
+ ~isempty(regexpi(obj.cst{i,2},'tumor', 'once'))
+
+ obj.cst{i,3} = 'TARGET';
+
+ obj.cst{i,5}.Priority = 1;
+
+ % default objectives for targets
+ objective = DoseObjectives.matRad_SquaredDeviation;
+ objective.penalty = 800;
+ objective.parameters = {30}; %Default reference Dose
+ obj.cst{i,6}{1} = struct(objective);
+
+ else
+
+ obj.cst{i,3} = 'OAR';
+
+ obj.cst{i,5}.Priority = 2;
+
+ obj.cst{i,6} = []; % define no OAR dummy objcetives
+
+ end
+
+ obj.cst{i,4}{1} = obj.importRtss.structures(i).indices;
+
+ % set default parameter for biological planning
+ obj.cst{i,5}.alphaX = 0.1;
+ obj.cst{i,5}.betaX = 0.05;
+ obj.cst{i,5}.Visible = 1;
+ if isfield(obj.importRtss.structures(i),'structColor') && ~isempty(obj.importRtss.structures(i).structColor)
+ obj.cst{i,5}.visibleColor = obj.importRtss.structures(i).structColor' ./ 255;
+ else
+ obj.cst{i,5}.visibleColor = defaultColors(i,:);
+ matRad_cfg.dispInfo('No color information for structure %d "%s". Assigned default color [%f %f %f]\n',i,obj.cst{i,2},defaultColors(i,1),defaultColors(i,2),defaultColors(i,3));
+ end
+end
diff --git a/dicom/matRad_dummyCst.m b/matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m
similarity index 53%
rename from dicom/matRad_dummyCst.m
rename to matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m
index bb8700a2b..7534ddc4b 100644
--- a/dicom/matRad_dummyCst.m
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m
@@ -1,14 +1,14 @@
-function cst = matRad_dummyCst(ct)
+function obj = matRad_dummyCst(obj)
% matRad function to create a dummy cst struct for a ct
-%
-% call
-% cst = matRad_dummyCst(ct)
%
-% input
-% ct: matRad ct struct
+% In your object, there should be properties that contain:
+% - ct structure;
+% - cst structure.
%
-% output
-% cst: matRad cst struct
+% Output - matRad cst structure
+%
+% call
+% obj = matRad_dummyCst(obj)
%
% References
% -
@@ -19,7 +19,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -29,21 +29,21 @@
warning('Did not find RTSS. Creating dummy segmentation for matRad.');
% allocate
-cst = cell(1,6);
+obj.cst = cell(1,6);
% fill
-cst{1,1} = 0; % first organ has number 0
-cst{1,2} = 'dummyContour';
-cst{1,3} = 'OAR';
-cst{1,4}{1} = find(ct.cubeHU{1}>0.1);
-cst{1,5}.Priority = 1;
+obj.cst{1,1} = 0; % first organ has number 0
+obj.cst{1,2} = 'dummyContour';
+obj.cst{1,3} = 'OAR';
+obj.cst{1,4}{1} = find(obj.ct.cubeHU{1}>0.1);
+obj.cst{1,5}.Priority = 1;
% set default parameter for biological planning
-cst{1,5}.alphaX = 0.1;
-cst{1,5}.betaX = 0.05;
-cst{1,5}.Visible = 1;
-cst{1,5}.visibleColor = [0 0 0];
+obj.cst{1,5}.alphaX = 0.1;
+obj.cst{1,5}.betaX = 0.05;
+obj.cst{1,5}.Visible = 1;
+obj.cst{1,5}.visibleColor = [0 0 0];
% define no objcetives
-cst{1,6} = [];
+obj.cst{1,6} = [];
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m
new file mode 100644
index 000000000..2d209052b
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m
@@ -0,0 +1,217 @@
+function matRad_importDicom(obj)
+% matRad wrapper function to import a predefined set of dicom files files
+% into matRad's native data formats
+%
+% In your object, there must be properties that contain:
+% - list of files to be imported.
+% Optional:
+% - а boolean; if you don't want to import complete DICOM information,
+% set it to false.
+%
+% Next matRad structures are created in the object and saved in the
+% workspace:
+% - ct, cst, stf, pln, resultGUI.
+% *to save them as .mat file you can use matRad_importDicomWidget
+%
+% call
+% matRad_importDicom(obj)
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+
+%%
+if ~exist('dicomMetaBool','var')
+ obj.dicomMetaBool = true;
+end
+
+%%
+if ~matRad_cfg.disableGUI
+ h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor);
+ matRad_applyThemeToWaitbar(h);
+else
+ h = [];
+end
+%h.WindowStyle = 'Modal';
+steps = 2;
+
+%% import ct-cube
+if any(ishandle(h))
+ waitbar(1/steps, h)
+end
+obj.importCT.resolution.x = str2double(obj.importFiles.resx);
+obj.importCT.resolution.y = str2double(obj.importFiles.resy);
+obj.importCT.resolution.z = str2double(obj.importFiles.resz); % [mm] / lps coordinate system
+if obj.importFiles.useImportGrid && isfield(obj.importFiles,'rtdose')
+ % get grid from dose cube
+ if matRad_cfg.isOctave || verLessThan('matlab','9')
+ doseInfo = dicominfo(obj.importFiles.rtdose{1,1});
+ else
+ doseInfo = dicominfo(obj.importFiles.rtdose{1,1},'UseDictionaryVR',true);
+ end
+ obj.ImportGrid{1} = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ...
+ doseInfo.PixelSpacing(1) * double(0:doseInfo.Columns - 1);
+ obj.ImportGrid{2} = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ...
+ doseInfo.PixelSpacing(2) * double(0:doseInfo.Rows - 1);
+ obj.ImportGrid{3} = doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector(:)';
+
+ % get ct on grid
+ obj = matRad_importDicomCt(obj);
+
+else
+ obj = matRad_importDicomCt(obj);
+end
+
+if ~isempty(obj.importFiles.rtss)
+
+ %% import structure data
+ if any(ishandle(h))
+ waitbar(2/steps, h)
+ end
+ obj = matRad_importDicomRtss(obj);
+ if any(ishandle(h))
+ close(h)
+ end
+
+ %% creating structure cube
+ if ~matRad_cfg.disableGUI
+ h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor);
+ matRad_applyThemeToWaitbar(h);
+ else
+ h = [];
+ end
+ %h.WindowStyle = 'Modal';
+ steps = numel(obj.importRtss.structures);
+
+
+ % The x- & y-direction in lps-coordinates are specified in:
+ % ImageOrientationPatient
+
+obj.importRtss.xDir = obj.ct.dicomInfo.ImageOrientationPatient(1:3); % lps: [1;0;0]
+obj.importRtss.yDir = obj.ct.dicomInfo.ImageOrientationPatient(4:6); % lps: [0;1;0]
+
+
+if ~(obj.importRtss.xDir(1) == 1 && obj.importRtss.xDir(2) == 0 && obj.importRtss.xDir(3) == 0)
+ matRad_cfg.dispInfo('\nNonstandard image orientation: tring to Mirror RTSS x-direction...')
+end
+
+if ~(obj.importRtss.yDir(1) == 0 && obj.importRtss.yDir(2) == 1 && obj.importRtss.yDir(3) == 0)
+ matRad_cfg.dispInfo('\nNonstandard image orientation: trying to Mirror RTSS y direction...')
+end
+ for i = 1:numel(obj.importRtss.structures)
+ % computations take place here
+ if any(ishandle(h))
+ waitbar(1/steps, h)
+ end
+ matRad_cfg.dispInfo('creating cube for %s volume... ', obj.importRtss.structures(i).structName);
+ try
+ obj.importRtss.structures(i).indices = matRad_convRtssContours2Indices(obj.importRtss.structures(i),obj.ct);
+ matRad_cfg.dispInfo('\n');
+ catch ME
+ warning('matRad:dicomImport','could not be imported: %s',ME.message);
+ obj.importRtss.structures(i).indices = [];
+ end
+ end
+ matRad_cfg.dispInfo('finished!\n');
+ close(h)
+
+ %% creating cst
+ obj = matRad_createCst(obj);
+
+else
+
+ obj = matRad_dummyCst(obj);
+
+end
+
+%% determine pln parameters
+if ~isempty(obj.importFiles.rtplan)
+ if ~(cellfun(@isempty,obj.importFiles.rtplan(1,:)))
+ obj = matRad_importDicomRTPlan(obj);
+ end
+else
+ obj.pln = struct([]);
+end
+
+%% import stf
+if ~isempty(obj.importFiles.rtplan)
+ if ~(cellfun(@isempty,obj.importFiles.rtplan(1,:)))
+ if (strcmp(obj.pln.radiationMode,'protons') || strcmp(obj.pln.radiationMode,'carbon'))
+ %% import steering file
+ % pln output because bixelWidth is determined via the stf
+ obj = matRad_importDicomSteeringParticles(obj);
+ elseif strcmp(obj.pln.radiationMode, 'photons') && isfield(obj.pln.propStf,'collimation')
+ % return correct angles in pln
+ obj = matRad_importDicomSteeringPhotons(obj);
+ else
+ warning('No support for DICOM import of steering information for this modality.');
+ end
+ end
+else
+ obj.stf = struct([]);
+end
+
+%% import dose cube
+if ~isempty(obj.importFiles.rtdose)
+ % check if obj.importFiles.rtdose contains a path and is labeld as RTDose
+ % only the first two elements are relevant for loading the rt dose
+ if ~(cellfun(@isempty,obj.importFiles.rtdose(1,1)))
+ matRad_cfg.dispInfo('loading dose files...\n');
+ % parse plan in order to scale dose cubes to a fraction based dose
+ obj = matRad_importDicomRTDose(obj);
+ if size(obj.resultGUI) == 0
+ obj.resultGUI = struct([]);
+ end
+ end
+ matRad_cfg.dispInfo('finished!\n');
+else
+ obj.resultGUI = struct([]);
+ matRad_cfg.dispInfo('There are no dose files!\n');
+end
+
+%% put weight also into resultGUI
+if ~isempty(obj.stf) && ~isempty(obj.resultGUI)
+ obj.resultGUI.w = [];
+ for i = 1:size(obj.stf,2)
+ obj.resultGUI.w = [obj.resultGUI.w; [obj.stf(i).ray.weight]'];
+ end
+end
+
+%% put ct, cst, pln, stf, resultGUI to the workspace
+ct = obj.ct;
+cst = obj.cst;
+pln = obj.pln;
+stf = obj.stf;
+resultGUI = obj.resultGUI;
+
+assignin('base', 'ct', ct);
+assignin('base', 'cst', cst);
+
+if ~isempty(obj.pln)
+ assignin('base', 'pln', pln);
+end
+
+if ~isempty(obj.stf)
+ assignin('base', 'stf', stf);
+end
+
+if ~isempty(obj.resultGUI)
+assignin('base', 'resultGUI', resultGUI);
+end
+
+end
+
+
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m
new file mode 100644
index 000000000..82e152ab0
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m
@@ -0,0 +1,267 @@
+function obj = matRad_importDicomCt(obj)
+% matRad function to import dicom ct data
+%
+% In your object, there must be properties that contain:
+% - list of dicom ct files;
+% - resolution of the imported ct cube, i.e. this function will
+% interpolate to a different resolution if desired;
+% - a boolean, if you don't want to import complete dicom information set
+% it false.
+% Optional:
+% - a priori grid specified for interpolation;
+% - a boolean to turn off/on visualization.
+%
+% Output - matRad ct structure.
+% Note that this 3D matlab array contains water euqivalent
+% electron denisities. Hounsfield units are converted using a standard
+% lookup table in matRad_calcWaterEqD
+%
+% call
+% matRad_importDicomCt(obj)
+%
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+matRad_cfg.dispInfo('\nimporting ct-cube...');
+
+%% processing input variables
+if ~exist('visBool','var')
+ obj.visBool = 0;
+end
+
+matRad_checkEnvDicomRequirements(matRad_cfg.env);
+
+
+% creation of ctInfo list
+numOfSlices = size(obj.importFiles.ct, 1);
+matRad_cfg.dispInfo('\ncreating info...')
+
+sliceThicknessStandard = true;
+for i = 1:numOfSlices
+
+ if matRad_cfg.isOctave || verLessThan('matlab','9')
+ tmpDicomInfo = dicominfo(obj.importFiles.ct{i, 1});
+ else
+ tmpDicomInfo = dicominfo(obj.importFiles.ct{i, 1},'UseDictionaryVR',true);
+ end
+
+ % remember relevant dicom info - do not record everything as some tags
+ % might not been defined for individual files
+ obj.importCT.ctInfo(i).PixelSpacing = tmpDicomInfo.PixelSpacing;
+ obj.importCT.ctInfo(i).ImagePositionPatient = tmpDicomInfo.ImagePositionPatient;
+ obj.importCT.ctInfo(i).SliceThickness = str2double(obj.importFiles.resz);
+ obj.importCT.ctInfo(i).ImageOrientationPatient = tmpDicomInfo.ImageOrientationPatient;
+ obj.importCT.ctInfo(i).PatientPosition = tmpDicomInfo.PatientPosition;
+ obj.importCT.ctInfo(i).Rows = tmpDicomInfo.Rows;
+ obj.importCT.ctInfo(i).Columns = tmpDicomInfo.Columns;
+ obj.importCT.ctInfo(i).Width = tmpDicomInfo.Columns;%tmpDicomInfo.Width;
+ obj.importCT.ctInfo(i).Height = tmpDicomInfo.Rows;%tmpDicomInfo.Height;
+ obj.importCT.ctInfo(i).RescaleSlope = tmpDicomInfo.RescaleSlope;
+ obj.importCT.ctInfo(i).RescaleIntercept = tmpDicomInfo.RescaleIntercept;
+
+ %Problem due to some CT files using non-standard SpacingBetweenSlices
+
+ if isempty(obj.importCT.ctInfo(i).SliceThickness)
+ %Print warning once
+ if sliceThicknessStandard
+ matRad_cfg.dispWarning('Non-standard use of SliceThickness Attribute (empty), trying to overwrite with SpacingBetweenSlices');
+ sliceThicknessStandard = false;
+ end
+ obj.importCT.ctInfo(i).SliceThickness = tmpDicomInfo.SpacingBetweenSlices;
+ end
+
+ if i == 1
+ completeDicom = tmpDicomInfo;
+ end
+
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(i,numOfSlices);
+ end
+end
+
+% adjusting sequence of slices (filenames may not be ordered propperly....
+% e.g. CT1.dcm, CT10.dcm, CT100zCoordList = [ctInfo.ImagePositionPatient(1,3)]';.dcm, CT101.dcm,...
+CoordList = [obj.importCT.ctInfo.ImagePositionPatient]';
+[~, indexing] = unique(CoordList(:,3)); % get sortation from z-coordinates
+
+obj.importFiles.ct = obj.importFiles.ct(indexing');
+obj.importCT.ctInfo = obj.importCT.ctInfo(indexing');
+
+%% check data set for consistency
+if size(unique([obj.importCT.ctInfo.PixelSpacing]','rows'),1) > 1
+ matRad_cfg.dispError('Different pixel size in different CT slices');
+end
+
+coordsOfFirstPixel = [obj.importCT.ctInfo.ImagePositionPatient];
+if numel(unique(coordsOfFirstPixel(1,:))) > 1 || numel(unique(coordsOfFirstPixel(2,:))) > 1
+ matRad_cfg.dispError('Ct slices are not aligned');
+end
+if sum(diff(coordsOfFirstPixel(3,:))<=0) > 0
+ matRad_cfg.dispError('Ct slices not monotonically increasing');
+end
+if numel(unique([obj.importCT.ctInfo.Rows])) > 1 || numel(unique([obj.importCT.ctInfo.Columns])) > 1
+ matRad_cfg.dispError('Ct slice sizes inconsistent');
+end
+
+
+%% checking the patient position
+% As of now, the matRad treatment planning system is only valid for
+% patients in a supine position. Other orientations (e.g. prone, decubitus
+% left/right) are not supported.
+% Defined Terms:
+% HFP Head First-Prone (supported)
+% HFS Head First-Supine (supported)
+% HFDR Head First-Decubitus Right (not supported)
+% HFDL Head First-Decubitus Left (not supported)
+% FFDR Feet First-Decubitus Right (not supported)
+% FFDL Feet First-Decubitus Left (not supported)
+% FFP Feet First-Prone (supported)
+% FFS Feet First-Supine (supported)
+
+if isempty(regexp(obj.importCT.ctInfo(1).PatientPosition,{'S','P'}, 'once'))
+ matRad_cfg.dispError(['This Patient Position is not supported by matRad.'...
+ ' As of now only ''HFS'' (Head First-Supine), ''FFS'''...
+ ' (Feet First-Supine), '...
+ '''HFP'' (Head First-Prone), and ''FFP'''...
+ ' (Feet First-Prone) can be processed.'])
+end
+
+%% creation of ct-cube
+matRad_cfg.dispInfo('reading slices...')
+origCt = zeros(obj.importCT.ctInfo(1).Height, obj.importCT.ctInfo(1).Width, numOfSlices);
+for i = 1:numOfSlices
+ currentFilename = obj.importFiles.ct{i};
+ if matRad_cfg.isOctave
+ currentImage = dicomread(currentFilename);
+ map = [];
+ else
+ [currentImage, map] = dicomread(currentFilename);
+ end
+ origCt(:,:,i) = currentImage(:,:); % creation of the ct cube
+
+ % draw current ct-slice
+ if obj.visBool
+ if ~isempty(map)
+ image(ind2rgb(uint8(63*currentImage/max(currentImage(:))),map));
+ xlabel('x [voxelnumber]')
+ ylabel('y [voxelnumber]')
+ title(['Slice # ' int2str(i) ' of ' int2str(numOfSlices)])
+ else
+ image(ind2rgb(uint8(63*currentImage/max(currentImage(:))),bone));
+ xlabel('x [voxelnumber]')
+ ylabel('y [voxelnumber]')
+ title(['Slice # ' int2str(i) ' of ' int2str(numOfSlices)])
+ end
+ axis equal tight;
+ pause(0.1);
+ end
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(i,numOfSlices);
+ end
+end
+
+%% correction if not lps-coordinate-system
+% when using the physical coordinates (ctInfo.ImagePositionPatient) to
+% arrange the slices in z-direction, there is no more need for mirroring
+% in the z-direction
+matRad_cfg.dispInfo('\nz-coordinates taken from ImagePositionPatient\n')
+
+% The x- & y-direction in lps-coordinates are specified in:
+% ImageOrientationPatient
+xDir = obj.importCT.ctInfo(1).ImageOrientationPatient(1:3); % lps: [1;0;0]
+yDir = obj.importCT.ctInfo(1).ImageOrientationPatient(4:6); % lps: [0;1;0]
+nonStandardDirection = false;
+
+% correct x- & y-direction
+
+if xDir(1) == 1 && xDir(2) == 0 && xDir(3) == 0
+ matRad_cfg.dispInfo('x-direction OK\n')
+elseif xDir(1) == -1 && xDir(2) == 0 && xDir(3) == 0
+ matRad_cfg.dispInfo('\nMirroring x-direction...')
+ origCt = flip(origCt,1);
+ matRad_cfg.dispInfo('finished!\n')
+else
+ nonStandardDirection = true;
+end
+
+if yDir(1) == 0 && yDir(2) == 1 && yDir(3) == 0
+ matRad_cfg.dispInfo('y-direction OK\n')
+elseif yDir(1) == 0 && yDir(2) == -1 && yDir(3) == 0
+ matRad_cfg.dispInfo('\nMirroring y-direction...')
+ origCt = flip(origCt,2);
+ matRad_cfg.dispInfo('finished!\n')
+else
+ nonStandardDirection = true;
+end
+
+if nonStandardDirection
+ matRad_cfg.dispWarning(['Non-standard patient orientation.\n'...
+ 'CT might not fit to contoured structures\n'])
+end
+
+%% interpolate cube
+matRad_cfg.dispInfo('\nInterpolating CT cube...');
+if ~isempty(obj.ImportGrid)
+ obj.ct = matRad_interpDicomCtCube(origCt, obj.importCT.ctInfo, obj.importCT.resolution, obj.ImportGrid);
+else
+ obj.ct = matRad_interpDicomCtCube(origCt, obj.importCT.ctInfo, obj.importCT.resolution);
+end
+matRad_cfg.dispInfo('finished!\n');
+
+%% remember some parameters of original dicom
+tmp = [obj.importCT.ctInfo.ImagePositionPatient];
+
+obj.ct.dicomInfo.PixelSpacing = obj.importCT.ctInfo(1).PixelSpacing;
+obj.ct.dicomInfo.SlicePositions = tmp(3,:);
+obj.ct.dicomInfo.SliceThickness = str2double(obj.importCT.ctInfo(1).SliceThickness);
+obj.ct.dicomInfo.ImagePositionPatient = obj.importCT.ctInfo(1).ImagePositionPatient;
+obj.ct.dicomInfo.ImageOrientationPatient = obj.importCT.ctInfo(1).ImageOrientationPatient;
+obj.ct.dicomInfo.PatientPosition = obj.importCT.ctInfo(1).PatientPosition;
+obj.ct.dicomInfo.Width = obj.importCT.ctInfo(1).Width;
+obj.ct.dicomInfo.Height = obj.importCT.ctInfo(1).Height;
+obj.ct.dicomInfo.RescaleSlope = obj.importCT.ctInfo(1).RescaleSlope;
+obj.ct.dicomInfo.RescaleIntercept = obj.importCT.ctInfo(1).RescaleIntercept;
+if isfield(completeDicom, 'Manufacturer')
+obj.ct.dicomInfo.Manufacturer = completeDicom.Manufacturer;
+end
+if isfield(completeDicom, 'ManufacturerModelName')
+obj.ct.dicomInfo.ManufacturerModelName = completeDicom.ManufacturerModelName;
+end
+if isfield(completeDicom, 'ConvolutionKernel')
+obj.ct.dicomInfo.ConvolutionKernel = completeDicom.ConvolutionKernel;
+end
+
+% store patientName only if user wants to
+if isfield(completeDicom,'PatientName') && obj.dicomMetaBool == true
+ obj.ct.dicomInfo.PatientName = completeDicom.PatientName;
+end
+if obj.dicomMetaBool == true
+ obj.ct.dicomMeta = completeDicom;
+end
+
+obj.ct.timeStamp = datestr(clock);
+
+% convert to Hounsfield units
+matRad_cfg.dispInfo('\nconversion of ct-Cube to Hounsfield units...');
+obj = matRad_calcHU(obj);
+matRad_cfg.dispInfo('finished!\n');
+
+end
diff --git a/dicom/matRad_importDicomRTDose.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m
similarity index 64%
rename from dicom/matRad_importDicomRTDose.m
rename to matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m
index 22ff0d094..52672fab6 100644
--- a/dicom/matRad_importDicomRTDose.m
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m
@@ -1,19 +1,19 @@
-function [resultGUI] = matRad_importDicomRTDose(ct, rtDoseFiles, pln)
+function obj = matRad_importDicomRTDose(obj)
% matRad function to import dicom RTDOSE data
%
-% call
-% resultGUI = matRad_importDicomRTDose(ct, rtDoseFiles)
-% resultGUI = matRad_importDicomRTDose(ct, rtDoseFiles, pln)
+% In your object, there must be properties that contain:
+% - ct imported by the matRad_importDicomCt function;
+% - cell array of RTDose DICOM files.
+% Optional:
+% - matRad pln structure.
+%
+% Output - matRad resultGUI structure with different beams.
+% Note that the summation (called plan) of the beams is named without
+% subscripts, e.g. physical_Dose.
%
-% input
-% ct: ct imported by the matRad_importDicomCt function
-% rtDoseFiles: cell array of RTDOSE Dicom files
-% pln: (optional) matRad pln struct
+% call
+% obj = matRad_importDicomRTDose(obj)
%
-% output
-% resultGUI: matRad resultGUI struct with different beams. Note that
-% the summation (called plan) of the beams is named
-% without subscripts, e.g. physical_Dose.
%
% References
% -
@@ -24,7 +24,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -35,18 +35,22 @@
%% import and interpolate dose files
% number of dosefiles
-numDoseFiles = size(rtDoseFiles,1);
+numDoseFiles = size(obj.importFiles.rtdose,1);
for i = 1 : numDoseFiles
- currDose = rtDoseFiles(i,:);
+ obj.importRTDose.currDose = obj.importFiles.rtdose(i,:);
itemName = strcat('Item_',num2str(i));
- dose.(itemName) = matRad_interpDicomDoseCube( ct, currDose);
+ obj = matRad_interpDicomDoseCube(obj);
+ dose.(itemName) = obj.importRTDose.dose;
end
%% put dose information and dose meta information to resultGUI
countBeamNumberPhysDose = 1;
countBeamNumberRBExDose = 1;
countBeamNumberOther = 1;
+
+obj.resultGUI = struct();
+
for i = 1 : numDoseFiles
itemName = strcat('Item_',num2str(i));
doseTypeHelper = dose.(itemName).dicomInfo.DoseType;
@@ -67,8 +71,8 @@
%If given as plan and not per fraction
if strcmpi(doseSumHelper,'PLAN') || strcmpi(doseSumHelper,'BEAM')
- if exist('pln','var')
- dose.(itemName).cube = dose.(itemName).cube / pln.numOfFractions;
+ if ~isempty(obj.pln)
+ dose.(itemName).cube = dose.(itemName).cube / obj.pln.numOfFractions;
else
matRad_cfg.dispWarning('DICOM dose given as PLAN, but no pln struct available to compute fraction dose! Assuming 1 fraction!');
end
@@ -100,15 +104,29 @@
instanceSuffix = ['_' num2str(doseInstanceHelper)];
else
instanceSuffix = '';
- end
-
+ end
resultName = strcat(doseTypeHelper,instanceSuffix,beamSuffix);
+
+
+ if isfield(obj.resultGUI,resultName)
+ count = 1;
+ addSuffix = ['_' num2str(count)];
+ resultNameNew = [resultName addSuffix];
+ while isfield(obj.resultGUI,resultNameNew)
+ count = count + 1;
+ addSuffix = ['_' num2str(count)];
+ resultNameNew = [resultName addSuffix];
+ end
+
+ matRad_cfg.dispWarning('Already imported dose ''%s'', naming the new dose ''%s'', manually organize resultGUI afterwards for duplicates!',resultName, resultNameNew);
+ resultName = resultNameNew;
+ end
- resultGUI.(resultName) = dose.(itemName).cube;
- resultGUI.doseMetaInfo.(resultName) = dose.(itemName).dicomInfo;
+ obj.resultGUI.(resultName) = dose.(itemName).cube;
+ obj.resultGUI.doseMetaInfo.(resultName) = dose.(itemName).dicomInfo;
end
% save timeStamp
-resultGUI.doseMetaInfo.timeStamp = datestr(clock);
+obj.resultGUI.doseMetaInfo.timeStamp = datestr(clock);
end
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m
new file mode 100644
index 000000000..4445ca52e
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m
@@ -0,0 +1,190 @@
+function obj = matRad_importDicomRTPlan(obj)
+% matRad function to import dicom RTPLAN data
+%
+% In your object, there must be properties that contain:
+% - ct imported by the matRad_importDicomCt function;
+% - list of RTPlan Dicom files;
+% - a boolean, if you don't want to import whole dicom information set it
+% false.
+%
+% Output - matRad pln structure with meta information.
+% Note that bixelWidth is determined via the importSteering function.
+%
+% call
+% obj = matRad_importDicomRTPlan(obj)
+%
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+matRad_checkEnvDicomRequirements(matRad_cfg.env);
+
+%% load plan file
+% check size of RT Plan
+if size(obj.importFiles.rtplan,1) ~= 1
+ errordlg('Too few or to many RTPlan files')
+end
+
+% read information out of the RT file
+if matRad_cfg.isOctave || verLessThan('matlab','9')
+ planInfo = dicominfo(obj.importFiles.rtplan{1});
+else
+ planInfo = dicominfo(obj.importFiles.rtplan{1},'UseDictionaryVR',true);
+end
+
+% check which type of Radiation is used
+if isfield(planInfo, 'BeamSequence')
+ BeamParam = 'BeamSequence';
+ ControlParam = 'ControlPointSequence';
+elseif isfield(planInfo, 'IonBeamSequence')
+ BeamParam = 'IonBeamSequence';
+ ControlParam = 'IonControlPointSequence';
+else
+ errordlg('Not supported kind of DICOM RT plan file.');
+end
+
+% get beam sequence
+BeamSequence = planInfo.(BeamParam);
+BeamSeqNames = fieldnames(BeamSequence);
+
+% use the treatment beams only
+if isfield(BeamSequence.(BeamSeqNames{1}),'TreatmentDeliveryType')
+ for i = 1:length(BeamSeqNames)
+ currBeamSeq = BeamSequence.(BeamSeqNames{i});
+ try
+ treatDelType = currBeamSeq.TreatmentDeliveryType;
+ if ~strcmpi(treatDelType,'TREATMENT')
+ BeamSequence = rmfield(BeamSequence,BeamSeqNames{i});
+ end
+ catch
+ warning('Something went wrong while determining the type of the beam.');
+ end
+ end
+ BeamSeqNames = fieldnames(BeamSequence);
+end
+
+
+%% get information may change between beams
+% loop over beams
+gantryAngles{length(BeamSeqNames)} = [];
+PatientSupportAngle{length(BeamSeqNames)} = [];
+isoCenter = NaN*ones(length(BeamSeqNames),3);
+for i = 1:length(BeamSeqNames)
+ currBeamSeq = BeamSequence.(BeamSeqNames{i});
+ % parameters not changing are stored in the first ControlPointSequence
+ gantryAngles{i} = currBeamSeq.(ControlParam).Item_1.GantryAngle;
+ PatientSupportAngle{i} = currBeamSeq.(ControlParam).Item_1.PatientSupportAngle;
+ isoCenter(i,:) = currBeamSeq.(ControlParam).Item_1.IsocenterPosition';
+ if ~ismember(isoCenter(i,1), obj.ct.x) || ~ismember(isoCenter(i,2), obj.ct.y) || ~ismember(isoCenter(i,3), obj.ct.z)
+ isoCenter(i,:) = matRad_getIsoCenter(obj.cst, obj.ct);
+ end
+end
+
+% transform iso. At the moment just this way for HFS
+if obj.ct.dicomInfo.ImageOrientationPatient ~= [1;0;0;0;1;0]
+ matRad_cfg.dispError('This Orientation is not yet supported.');
+end
+
+%% read constant parameters
+% readout charge and mass to set radiationMode to matRad specific name
+radiationMode = planInfo.(BeamParam).Item_1.RadiationType;
+if ~strncmpi(radiationMode,'photons',6)
+ try
+ radiationMass = planInfo.(BeamParam).Item_1.RadiationMassNumber;
+ radiationAtomicNumber = planInfo.(BeamParam).Item_1.RadiationAtomicNumber;
+ catch
+ matRad_cfg.dispWarning('Could not determine mass and atomic number of the particle');
+ end
+end
+
+if strncmpi(radiationMode,'photons',6)
+ radiationMode = 'photons';
+elseif strncmpi(radiationMode,'proton',6)
+ radiationMode = 'protons';
+elseif (strncmpi(radiationMode,'ion',3) && radiationMass == 12 && radiationAtomicNumber == 6)
+ radiationMode = 'carbon';
+else
+ matRad_cfg.dispError('The given type of radiation is not yet supported');
+end
+
+% extract field shapes
+if strcmp(radiationMode, 'photons')
+
+ fractionSequence = planInfo.FractionGroupSequence.Item_1;
+ obj.pln.propStf.collimation = matRad_importFieldShapes(BeamSequence,fractionSequence);
+
+end
+
+%% write parameters found to pln variable
+obj.pln.radiationMode = radiationMode; % either photons / protons / carbon
+obj.pln.numOfFractions = planInfo.FractionGroupSequence.Item_1.NumberOfFractionsPlanned;
+
+% set handling of multiple scenarios -> default: only nominal
+obj.pln.multScen = matRad_multScen(obj.ct,'nomScen');
+if isfield(BeamSequence.Item_1, 'TreatmentMachineName')
+ obj.pln.machine = BeamSequence.Item_1.TreatmentMachineName;
+else
+ obj.pln.machine = 'Generic';
+end
+% set bio model parameters (default physical opt, no bio model)
+obj.pln.bioParam = matRad_bioModel(obj.pln.radiationMode,'physicalDose','none');
+
+% set properties for steering
+obj.pln.propStf.isoCenter = isoCenter;
+obj.pln.propStf.bixelWidth = NaN; % [mm] / also corresponds to lateral spot spacing for particles
+obj.pln.propStf.gantryAngles = [gantryAngles{1:length(BeamSeqNames)}];
+obj.pln.propStf.couchAngles = [PatientSupportAngle{1:length(BeamSeqNames)}]; % [??]
+obj.pln.propStf.numOfBeams = length(BeamSeqNames);
+numOfVoxels = 1;
+for i = 1:length(obj.ct.cubeDim)
+ numOfVoxels = numOfVoxels*obj.ct.cubeDim(i);
+end
+obj.pln.numOfVoxels = numOfVoxels;
+obj.pln.VoxelDimentions = obj.ct.cubeDim;
+
+%if there is not special doseGrid for rtdose
+if ~obj.importFiles.useImportGrid && isfield(obj.importFiles,'rtdose')
+ obj.pln.propDoseCalc.doseGrid.resolution.x = obj.ct.resolution.x;
+ obj.pln.propDoseCalc.doseGrid.resolution.y = obj.ct.resolution.y;
+ obj.pln.propDoseCalc.doseGrid.resolution.z = obj.ct.resolution.z;
+end
+
+% turn off sequerncing an DAO by default
+obj.pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below
+obj.pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles
+
+% if we imported field shapes then let's trigger field based dose calc by
+% setting the bixelWidth to 'field'
+if isfield(obj.pln.propStf,'collimation')
+ obj.pln.propStf.bixelWidth = 'field';
+end
+
+% timestamp
+obj.pln.DicomInfo.timeStamp = datestr(clock);
+
+try
+ obj.pln.DicomInfo.SOPClassUID = planInfo.SOPClassUID;
+ obj.pln.DicomInfo.SOPInstanceUID = planInfo.SOPInstanceUID;
+ obj.pln.DicomInfo.ReferencedDoseSequence = planInfo.ReferencedDoseSequence;
+catch
+end
+
+% safe entire dicomInfo
+if obj.dicomMetaBool == true
+ obj.pln.DicomInfo.Meta = planInfo;
+end
+end
diff --git a/dicom/matRad_importDicomRtss.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m
similarity index 54%
rename from dicom/matRad_importDicomRtss.m
rename to matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m
index 421e33568..b70bcea79 100644
--- a/dicom/matRad_importDicomRtss.m
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m
@@ -1,20 +1,19 @@
-function structures = matRad_importDicomRtss(filename,dicomInfo,visBool)
-% matRad function to read the data of the selected dicomRT structure set file
-% into a matlab struct
+function obj = matRad_importDicomRtss(obj)
+% matRad function to read the data of the selected dicomRT structure set
+% file into a matRad structure
%
-% call
-% structures = matRad_importDicomRtss(filename,dicomInfo)
-% structures = matRad_importDicomRtss(filename,dicomInfo,visBool)
+% In your object, there must be properties that contain:
+% - name of the rtss file;
+% - meta information from the dicom ct files for sanity checks.
+% Optional:
+% - boolean to turn on/off visualization.
+%
+% Output - structure containing names, numbers, colors and coordinates
+% of the polygon segmentations.
%
-% input
-% filename: name of the rtss file
-% dicomInfo: meta information from the dicom ct files for sanity
-% checks
-% visBool: (optional) turn on/off visualization
+% call
+% obj = matRad_importDicomRtss(obj)
%
-% output
-% structures: struct containing names, numbers, colors, and
-% coordinates of the polygon segmentations
%
% References
% -
@@ -25,31 +24,45 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-fprintf('\nReading structures...');
+matRad_cfg = MatRad_Config.instance();
+
+matRad_cfg.dispInfo('\nReading structures...');
if nargin < 3
- visBool = 0;
+ obj.visBool = 0;
end
+matRad_checkEnvDicomRequirements(matRad_cfg.env);
+
+
+
% read dicom info (this includes already all data for the rtss)
-if verLessThan('matlab','9')
- structInfo = dicominfo(filename);
+if matRad_cfg.isOctave || verLessThan('matlab','9')
+ structInfo = dicominfo(obj.importFiles.rtss{1});
else % apply 'UseVRHeuristic' option when available to use a to help read certain
% noncompliant files which switch value representation (VR) modes incorrectly
- structInfo = dicominfo(filename,'UseVRHeuristic',false,'UseDictionaryVR',true);
+ structInfo = dicominfo(obj.importFiles.rtss{1},'UseVRHeuristic',false,'UseDictionaryVR',true);
end
% list the defined structures
-listOfDefStructs = fieldnames(structInfo.StructureSetROISequence);
+try
+ listOfDefStructs = fieldnames(structInfo.StructureSetROISequence);
+catch
+ matRad_cfg.dispError('StructureSetROISequence not defined ')
+end
% list of contoured structures
-listOfContStructs = fieldnames(structInfo.ROIContourSequence);
+try
+ listOfContStructs = fieldnames(structInfo.ROIContourSequence);
+catch
+ matRad_cfg.dispError('ROIContourSequence not defined ')
+end
%% process structure data
numOfDefStructs = numel(listOfDefStructs);
@@ -64,13 +77,14 @@
break;
end
end
- structures(i).structName = structInfo.StructureSetROISequence.(...
- listOfDefStructs{j}).ROIName;
+ obj.importRtss.structures(i).structName = regexprep(... % replace nonregular characters by whitespace
+ structInfo.StructureSetROISequence.(listOfDefStructs{j}).ROIName,...
+ '[^a-zA-Z0-9]',' ');
- structures(i).structNumber = structInfo.ROIContourSequence.(...
+ obj.importRtss.structures(i).structNumber = structInfo.ROIContourSequence.(...
listOfContStructs{i}).ReferencedROINumber;
if isfield(structInfo.ROIContourSequence.(listOfContStructs{i}),'ROIDisplayColor')
- structures(i).structColor = structInfo.ROIContourSequence.(...
+ obj.importRtss.structures(i).structColor = structInfo.ROIContourSequence.(...
listOfContStructs{i}).ROIDisplayColor;
end
@@ -81,11 +95,11 @@
listOfSlices = fieldnames(structInfo.ROIContourSequence.(...
listOfContStructs{i}).ContourSequence);
else
- warning(['Contour ' structures(i).structName ' is empty'])
+ matRad_cfg.dispWarning(['Contour ' obj.importRtss.structures(i).structName ' is empty'])
continue;
end
else
- warning(['Contour ' structures(i).structName ' is empty'])
+ matRad_cfg.dispWarning(['Contour ' obj.importRtss.structures(i).structName ' is empty'])
continue;
end
@@ -108,14 +122,14 @@
% sanity check 1
if numel(unique(structZ)) > 1
- error('Detected contour points outside of single slice\n');
+ matRad_cfg.dispError('Detected contour points outside of single slice\n');
end
% sanity check 2
- if unique(structZ) > max(dicomInfo.SlicePositions) || unique(structZ) < min(dicomInfo.SlicePositions)
- warning(['Omitting contour data for ' structures(i).structName ' at slice position ' num2str(unique(structZ)) 'mm - no ct data available.\n']);
+ if unique(structZ) > max(obj.ct.dicomInfo.SlicePositions) || unique(structZ) < min(obj.ct.dicomInfo.SlicePositions)
+ matRad_cfg.dispWarning(['Omitting contour data for ' obj.importRtss.structures(i).structName ' at slice position ' num2str(unique(structZ)) 'mm - no ct data available.\n']);
else
- structures(i).item(j).points = [structX, structY, structZ];
+ obj.importRtss.structures(i).item(j).points = [structX, structY, structZ];
end
end
@@ -124,18 +138,18 @@
%% visualization
% show all structure points in a single plot
-if visBool
+if obj.visBool
figure;
hold on
- for i = 1:numel(structures)
- plot3(structures(i).points(:,1),structures(i).points(:,2),...
- structures(i).points(:,3),'-',...
- 'Color',structures(i).structColor ./ 255,'Displayname',structures(i).structName);
+ for i = 1:numel(obj.importRtss.structures)
+ plot3(obj.importRtss.structures(i).points(:,1),obj.importRtss.structures(i).points(:,2),...
+ obj.importRtss.structures(i).points(:,3),'-',...
+ 'Color',obj.importRtss.structures(i).structColor ./ 255,'Displayname',obj.importRtss.structures(i).structName);
end
legend('show')
end
-fprintf('finished!\n');
+matRad_cfg.dispInfo('finished!\n');
end
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m
new file mode 100644
index 000000000..fa3ed34b8
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m
@@ -0,0 +1,353 @@
+function obj = matRad_importDicomSteeringParticles(obj)
+% matRad function to import a matRad stf struct from dicom RTPLAN data
+%
+% In your object, there must be properties that contain:
+% - ct imported by the matRad_importDicomCt function;
+% - matRad pln structure with meta information;
+% - name of RTPLAN DICOM file.
+%
+% Output - matRad stf and pln structures.
+% Note: pln is input and output since pln.bixelWidth is determined here.
+%
+% call
+% obj = matRad_importDicomSteeringParticles(obj)
+%
+%
+% References
+% -
+%
+% Note
+% not implemented - compensator. Fixed SAD.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+%% load plan file
+% load machine data
+
+matRad_cfg = MatRad_Config.instance();
+matRad_checkEnvDicomRequirements(matRad_cfg.env);
+
+dlgBaseDataText = ['Import steering information from DICOM Plan.','Choose corresponding matRad base data for ', ...
+ obj.pln.radiationMode, '.'];
+% messagebox only necessary for non windows users
+if ~ispc
+ uiwait(helpdlg(dlgBaseDataText,['DICOM import - ', obj.pln.radiationMode, ' base data' ]));
+end
+[fileName,pathName] = uigetfile([matRad_cfg.matRadSrcRoot filesep 'basedata' filesep '*.mat'], dlgBaseDataText);
+load([pathName filesep fileName]);
+
+ix = find(fileName == '_');
+obj.pln.machine = fileName(ix(1)+1:end-4);
+
+% RT Plan consists only on meta information
+if matRad_cfg.isOctave || verLessThan('matlab','9')
+ rtPlanInfo = dicominfo(obj.importFiles.rtplan{1});
+else
+ rtPlanInfo = dicominfo(obj.importFiles.rtplan{1},'UseDictionaryVR',true);
+end
+BeamSeq = rtPlanInfo.IonBeamSequence;
+BeamSeqNames = fieldnames(BeamSeq);
+% Number of Beams from plan
+numOfBeamsPlan = length(obj.pln.propStf.gantryAngles);
+
+% use only the treatment beams
+for i = 1:length(BeamSeqNames)
+ currBeamSeq = BeamSeq.(BeamSeqNames{i});
+ try
+ treatDelType = currBeamSeq.TreatmentDeliveryType;
+ if ~strcmpi(treatDelType,'TREATMENT')
+ BeamSeq = rmfield(BeamSeq,BeamSeqNames{i});
+ end
+ catch
+ warning('Something went wrong while determining the type of the beam.');
+ end
+end
+
+% reinitialize the BeamSeqNames and length, as the Seq itself is reduced.
+BeamSeqNames = fieldnames(BeamSeq);
+
+% remove empty ControlPointSequences
+for i = 1:length(BeamSeqNames)
+ currBeamSeq = BeamSeq.(BeamSeqNames{i});
+ ControlPointSeq = currBeamSeq.IonControlPointSequence;
+ ControlPointSeqNames = fieldnames(ControlPointSeq);
+ numOfContrPointSeq = length(ControlPointSeqNames);
+ for currContr = 1:numOfContrPointSeq
+ currContrSeq = ControlPointSeq.(ControlPointSeqNames{currContr});
+ if sum(currContrSeq.ScanSpotMetersetWeights) == 0
+ ControlPointSeq = rmfield(ControlPointSeq,ControlPointSeqNames{currContr});
+ end
+ end
+ BeamSeq.(BeamSeqNames{i}).IonControlPointSequence = ControlPointSeq;
+end
+
+% check if number of beams correspond
+if ~isequal(length(BeamSeqNames),numOfBeamsPlan)
+ warning('Number of beams from beamsequences do not correspond to number of Gantry Angles');
+end
+
+%% generate stf struct
+% surfaceEntry = BeamSeq.Item_1.IonControlPointSequence.Item_1.SurfaceEntryPoint;
+
+% Preallocate stf
+obj.stf(length(BeamSeqNames)).gantryAngle = [];
+obj.stf(length(BeamSeqNames)).couchAngle = [];
+obj.stf(length(BeamSeqNames)).bixelWidth = [];
+obj.stf(length(BeamSeqNames)).radiationMode = [];
+obj.stf(length(BeamSeqNames)).SAD = [];
+obj.stf(length(BeamSeqNames)).isoCenter = [];
+obj.stf(length(BeamSeqNames)).sourcePoint_bev = [];
+obj.stf(length(BeamSeqNames)).numOfRays = [];
+obj.stf(length(BeamSeqNames)).numOfBixelsPerRay = [];
+obj.stf(length(BeamSeqNames)).totalNumOfBixels = [];
+obj.stf(length(BeamSeqNames)).ray = [];
+
+
+
+for i = 1:length(BeamSeqNames)
+ currBeamSeq = BeamSeq.(BeamSeqNames{i});
+ ControlPointSeq = currBeamSeq.IonControlPointSequence;
+ obj.stf(i).gantryAngle = obj.pln.propStf.gantryAngles(i);
+ obj.stf(i).couchAngle = obj.pln.propStf.couchAngles(i);
+ obj.stf(i).bixelWidth = obj.pln.propStf.bixelWidth;
+ obj.stf(i).radiationMode = obj.pln.radiationMode;
+ % there might be several SAD's, e.g. compensator?
+ obj.stf(i).SAD_x = currBeamSeq.VirtualSourceAxisDistances(1);
+ obj.stf(i).SAD_y = currBeamSeq.VirtualSourceAxisDistances(2);
+ %stf(i).SAD = machine.meta.SAD; %we write the SAD later when we check machine match
+ %stf(i).sourcePoint_bev = [0 -stf(i).SAD 0];
+ obj.stf(i).isoCenter = obj.pln.propStf.isoCenter(i,:);
+
+ % now loop over ControlPointSequences
+ ControlPointSeqNames = fieldnames(ControlPointSeq);
+ numOfContrPointSeq = length(ControlPointSeqNames);
+ % create empty helper matrix
+ temporarySteering = zeros(0,8);
+ for currContr = 1:numOfContrPointSeq
+ currContrSeq = ControlPointSeq.(ControlPointSeqNames{currContr});
+ % get energy, equal for all coming elements in the next loop
+ currEnergy = currContrSeq.NominalBeamEnergy;
+ % get focusValue
+ currFocus = unique(currContrSeq.ScanningSpotSize);
+ % get the Spotpositions
+ numOfScanSpots = currContrSeq.NumberOfScanSpotPositions;
+ % x is 1, 3, 5 ...; y 2, 4, 6,
+ c1_help = currContrSeq.ScanSpotPositionMap(1:2:(2 * numOfScanSpots));
+ c2_help = currContrSeq.ScanSpotPositionMap(2:2:(2 * numOfScanSpots));
+ weight_help = currContrSeq.ScanSpotMetersetWeights;
+ if isfield(currContrSeq, 'RangeShifterSettingsSequence')
+ % rangeshifter identification
+ rashiID = currContrSeq.RangeShifterSettingsSequence.Item_1.ReferencedRangeShifterNumber;
+ % rangeshifter waterequivalent thickness
+ rashiWeThickness = currContrSeq.RangeShifterSettingsSequence.Item_1.RangeShifterWaterEquivalentThickness;
+ % rangeshifter isocenter to range shifter distance
+ rashiIsoRangeDist = currContrSeq.RangeShifterSettingsSequence.Item_1.IsocenterToRangeShifterDistance;
+ elseif currContr == 1
+ rashiID = 0;
+ rashiWeThickness = 0;
+ rashiIsoRangeDist = 0;
+ else
+ % in this case range shifter settings has not changed between this
+ % and previous control sequence, so reuse values.
+ end
+ temporarySteering = [temporarySteering; c1_help c2_help ...
+ (currEnergy * ones(numOfScanSpots,1)) weight_help (currFocus * ones(numOfScanSpots,1)) ...
+ (rashiID * ones(numOfScanSpots,1)) (rashiWeThickness * ones(numOfScanSpots,1)) (rashiIsoRangeDist * ones(numOfScanSpots,1))];
+ end
+
+ % finds all unique rays and saves them in to the stf
+ [RayPosTmp, ~, ic] = unique(temporarySteering(:,1:2), 'rows');
+ clear ray;
+ for j = 1:size(RayPosTmp,1)
+ obj.stf(i).ray(j).rayPos_bev = double([RayPosTmp(j,1) 0 RayPosTmp(j,2)]);
+ obj.stf(i).ray(j).energy = [];
+ obj.stf(i).ray(j).focusFWHM = [];
+ obj.stf(i).ray(j).focusIx = [];
+ obj.stf(i).ray(j).weight = [];
+ obj.stf(i).ray(j).rangeShifter = struct();
+ ray(j).ID = [];
+ ray(j).eqThickness = [];
+ ray(j).sourceRashiDistance = [];
+ end
+
+ % saves all energies and weights to their corresponding ray
+ for j = 1:size(temporarySteering,1)
+ k = ic(j);
+ obj.stf(i).ray(k).energy = [obj.stf(i).ray(k).energy double(temporarySteering(j,3))];
+ obj.stf(i).ray(k).focusFWHM = [obj.stf(i).ray(k).focusFWHM double(temporarySteering(j,5))];
+ obj.stf(i).ray(k).weight = [obj.stf(i).ray(k).weight double(temporarySteering(j,4)) / 1e6];
+ % helpers to construct something like a(:).b = c.b(:) after this
+ % loop
+ ray(k).ID = [ray(k).ID double(temporarySteering(j,6))];
+ ray(k).eqThickness = [ray(k).eqThickness double(temporarySteering(j,7))];
+ ray(k).sourceRashiDistance = [ray(k).sourceRashiDistance double(temporarySteering(j,8))];
+ end
+
+ % reassign to preserve data structure
+ for j = 1:numel(ray)
+ for k = 1:numel(ray(j).ID)
+ obj.stf(i).ray(j).rangeShifter(k).ID = ray(j).ID(k);
+ obj.stf(i).ray(j).rangeShifter(k).eqThickness = ray(j).eqThickness(k);
+ obj.stf(i).ray(j).rangeShifter(k).sourceRashiDistance = obj.stf(i).SAD - ray(j).sourceRashiDistance(k);
+ end
+ end
+
+
+ % getting some information of the rays
+ % clean up energies, so they appear only one time per energy
+ numOfRays = size(obj.stf(i).ray,2);
+ for l = 1:numOfRays
+ obj.stf(i).ray(l).energy = unique(obj.stf(i).ray(l).energy);
+ end
+ obj.stf(i).numOfRays = numel(obj.stf(i).ray);
+
+ % save total number of bixels
+ numOfBixels = 0;
+ for j = 1:numel(obj.stf(i).ray)
+ numOfBixels = numOfBixels + numel(obj.stf(i).ray(j).energy);
+ obj.stf(i).numOfBixelsPerRay(j) = numel(obj.stf(i).ray(j).energy);
+% w = [w stf(currBeam).ray(j).weight];
+ end
+
+ obj.stf(i).totalNumOfBixels = numOfBixels;
+
+ % get bixelwidth
+ bixelWidth_help = zeros(size(obj.stf(i).ray,2),2);
+ for j = 1:obj.stf(i).numOfRays
+ bixelWidth_help(j,1) = obj.stf(i).ray(j).rayPos_bev(1);
+ bixelWidth_help(j,2) = obj.stf(i).ray(j).rayPos_bev(3);
+ end
+ bixelWidth_help1 = unique(round(1e3*bixelWidth_help(:,1))/1e3,'sorted');
+ bixelWidth_help2 = unique(round(1e3*bixelWidth_help(:,2))/1e3,'sorted');
+
+ bixelWidth = unique([unique(diff(bixelWidth_help1))' unique(diff(bixelWidth_help2))']);
+
+ if numel(bixelWidth) == 1
+ obj.stf(i).bixelWidth = bixelWidth;
+ else
+ obj.stf(i).bixelWidth = NaN;
+ end
+
+end
+
+%% check if matching given machine
+% if a machine is given, check if we can exactly match the plan with
+% the machine details
+machineNotMatching = false;
+
+if ~isempty(obj.pln.machine)
+ matRad_cfg.dispInfo('Machine provided! Checking for exact steering match within RTPlan...');
+ for i = 1:numel(obj.stf)
+ for j = 1:obj.stf(i).numOfRays
+ % loop over all energies
+ numOfEnergy = length(obj.stf(i).ray(j).energy);
+ for k = 1:numOfEnergy
+ energyTemp = obj.stf(i).ray(j).energy(k);
+ focusFWHM = obj.stf(i).ray(j).focusFWHM(k);
+ energyIndex = find(abs([machine.data(:).energy]-energyTemp)<10^-2);
+ if isempty(energyIndex)
+ machineNotMatching = true;
+ break;
+ end
+ focusIndex = find(abs([machine.data(energyIndex).initFocus.SisFWHMAtIso] - focusFWHM )< 10^-3);
+ if isempty(focusIndex)
+ machineNotMatching = true;
+ break;
+ end
+ end
+
+ if machineNotMatching
+ break;
+ end
+ end
+ if machineNotMatching
+ break;
+ end
+ end
+
+ %If the machine matches, format the stf for direct use. Otherwise,
+ %leave it be
+ if machineNotMatching
+ matRad_cfg.dispInfo('not matching!\n');
+ matRad_cfg.dispWarning('The given machine does not match the steering info found in RTPlan. matRad will generate an stf, but it will be incompatible with the given machine and most likely not directly be usable in dose calculation!');
+
+ for i = 1:numel(obj.stf)
+ obj.stf(i).SAD = mean([obj.stf(i).SAD_x obj.stf(i).SAD_y]);
+ end
+ else
+ matRad_cfg.dispInfo('matching!\n');
+ matRad_cfg.dispInfo('Formatting stf for use with given machine...');
+
+ for i = 1:numel(obj.stf)
+ obj.stf(i).SAD = machine.meta.SAD;
+ for j = 1:obj.stf(i).numOfRays
+
+ % loop over all energies
+ numOfEnergy = length(obj.stf(i).ray(j).energy);
+ for k = 1:numOfEnergy
+ %If a corresponding machine was found, check assignment here
+ if ~isempty(machine)
+ energyTemp = obj.stf(i).ray(j).energy(k);
+ focusFWHM = obj.stf(i).ray(j).focusFWHM(k);
+ energyIndex = find(abs([machine.data(:).energy]-energyTemp)<10^-2);
+ focusIndex = find(abs([machine.data(energyIndex).initFocus.SisFWHMAtIso] - focusFWHM )< 10^-3);
+
+ obj.stf(i).ray(j).energy(k) = machine.data(energyIndex).energy;
+ obj.stf(i).ray(j).focusIx(k) = focusIndex;
+ obj.stf(i).ray(j).focusFWHM(k) = machine.data(energyIndex).initFocus.SisFWHMAtIso(obj.stf(i).ray(j).focusIx(k));
+ end
+ end
+ end
+
+
+ end
+ end
+end
+
+%% Finalize geometry
+for i = 1:numel(obj.stf)
+ % coordinate transformation with rotation matrix.
+ % use transpose matrix because we are working with row vectors
+ rotMat_vectors_T = transpose(matRad_getRotationMatrix(obj.stf(i).gantryAngle,obj.stf(i).couchAngle));
+
+ % set source point using (average/machine) SAD
+ obj.stf(i).sourcePoint_bev = [0 -obj.stf(i).SAD 0];
+
+ % Rotated Source point (1st gantry, 2nd couch)
+ obj.stf(i).sourcePoint = obj.stf(i).sourcePoint_bev*rotMat_vectors_T;
+
+ % Save ray and target position in lps system.
+ for j = 1:obj.stf(i).numOfRays
+ obj.stf(i).ray(j).targetPoint_bev = [2*obj.stf(i).ray(j).rayPos_bev(1) obj.stf(i).SAD 2*obj.stf(i).ray(j).rayPos_bev(3)];
+ obj.stf(i).ray(j).rayPos = obj.stf(i).ray(j).rayPos_bev*rotMat_vectors_T;
+ obj.stf(i).ray(j).targetPoint = obj.stf(i).ray(j).targetPoint_bev*rotMat_vectors_T;
+ end
+
+ % book keeping & calculate focus index
+ for j = 1:obj.stf(i).numOfRays
+ obj.stf(i).numOfBixelsPerRay(j) = numel([obj.stf(i).ray(j).energy]);
+ end
+
+ obj.stf(i).timeStamp = datetime('now');
+end
+
+if any(isnan([obj.stf(:).bixelWidth])) || numel(unique([obj.stf(:).bixelWidth])) > 1
+ obj.pln.propStf.bixelWidth = NaN;
+else
+ obj.pln.propStf.bixelWidth = obj.stf(1).bixelWidth;
+end
+
+end
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m
new file mode 100644
index 000000000..c591699f2
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m
@@ -0,0 +1,96 @@
+function obj = matRad_importDicomSteeringPhotons(obj)
+% matRad function to import a matRad stf struct from dicom RTPLAN data
+%
+% In your object, there must be a property that contains matRad pln
+% structure with meta information (collimation data included)
+%
+% Output - matRad stf and pln structures.
+%
+% call
+% obj = matRad_importDicomSteeringPhotons(obj)
+%
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+obj.stf = struct;
+if ~isfield(obj.pln.propStf.collimation,'Fields')
+ return
+end
+
+% get fields possessing a field weight vector greater than 0
+Fields = obj.pln.propStf.collimation.Fields([obj.pln.propStf.collimation.Fields(:).Weight] > 0);
+
+[UniqueComb,ia,ib] = unique( vertcat([Fields(:).GantryAngle], [Fields(:).CouchAngle])','rows');
+
+% return corret angles to pln, because some angle derivations might be
+% only in the control point sequences
+obj.pln.propStf.gantryAngles = UniqueComb(:,1)';
+obj.pln.propStf.couchAngles = UniqueComb(:,2)';
+
+obj.stf = struct;
+% loop over all fields
+for i = 1:size(UniqueComb,1)
+ % set necessary steering information
+ obj.stf(i).gantryAngle = UniqueComb(i,1);
+ obj.stf(i).couchAngle = UniqueComb(i,2);
+ obj.stf(i).isoCenter = obj.pln.propStf.isoCenter(i,:);
+
+ % bixelWidth = 'field' as keyword for whole field dose calc
+ obj.stf(i).bixelWidth = 'field';
+ obj.stf(i).radiationMode = 'photons';
+
+ % only one bixel per ray and one ray for photon dose calc based on
+ % fields
+ obj.stf(i).numOfBixelsPerRay = 1;
+ obj.stf(i).numOfRays = 1;
+ obj.stf(i).totalNumOfBixels = obj.stf(i).numOfRays;
+ obj.stf(i).SAD = Fields(ia(i)).SAD;
+ obj.stf(i).sourcePoint_bev = [0 -obj.stf(i).SAD 0];
+
+ % coordinate transformation with rotation matrix.
+ % use transpose matrix because we are working with row vectors
+ rotMat_vectors_T = transpose(matRad_getRotationMatrix(obj.stf(i).gantryAngle,obj.stf(i).couchAngle));
+
+
+ % Rotated Source point (1st gantry, 2nd couch)
+ obj.stf(i).sourcePoint = obj.stf(i).sourcePoint_bev*rotMat_vectors_T;
+
+ % only one ray in center position
+ obj.stf(i).ray.rayPos_bev = [0 0 0];
+ obj.stf(i).ray.rayPos = obj.stf(i).ray.rayPos_bev*rotMat_vectors_T;
+
+ % target point is for ray in center position at
+ obj.stf(i).ray.targetPoint_bev = [0 obj.stf(i).SAD 0];
+ obj.stf(i).ray.targetPoint = obj.stf(i).ray.targetPoint_bev*rotMat_vectors_T;
+
+ % set weight for output field
+ obj.stf(i).ray.weight = 1; % weighting incorporated into primary fluence --> finalShape
+ %obj.stf(i).ray.SSD = Fields(ia(i)).SSD;
+ obj.stf(i).ray.energy = Fields(ia(i)).Energy;
+
+ ix = (ib == i);
+ currFieldSeq = Fields(ix);
+
+ % add weighted shapes for the current beam
+ finalShape = 0;
+ for j = 1:sum(ix)
+ finalShape = finalShape + currFieldSeq(j).Weight * currFieldSeq(j).Shape;
+ end
+ obj.stf(i).ray.shape = finalShape;
+
+end
+
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m b/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m
new file mode 100644
index 000000000..5370f7b45
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m
@@ -0,0 +1,108 @@
+function obj = matRad_interpDicomDoseCube(obj)
+% matRad function to interpolate a given Dicom Dose Cube dicom RTDOSE data
+%
+% In your object, there must be properties that contain:
+% - ct imported by the matRad_importDicomCt function;
+% - one (of several) dose cubes which should be interpolated.
+% Optional:
+% - pln structure.
+%
+% Output - structure with different actual current dose cube and several
+% meta data.
+%
+% call
+% obj = matRad_interpDicomDoseCube(obj)
+%
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% read information out of the RT file
+
+matRad_cfg = MatRad_Config.instance();
+matRad_checkEnvDicomRequirements(matRad_cfg.env);
+
+dosefile = obj.importRTDose.currDose{1};
+
+if matRad_cfg.isOctave || verLessThan('matlab','9')
+ doseInfo = dicominfo(dosefile);
+else
+ doseInfo = dicominfo(dosefile,'UseDictionaryVR',true);
+end
+
+% read the dosefile itself
+dosedata = dicomread(dosefile);
+obj.importRTDose.dose.cube = double(dosedata);
+
+% give it an internal name
+ % obj.dose.internalName = obj.currDose{12};%?????
+
+% read out the resolution
+obj.importRTDose.dose.resolution.x = doseInfo.PixelSpacing(1);
+obj.importRTDose.dose.resolution.y = doseInfo.PixelSpacing(2);
+obj.importRTDose.dose.resolution.z = obj.importFiles.resz;
+
+% target resolution is ct.resolution
+target_resolution = obj.ct.resolution;
+
+% convert dosedata to 3-D cube
+obj.importRTDose.dose.cube = squeeze(obj.importRTDose.dose.cube(:,:,1,:));
+
+% ct resolution is target resolution, now convert to new cube;
+
+% generating grid vectors
+x = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ...
+ doseInfo.PixelSpacing(1) * double(0:doseInfo.Columns - 1);
+y = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ...
+ doseInfo.PixelSpacing(2) * double(0:doseInfo.Rows - 1);
+z = doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector;
+
+% set up grid matrices - implicit dimension permuation (X Y Z-> Y X Z)
+% Matlab represents internally in the first matrix dimension the
+% ordinate axis and in the second matrix dimension the abscissas axis
+[ X, Y, Z] = meshgrid(x,y,z);
+[Xq, Yq, Zq] = meshgrid(obj.ct.x,obj.ct.y,obj.ct.z);
+
+% get GridScalingFactor
+gridScale = double(doseInfo.DoseGridScaling);
+% rescale importRTDose.dose.cube
+obj.importRTDose.dose.cube = gridScale * obj.importRTDose.dose.cube;
+
+% interpolation to ct grid - cube is now stored in Y X Z
+obj.importRTDose.dose.cube = interp3(X,Y,Z,obj.importRTDose.dose.cube,Xq,Yq,Zq,'linear',0);
+
+% write new parameters
+obj.importRTDose.dose.resolution = obj.ct.resolution;
+obj.importRTDose.dose.x = obj.ct.x;
+obj.importRTDose.dose.y = obj.ct.y;
+obj.importRTDose.dose.z = obj.ct.z;
+
+% write Dicom-Tags
+obj.importRTDose.dose.dicomInfo.PixelSpacing = [target_resolution.x; ...
+ target_resolution.y];
+obj.importRTDose.dose.dicomInfo.ImagePositionPatient = [min(obj.importRTDose.dose.x); min(obj.importRTDose.dose.y); min(obj.importRTDose.dose.z)];
+obj.importRTDose.dose.dicomInfo.SliceThickness = target_resolution.z;
+obj.importRTDose.dose.dicomInfo.ImageOrientationPatient = doseInfo.ImageOrientationPatient;
+obj.importRTDose.dose.dicomInfo.DoseType = doseInfo.DoseType;
+obj.importRTDose.dose.dicomInfo.DoseSummationType = doseInfo.DoseSummationType;
+%importRTDose.dose.dicomInfo.InstanceNumber = doseInfo.InstanceNumber; %Not
+%always given
+obj.importRTDose.dose.dicomInfo.SOPClassUID = doseInfo.SOPClassUID;
+obj.importRTDose.dose.dicomInfo.SOPInstanceUID = doseInfo.SOPInstanceUID;
+obj.importRTDose.dose.dicomInfo.ReferencedRTPlanSequence = doseInfo.ReferencedRTPlanSequence;
+
+end
+
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m
new file mode 100644
index 000000000..9ffb9578b
--- /dev/null
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m
@@ -0,0 +1,306 @@
+function obj = matRad_scanDicomImportFolder(obj)
+% matRad function to scan a folder for dicom data
+%
+% In your object, there must be a property that contains path to the
+% folder to be scanned
+%
+% Output:
+% - matlab struct with a list of dicom files including meta
+% infomation (type, series number etc.)
+% - list of patients with dicom data in the folder
+%
+% call
+% obj = matRad_scanDicomImportFolder(obj)
+%
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+%% print current status of the import script
+matRad_cfg.dispInfo('Dose series matched to the different plans are displayed and could be selected.\n');
+matRad_cfg.dispInfo('Rechecking of correct matching procedure is recommended.\n');
+
+global warnDlgDICOMtagShown;
+warnDlgDICOMtagShown = false;
+
+%% get all files in search directory
+
+% dicom import needs image processing toolbox -> check if available
+available = matRad_checkEnvDicomRequirements(matRad_cfg.env);
+
+if ~available
+ matRad_cfg.dispError('Image processing toolbox / packages not available!');
+end
+
+obj.allfiles = matRad_listAllFiles(obj.patDir);
+
+if ~isempty(obj.allfiles)
+ %% check for dicom files and differentiate patients, types, and series
+
+ % Arrays of slice locations to find z resolution
+ % if it is not given initially in the files
+ LocationsArray = NaN(1, numel(obj.allfiles(:,1)));
+ ThBool = [];
+
+ numOfFiles = numel(obj.allfiles(:,1));
+ if ~matRad_cfg.disableGUI
+ h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor);
+ matRad_applyThemeToWaitbar(h);
+ else
+ h = [];
+ end
+ % precision value for double to string conversion
+ str2numPrc = 10;
+ %h.WindowStyle = 'Modal';
+ steps = numOfFiles;
+ for i = numOfFiles:-1:1
+ if any(ishandle(h))
+ waitbar((numOfFiles+1-i) / steps, h)
+ end
+
+ try % try to get DicomInfo
+ if matRad_cfg.isOctave || verLessThan('matlab','9')
+ info = dicominfo(obj.allfiles{i});
+ else
+ info = dicominfo(obj.allfiles{i},'UseDictionaryVR',true);
+ end
+ catch
+ obj.allfiles(i,:) = [];
+
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(numOfFiles+1-i, numOfFiles);
+ end
+
+ continue;
+ end
+ try
+ obj.allfiles{i,2} = info.Modality;
+ catch
+ obj.allfiles{i,2} = NaN;
+ end
+
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'PatientID',i,3);
+
+ switch obj.allfiles{i,2}
+ case 'CT'
+
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesInstanceUID',i,4);
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,15);
+
+ case 'RTPLAN'
+
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,4);
+ if isfield(info.ReferencedStructureSetSequence.Item_1,'ReferencedSOPInstanceUID')
+ obj.allfiles = parseDicomTag(obj.allfiles,info.ReferencedStructureSetSequence.Item_1,'ReferencedSOPInstanceUID',i,15);
+ end
+ case 'RTDOSE'
+
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,4);
+ if isfield(info.ReferencedRTPlanSequence.Item_1,'ReferencedSOPInstanceUID')
+ obj.allfiles = parseDicomTag(obj.allfiles,info.ReferencedRTPlanSequence.Item_1,'ReferencedSOPInstanceUID',i,15);
+ end
+ case 'RTSTRUCT'
+
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,4);
+ if isfield(info.ReferencedFrameOfReferenceSequence.Item_1.RTReferencedStudySequence.Item_1,'ReferencedSOPInstanceUID')
+ obj.allfiles = parseDicomTag(obj.allfiles,info.ReferencedFrameOfReferenceSequence.Item_1.RTReferencedStudySequence.Item_1,'ReferencedSOPInstanceUID',i,15);
+ end
+
+ otherwise
+
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesInstanceUID',i,4);
+
+ end
+
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesNumber',i,5,@seriesnum2str); %We want to make sure the series number is stored as string
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'FamilyName',i,6);
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'GivenName',i,7);
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'PatientBirthDate',i,8);
+ obj.allfiles = parseDicomTag(obj.allfiles,info,'StudyInstanceUID',i,14);
+
+ try
+ if strcmp(info.Modality,'CT')
+ obj.allfiles{i,9} = num2str(info.PixelSpacing(1),str2numPrc);
+ else
+ obj.allfiles{i,9} = NaN;
+ end
+ catch
+ obj.allfiles{i,9} = NaN;
+ end
+ try
+ if strcmp(info.Modality,'CT')
+
+ obj.allfiles{i,10} = num2str(info.PixelSpacing(2),str2numPrc);
+ else
+ obj.allfiles{i,10} = NaN;
+ end
+ catch
+ obj.allfiles{i,10} = NaN;
+ end
+ try
+ if strcmp(info.Modality,'CT')
+ %usually the Attribute should be SliceThickness, but it
+ %seems like some data uses "SpacingBetweenSlices" instead,
+ %but if there is neither this nor that attribute,
+ %resolution will be calculated based on SliceLocations
+ if isfield(info,'SliceThickness') && info.SliceThickness ~= 0
+ obj.allfiles{i,11} = num2str(info.SliceThickness,str2numPrc);
+ ThBool = 1;
+ elseif isfield(info,'SpacingBetweenSlices') && ~isempty(info.SpacingBetweenSlices)
+ obj.allfiles{i,11} = num2str(info.SpacingBetweenSlices,str2numPrc);
+ ThBool = 1;
+ else
+ LocationsArray(i) = info.SliceLocation;
+
+ end
+ else
+ obj.allfiles{i,11} = NaN;
+ end
+ catch
+ obj.allfiles{i,11} = NaN;
+ end
+ try
+
+ if strcmp(info.Modality,'RTDOSE')
+ dosetext_helper = strcat('Instance','_', num2str(info.InstanceNumber),'_', ...
+ info.DoseSummationType, '_', info.DoseType);
+ obj.allfiles{i,12} = dosetext_helper;
+ else
+ obj.allfiles{i,12} = NaN;
+ end
+ catch
+ obj.allfiles{i,12} = NaN;
+ end
+ % writing corresponding dose dist.
+ try
+ if strcmp(obj.allfiles{i,2},'RTPLAN')
+ corrDose = [];
+ numDose = length(fieldnames(info.ReferencedDoseSequence));
+ for j = 1:numDose
+ fieldName = strcat('Item_',num2str(j));
+ corrDose{j} = info.ReferencedDoseSequence.(fieldName).ReferencedSOPInstanceUID;
+ end
+ obj.allfiles{i,13} = corrDose;
+ else
+ obj.allfiles{i,13} = {'NaN'};
+ end
+
+ catch
+ obj.allfiles{i,13} = {'NaN'};
+ end
+
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(numOfFiles+1-i, numOfFiles);
+ end
+
+ end
+
+ % Filtration, getting and assigning z resolution to all CT files
+ FiltredLocArray = unique(LocationsArray);
+ locZ = ~isnan(FiltredLocArray);
+ Thickness = unique(diff(FiltredLocArray(locZ)));
+ numOfFiles = numel(obj.allfiles(:,1));
+
+ if numel(Thickness) > 1
+ matRad_cfg.dispWarning('Slices are not equidistant! CT will be interpolate with the lowest value of resolution is given by CT slices.');
+ Thickness = Thickness(1); % min thickness
+ ThBool = []; % in this case we will also create ct cube with the same resolution for all slices
+ PriorThicknesses = flip(rmmissing(diff(FiltredLocArray))); % prior values of spacing, which are needed for interpolation
+ Counter = 0;
+
+ for i = 1:numOfFiles
+ if strcmp(obj.allfiles{i,2},'CT') && (i - Counter <= numel(PriorThicknesses))
+ obj.allfiles{i,16} = num2str(PriorThicknesses(i - Counter));
+ else
+ Counter = Counter + 1;
+ obj.allfiles{i,16} = NaN;
+ end
+ end
+
+ end
+
+ if isempty(ThBool)
+ for i = numOfFiles:-1:1
+ if strcmp(obj.allfiles{i,2},'CT')
+ obj.allfiles{i,11} = num2str(Thickness);
+ end
+ end
+ end
+ close(h)
+
+ if ~isempty(obj.allfiles)
+ obj.patient = unique(obj.allfiles(:,3));
+
+ if isempty(obj.patient)
+ matRad_cfg.dispError('No patient found with DICOM CT _and_ RT structure set in patient directory!');
+ end
+ else
+ matRad_cfg.dispError('No DICOM files found in patient directory!');
+ end
+else
+ matRad_cfg.dispError('Folder is empty!');
+end
+
+clear warnDlgDICOMtagShown;
+
+end
+
+function allfiles = parseDicomTag(allfiles,info,tag,row,column,parsefcn)
+
+global warnDlgDICOMtagShown;
+
+defaultPlaceHolder = '001';
+
+if nargin < 6
+ parsefcn = @(x) x;
+end
+
+try
+ if isfield(info,tag)
+ if ~isempty(info.(tag))
+ allfiles{row,column} = parsefcn(info.(tag));
+ else
+ allfiles{row,column} = defaultPlaceHolder;
+ end
+ else
+ allfiles{row,column} = defaultPlaceHolder;
+ end
+catch
+ allfiles{row,column} = NaN;
+end
+
+if strcmp(allfiles{row,column},defaultPlaceHolder) && (column == 3 || column == 4)
+ matRad_cfg = MatRad_Config.instance();
+ wrnTxt = ['matRad_scanDicomImportFolder: Could not parse dicom tag: ' tag '. Using placeholder ' defaultPlaceHolder ' instead. Please check imported data carefully!'];
+ matRad_cfg.dispWarning(wrnTxt)
+
+end
+end
+
+
+function value = seriesnum2str(value)
+ if isnumeric(value)
+ value = num2str(value);
+ end
+end
+
+
+
+
diff --git a/dicom/matRad_calcWaterEqD.m b/matRad/dicom/matRad_calcWaterEqD.m
similarity index 64%
rename from dicom/matRad_calcWaterEqD.m
rename to matRad/dicom/matRad_calcWaterEqD.m
index 86694b50e..8f1d7ec80 100644
--- a/dicom/matRad_calcWaterEqD.m
+++ b/matRad/dicom/matRad_calcWaterEqD.m
@@ -1,19 +1,18 @@
-function ct = matRad_calcWaterEqD(ct, pln)
+function ct = matRad_calcWaterEqD(ct, radiationMode)
% matRad function to calculate the equivalent densities from a dicom ct
% that originally uses intensity values
%
% call
-% ct = matRad_calcWaterEqD(ct, pln)
+% ct = matRad_calcWaterEqD(ct, radiationMode)
%
% input
-% ct: unprocessed dicom ct data which are stored as intensity values (IV)
-%
-% HU = IV * slope + intercept
-%
-% pln: matRad plan struct
+% ct: ct containing a cubeHU to compute rED/rSP values from
+% radiationMode: radiationMode as character array (e.g. 'photons') since matRad 3.
+% Can also be a pln-struct for downwards compatibility
%
% output
-% ct: ct struct with cube with relative _electron_ densities
+% ct: ct struct with cube with relative _electron_ densities stored in
+% ct.cube
%
% References
% -
@@ -24,26 +23,29 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
% load hlut
-hlut = matRad_loadHLUT(ct, pln);
-
+hlut = matRad_loadHLUT(ct, radiationMode);
+
for i = 1:ct.numOfCtScen
% Manual adjustments if ct data is corrupt. If some values are out of range
% of the LUT, then these values are adjusted.
if max(ct.cubeHU{i}(:)) > max(hlut(:,1))
- warning('projecting out of range HU values');
+ matRad_cfg.dispWarning('projecting out of range HU values');
ct.cubeHU{i}(ct.cubeHU{i} > max(hlut(:,1))) = max(hlut(:,1));
end
if min(ct.cubeHU{i}(:)) < min(hlut(:,1))
- warning('projecting out of range HU values');
+ matRad_cfg.dispWarning('projecting out of range HU values');
ct.cubeHU{i}(ct.cubeHU{i} < min(hlut(:,1))) = min(hlut(:,1));
end
diff --git a/matRad/dicom/matRad_checkEnvDicomRequirements.m b/matRad/dicom/matRad_checkEnvDicomRequirements.m
new file mode 100644
index 000000000..f7f3d9f7f
--- /dev/null
+++ b/matRad/dicom/matRad_checkEnvDicomRequirements.m
@@ -0,0 +1,51 @@
+function available = matRad_checkEnvDicomRequirements(env)
+% matRad function to check if requirements for dicom import / export are
+% given. Throws an error if requirements not met
+%
+% call
+% matRad_checkEnvDicomRequirements(env)
+%
+% input
+% env: folder to be scanned
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+available = true;
+
+matRad_cfg = MatRad_Config.instance();
+
+if nargin < 1
+ isOctave = matRad_cfg.isOctave;
+else
+ isOctave = strcmp(env,'OCTAVE');
+end
+
+if isOctave
+ try
+ pkg load dicom;
+ pkg load image;
+ catch
+ available = false;
+ end
+else
+ if ~license('checkout','image_toolbox')
+ available = false;
+ end
+end
+
+end
+
diff --git a/dicom/matRad_convRtssContours2Indices.m b/matRad/dicom/matRad_convRtssContours2Indices.m
similarity index 68%
rename from dicom/matRad_convRtssContours2Indices.m
rename to matRad/dicom/matRad_convRtssContours2Indices.m
index 4d7856b2a..4d830b310 100644
--- a/dicom/matRad_convRtssContours2Indices.m
+++ b/matRad/dicom/matRad_convRtssContours2Indices.m
@@ -23,13 +23,13 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+matRad_cfg = MatRad_Config.instance();
voiCube = zeros(ct.cubeDim);
% loop over all closed contour items
@@ -39,20 +39,26 @@
dicomCtSlicePos = unique(structure.item(i).points(:,3));
- if numel(dicomCtSlicePos) > 1
- error('Contour defined over multiple planes\n');
+ if numel(dicomCtSlicePos) > 1 || isempty(dicomCtSlicePos)
+ matRad_cfg.dispError('Contour defined over multiple planes!');
end
round2 = @(a,b) round(a*10^b)/10^b;
- dicomCtSliceThickness = ct.dicomInfo.SliceThickness(round2(ct.dicomInfo.SlicePositions,1)==round2(dicomCtSlicePos,1));
+ dicomCtSliceThickness = ct.dicomInfo.SliceThickness;
+
+ %Sanity check
+ msg = checkSliceThickness(dicomCtSliceThickness);
+ if ~isempty(msg)
+ matRad_cfg.dispError('Slice Thickness of slice at %f could not be identified: %s',dicomCtSlicePos,msg);
+ end
+
+ slicesInMatradCt = find(dicomCtSlicePos+dicomCtSliceThickness/2 > ct.z & dicomCtSlicePos-dicomCtSliceThickness/2 <= ct.z);
coords1 = interp1(ct.x,1:ct.cubeDim(2),structure.item(i).points(:,1),'linear','extrap');
coords2 = interp1(ct.y,1:ct.cubeDim(1),structure.item(i).points(:,2),'linear','extrap');
binIn = poly2mask(coords1,coords2,ct.cubeDim(1),ct.cubeDim(2));
- slicesInMatradCt = find(dicomCtSlicePos+dicomCtSliceThickness/2 > ct.z & dicomCtSlicePos-dicomCtSliceThickness/2 <= ct.z);
-
% loop over all slices in matRad ct
for j = 1:numel(slicesInMatradCt)
voiCube(:,:,slicesInMatradCt(j)) = voiCube(:,:,slicesInMatradCt(j)) | binIn;
@@ -63,3 +69,17 @@
end
indices = find(voiCube(:));
+
+end
+
+function msg = checkSliceThickness(dicomCtSliceThickness)
+ if isempty(dicomCtSliceThickness)
+ msg = 'Slice could not be identified (empty)';
+ elseif ~isscalar(dicomCtSliceThickness)
+ msg = 'Slice thickness not unique';
+ elseif ~isnumeric(dicomCtSliceThickness)
+ msg = 'unexpected value';
+ else
+ msg = '';
+ end
+end
diff --git a/matRad/dicom/matRad_exportDicomGUI.m b/matRad/dicom/matRad_exportDicomGUI.m
new file mode 100644
index 000000000..f1cc66cef
--- /dev/null
+++ b/matRad/dicom/matRad_exportDicomGUI.m
@@ -0,0 +1,28 @@
+function hGUI = matRad_exportDicomGUI()
+% matRad compatability function to call the dicom export widget
+%
+% call
+% matRad_importDicomGUI
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if nargout > 0
+ hGUI = matRad_exportDicomWidget();
+else
+ matRad_exportDicomWidget();
+end
+end
\ No newline at end of file
diff --git a/matRad/dicom/matRad_importDicomGUI.m b/matRad/dicom/matRad_importDicomGUI.m
new file mode 100644
index 000000000..eb1fb5451
--- /dev/null
+++ b/matRad/dicom/matRad_importDicomGUI.m
@@ -0,0 +1,30 @@
+function hGUI = matRad_importDicomGUI()
+% matRad compatability function to call the dicom importwidget
+%
+% call
+% matRad_importDicomGUI
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+if nargout > 0
+ hGUI = matRad_importDicomWidget();
+
+else
+ matRad_importDicomWidget();
+end
+end
\ No newline at end of file
diff --git a/dicom/matRad_importFieldShapes.m b/matRad/dicom/matRad_importFieldShapes.m
similarity index 78%
rename from dicom/matRad_importFieldShapes.m
rename to matRad/dicom/matRad_importFieldShapes.m
index db60b2738..2450e7376 100644
--- a/dicom/matRad_importFieldShapes.m
+++ b/matRad/dicom/matRad_importFieldShapes.m
@@ -21,7 +21,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -53,10 +53,21 @@
% set device specific parameters
device = struct;
+
for j = 1:length(currDeviceSeqNames)
currLimitsSeq = currDeviceSeq.(currDeviceSeqNames{j});
device(j).DeviceType = currLimitsSeq.RTBeamLimitingDeviceType;
- device(j).NumOfLeafs = currLimitsSeq.NumberOfLeafJawPairs;
+
+ % Check a type of collimator
+ if isfield(currLimitsSeq, 'NumberOfLeafJawPairs')
+ device(j).NumOfLeafs = currLimitsSeq.NumberOfLeafJawPairs;
+ elseif any(strcmpi(device(j).DeviceType, {'X', 'Y', 'ASYMX', 'ASYMY'} ))
+ device(j).NumOfLeafs = 1;
+ currLimitsSeq.NumberOfLeafJawPairs = device(j).NumOfLeafs;
+ else
+ error('Number of leafs/jaws is not determined');
+ end
+
% Check for nonstandard double collimators
if strncmpi(device(j).DeviceType,'MLC',3)
device(j).Limits = currLimitsSeq.LeafPositionBoundaries;
@@ -80,11 +91,18 @@
return;
end
end
-
- for j = 1:length(currControlPointSeqNames)
+
+ for j = 1:length(currControlPointSeqNames)-1
counter = counter + 1;
- currControlPointElement = currBeamSeq.ControlPointSequence.(currControlPointSeqNames{j});
-
+ currControlPointElement = currBeamSeq.ControlPointSequence.(currControlPointSeqNames{j});
+ %We need this to get the correct weight from the next Control Point
+ nextControlPointElement = currBeamSeq.ControlPointSequence.(currControlPointSeqNames{j+1});
+
+ if j == 1 && isfield(currControlPointElement, 'CumulativeMetersetWeight') && currControlPointElement.CumulativeMetersetWeight > 0
+ matRad_cfg.dispWarning('First Control Point in Beam %d already has a CumulativeMeterset bigger than 0.',i)
+ cumWeight = currControlPointElement.CumulativeMetersetWeight;
+ end
+
if isfield(currControlPointElement, 'BeamLimitingDevicePositionSequence')
% get the leaf position for every device
tmpCollimation.Fields(counter).LeafPos{length(currDeviceSeqNames),1} = [];
@@ -93,8 +111,8 @@
% the first control point and has to be defined on following
% points only if it changes -> default initilation if counter > 1
if counter > 1
- for k = 1:length(currDeviceSeqNames)
- tmpCollimation.Fields(counter).LeafPos{k} = tmpCollimation.Fields(counter-1).LeafPos{k};
+ for n = 1:length(currDeviceSeqNames)
+ tmpCollimation.Fields(counter).LeafPos{n} = tmpCollimation.Fields(counter-1).LeafPos{n};
end
end
@@ -106,13 +124,13 @@
deviceIx = find(strcmp({device(:).DeviceType}, ...
currControlPointElement.BeamLimitingDevicePositionSequence.(currDeviceSeqNames{k}).RTBeamLimitingDeviceType));
- if (length(currLeafPos) ~= 2 * device(deviceIx).NumOfLeafs)
+ if length(currLeafPos) ~= 2*device(deviceIx).NumOfLeafs
warning(['Number of leafs/jaws does not match given number of leaf/jaw positions in control point sequence ' ...
currControlPointSeqNames{j} ' on beam sequence ' beamSeqNames{i} ' for device ' ...
device(deviceIx).DeviceType '. No field shape import performed!']);
return;
end
-
+
% set left and right leaf positions
tmpCollimation.Fields(counter).LeafPos{deviceIx}(:,1) = currLeafPos(1:device(deviceIx).NumOfLeafs);
tmpCollimation.Fields(counter).LeafPos{deviceIx}(:,2) = currLeafPos(device(deviceIx).NumOfLeafs+1:end);
@@ -126,14 +144,14 @@
end
else
tmpCollimation.Fields(counter) = tmpCollimation.Fields(counter - 1);
- end
-
- % get field meta information
- if isfield(currControlPointElement, 'CumulativeMetersetWeight')
- newCumWeight = currControlPointElement.CumulativeMetersetWeight;
- tmpCollimation.Fields(counter).Weight = (newCumWeight - cumWeight) / ...
- currBeamSeq.FinalCumulativeMetersetWeight * ...
- tmpCollimation.beamMeterset(i)/100;
+ end
+
+ % get field meta information
+ if isfield(nextControlPointElement, 'CumulativeMetersetWeight')
+ newCumWeight = nextControlPointElement.CumulativeMetersetWeight;
+ relativeShapeWeight = (newCumWeight - cumWeight) / currBeamSeq.FinalCumulativeMetersetWeight;
+ tmpCollimation.Fields(counter).Weight = relativeShapeWeight * tmpCollimation.beamMeterset(i);
+
cumWeight = newCumWeight;
else
warning(['No CumulativeMetersetWeight found in control point sequence ' currControlPointSeqNames{j} ...
@@ -141,7 +159,7 @@
return;
end
tmpCollimation.Fields(counter).SAD = currBeamSeq.SourceAxisDistance;
-
+
% other meta information is only included in all control point
% sequences if it changes during treatment, otherwise use FieldMeta
for k = 1:length(meta)
@@ -151,7 +169,7 @@
tmpCollimation.Fields(counter).(meta{k,2}) = FieldMeta.(meta{k,2});
end
end
- % save information which control point sequence belongs to which beam sequence
+ % % save information which control point sequence belongs to which beam sequence
tmpCollimation.Fields(counter).BeamIndex = i;
end
end
@@ -169,12 +187,13 @@
maximumVoxelExtent = 0;
[X,Y] = meshgrid(-shapeLimit:shapeLimit-1);
for i = 1:length(tmpCollimation.Fields)
- shape = ones(2*shapeLimit);
+ shape = ones(2*shapeLimit);
beamIndex = tmpCollimation.Fields(i).BeamIndex;
for j = 1:length(tmpCollimation.Devices{beamIndex})
% check for ASYM and SYM jaws == type 1
if strncmpi(tmpCollimation.Devices{beamIndex}(j).DeviceType,'ASYM',4)
type = 1;
+
elseif (strcmpi(tmpCollimation.Devices{beamIndex}(j).DeviceType,'X') || ...
strcmpi(tmpCollimation.Devices{beamIndex}(j).DeviceType,'Y'))
type = 1;
@@ -186,6 +205,26 @@
' not supported. Field shapes could not be imported!']);
return;
end
+
+ %Consider orthogonal limits
+ if type == 2
+ firstLeafStart = ceil(tmpCollimation.Devices{beamIndex}(j).Limits(1)/convResolution)+shapeLimit+1;
+ lastLeafEnd = ceil(tmpCollimation.Devices{beamIndex}(j).Limits(end)/convResolution)+shapeLimit;
+
+ if ismember(tmpCollimation.Devices{beamIndex}(j).Direction, {'X', 'X1', 'X2'}, 'legacy')
+ shape(1:firstLeafStart-1,:) = 0;
+ shape(lastLeafEnd+1:end,:) = 0;
+ elseif ismember(tmpCollimation.Devices{beamIndex}(j).Direction, {'Y', 'Y1', 'Y2'}, 'legacy')
+ shape(:,1:firstLeafStart-1) = 0;
+ shape(:,lastLeafEnd+1:end) = 0;
+ else
+ warning(['Wrong collimation direction ' tmpCollimation.Devices{beamIndex}(j).Direction ...
+ ' given for device ' tmpCollimation.Devices{beamIndex}(j).DeviceType ...
+ '. Fields could not be imported.']);
+ return;
+ end
+ end
+
for k = 1:tmpCollimation.Devices{beamIndex}(j).NumOfLeafs
% determine corner points of the open area
p1 = ceil(tmpCollimation.Fields(i).LeafPos{j}(k,1)/convResolution)+shapeLimit;
@@ -241,3 +280,5 @@
end
collimation = tmpCollimation;
end
+
+
diff --git a/matRad/dicom/matRad_interpDicomCtCube.m b/matRad/dicom/matRad_interpDicomCtCube.m
new file mode 100644
index 000000000..50cb061c9
--- /dev/null
+++ b/matRad/dicom/matRad_interpDicomCtCube.m
@@ -0,0 +1,137 @@
+function interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution, doseGrid)
+% matRad function to interpolate a 3D ct cube to a different resolution
+%
+% call
+% interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution)
+% interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution, doseGrid)
+%
+% input
+% origCt: original CT as matlab 3D array
+% origCtInfo: meta information about the geometry of the orgiCt cube
+% resolution: target resolution [mm] in x, y, an z direction for the
+% new cube
+% doseGrid: optional: externally specified grid vector
+%
+% output
+% interpCt: interpolated ct cube as matlab 3D array
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+coordsOfFirstPixel = [origCtInfo.ImagePositionPatient];
+
+xDir = origCtInfo(1).ImageOrientationPatient(1:3); % lps: [1;0;0]
+yDir = origCtInfo(1).ImageOrientationPatient(4:6); % lps: [0;1;0]
+
+% set up original grid vectors
+x = coordsOfFirstPixel(1,1) + origCtInfo(1).ImageOrientationPatient(1) * ...
+ origCtInfo(1).PixelSpacing(1)*double([0:origCtInfo(1).Columns-1]);
+y = coordsOfFirstPixel(2,1) + origCtInfo(1).ImageOrientationPatient(5) * ...
+ origCtInfo(1).PixelSpacing(2)*double([0:origCtInfo(1).Rows-1]);
+z = coordsOfFirstPixel(3,:);
+
+if xDir(1) == -1 && xDir(2) == 0 && xDir(3) == 0
+ x = flip(x,2);
+end
+
+if yDir(1) == 0 && yDir(2) == -1 && yDir(3) == 0
+ y = flip(y,2);
+end
+
+if exist('doseGrid','var')
+ xq = doseGrid{1};
+ yq = doseGrid{2};
+ zq = doseGrid{3};
+
+ % calculate intersection of regions to avoid interpolation issues
+ xqRe = coordsOfFirstPixel(1,1):origCtInfo(1).ImageOrientationPatient(1)*resolution.x: ...
+ (coordsOfFirstPixel(1,1)+origCtInfo(1).ImageOrientationPatient(1)*origCtInfo(1).PixelSpacing(1)*double(origCtInfo(1).Columns-1));
+ yqRe = [coordsOfFirstPixel(2,1):origCtInfo(1).ImageOrientationPatient(5)*resolution.y: ...
+ (coordsOfFirstPixel(2,1)+origCtInfo(1).ImageOrientationPatient(5)*origCtInfo(1).PixelSpacing(2)*double(origCtInfo(1).Rows-1))];
+ zqRe = coordsOfFirstPixel(3,1):resolution.z: coordsOfFirstPixel(3,end);
+ if xDir(1) == -1 && xDir(2) == 0 && xDir(3) == 0
+ x = flip(x,2);
+ end
+
+ if yDir(1) == 0 && yDir(2) == -1 && yDir(3) == 0
+ y = flip(y,2);
+ end
+ % cut values
+ xq(xq < min(xqRe)) = [];
+ xq(xq > max(xqRe)) = [];
+ yq(yq < min(yqRe)) = [];
+ yq(yq > max(yqRe)) = [];
+ zq(zq < min(zqRe)) = [];
+ zq(zq > max(zqRe)) = [];
+
+ % set up grid matrices - implicit dimension permuation (X Y Z-> Y X Z)
+ % Matlab represents internally in the first matrix dimension the
+ % ordinate axis and in the second matrix dimension the abscissas axis
+ [ X, Y, Z] = meshgrid(x,y,z);
+ [Xq, Yq, Zq] = meshgrid(xq,yq,zq);
+ % interpolate cube - cube is now stored in Y X Z
+ interpCt.cubeIV{1} = interp3(X,Y,Z,double(origCt),Xq,Yq,Zq);
+
+else
+ % skip interpolation if the given resolution is equal (with an acceptance of 1e^-6) to the orignal resolution
+ if(all(abs([origCtInfo(1).PixelSpacing(1),origCtInfo(1).PixelSpacing(2),origCtInfo(1).SliceThickness] - [resolution.x,resolution.y,resolution.z]) < 1e-6))
+
+ % set the grid and resolution to the original values, to avoid reounding errors
+ resolution.x = origCtInfo(1).PixelSpacing(1);
+ resolution.y = origCtInfo(1).PixelSpacing(2);
+ resolution.z = origCtInfo(1).SliceThickness;
+ xq = x;
+ yq = y;
+ zq = z;
+
+ % set the return ctCube to the original Ct information because
+ % there is no interpolation needed
+ interpCt.cubeIV{1} = origCt;
+ else
+ % calculate new grid for the interpolation,
+ % grid equals a range of the first pixel, to the original
+ % last pixel, with steps equal the given resolution
+ xq = coordsOfFirstPixel(1,1):origCtInfo(1).ImageOrientationPatient(1)*resolution.x: ...
+ (coordsOfFirstPixel(1,1)+origCtInfo(1).ImageOrientationPatient(1)*origCtInfo(1).PixelSpacing(1)*double(origCtInfo(1).Columns-1));
+ yq = [coordsOfFirstPixel(2,1):origCtInfo(1).ImageOrientationPatient(5)*resolution.y: ...
+ (coordsOfFirstPixel(2,1)+origCtInfo(1).ImageOrientationPatient(5)*origCtInfo(1).PixelSpacing(2)*double(origCtInfo(1).Rows-1))];
+ zq = coordsOfFirstPixel(3,1):resolution.z: coordsOfFirstPixel(3,end);
+ if xDir(1) == -1 && xDir(2) == 0 && xDir(3) == 0
+ xq = flip(xq,2);
+ end
+ if yDir(1) == 0 && yDir(2) == -1 && yDir(3) == 0
+ yq = flip(yq,2);
+ end
+ % set up grid matrices - implicit dimension permuation (X Y Z-> Y X Z)
+ % Matlab represents internally in the first matrix dimension the
+ % ordinate axis and in the second matrix dimension the abscissas axis
+ [ X, Y, Z] = meshgrid(x,y,z);
+ [Xq, Yq, Zq] = meshgrid(xq,yq,zq);
+ % interpolate cube - cube is now stored in Y X Z
+ interpCt.cubeIV{1} = interp3(X,Y,Z,double(origCt),Xq,Yq,Zq);
+
+ end
+end
+
+% some meta information
+interpCt.resolution = resolution;
+
+interpCt.x = xq;
+interpCt.y = yq;
+interpCt.z = zq;
+
+interpCt.cubeDim = [numel(yq) numel(xq) numel(zq)];
+interpCt.numOfCtScen = 1;
diff --git a/dicom/matRad_listAllFiles.m b/matRad/dicom/matRad_listAllFiles.m
similarity index 96%
rename from dicom/matRad_listAllFiles.m
rename to matRad/dicom/matRad_listAllFiles.m
index cecc8452e..1a649824b 100644
--- a/dicom/matRad_listAllFiles.m
+++ b/matRad/dicom/matRad_listAllFiles.m
@@ -28,7 +28,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/dicom/matRad_loadHLUT.m b/matRad/dicom/matRad_loadHLUT.m
similarity index 61%
rename from dicom/matRad_loadHLUT.m
rename to matRad/dicom/matRad_loadHLUT.m
index c035640ee..7be93f0e2 100644
--- a/dicom/matRad_loadHLUT.m
+++ b/matRad/dicom/matRad_loadHLUT.m
@@ -1,12 +1,13 @@
-function hlut = matRad_loadHLUT(ct, pln)
+function hlut = matRad_loadHLUT(ct, radiationMode)
% matRad function to load HLUT file based on the provided ct
%
% call
% hlut = matRad_loadHLUT(ct, pln)
%
% input
-% ct: unprocessed dicom ct data
-% pln: matRad pln struct
+% ct: ct with dicom information
+% radiationMode: radiationMode as character array (e.g. 'photons') since matRad 3.
+% Can also be a pln-struct for downwards compatibility
%
% output
% hlut: lookup table
@@ -20,7 +21,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -29,18 +30,25 @@
matRad_cfg = MatRad_Config.instance();
-% directory with look up table files
-if ~isdeployed
- hlutDir = fullfile(fileparts(mfilename('fullpath')),'hlutLibrary',filesep);
+% directories with look up table files
+hlutDefaultDir = [matRad_cfg.matRadSrcRoot filesep 'hluts' filesep];
+hlutUserDirs = cellfun(@(uf) strcat(uf,filesep,'hluts',filesep),matRad_cfg.userfolders,'UniformOutput',false);
+hlutDirs = [hlutDefaultDir hlutUserDirs(:)'];
+
+%Old version - takes radiation mode from pln
+if isstruct(radiationMode) && isfield(radiationMode,'radiationMode')
+ particle = radiationMode.radiationMode;
+elseif ischar(radiationMode)
+ particle = radiationMode;
else
- hlutDir = [];
+ matRad_cfg.dispError('Invalid radiation mode!');
end
+
% if possible -> file standard out of dicom tags
try
hlutFileName = '';
- particle = pln.radiationMode;
manufacturer = ct.dicomInfo.Manufacturer;
model = ct.dicomInfo.ManufacturerModelName;
if isfield(ct.dicomInfo,'ConvolutionKernel')
@@ -57,10 +65,14 @@
hlutFileCell{3} = regexprep(hlutFileName,' ','_');
% add pathname
- hlutFileCell = strcat(hlutDir,hlutFileCell);
+ existIx = [];
+ for i = 1:numel(hlutDirs)
+ hlutDir = hlutDirs{i};
+ hlutFileCell = strcat(hlutDir,hlutFileCell);
- % check if files exist
- existIx = cellfun(@(x) exist(x,'file') == 2,hlutFileCell);
+ % check if files exist
+ existIx = [existIx cellfun(@(x) exist(x,'file') == 2,hlutFileCell)];
+ end
if sum(existIx) == 0
produce an error to enter catch statment below :)
@@ -76,8 +88,7 @@
matRad_cfg.dispWarning(warnText);
% load default HLUT
- hlutFileName = strcat(hlutDir,'matRad_default.hlut');
-
+ hlutFileName = strcat(hlutDefaultDir,'matRad_default.hlut');
end
hlut = matRad_readHLUT(hlutFileName);
diff --git a/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/getAvailableEngines.m b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/getAvailableEngines.m
new file mode 100644
index 000000000..8ef17d112
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/getAvailableEngines.m
@@ -0,0 +1,88 @@
+function classList = getAvailableEngines(pln,optionalPaths)
+% Returns a list of names and coresponding handle for available dose calc engines
+% Returns all dose calc engines in the package when no arg is
+% given. If no engines are found return gonna be empty.
+%
+% call:
+% classList = DoseEngines.matRad_DoseEngine.getAvailableEngines(pln,optional_path)
+%
+% input:
+% pln: containing proposed dose calc and machine file informations
+% optionalPath: cell array of other folders to search in
+%
+% returns:
+% classList: struct array containing name, shortName, className, and
+% handle for construction of the Engine
+
+matRad_cfg = MatRad_Config.instance();
+
+%Parse inputs
+if nargin < 2
+ optionalPaths = {};
+else
+ if ~(iscellstr(optionalPaths) && all(optionalPaths))
+ matRad_cfg.dispError('Invalid path array!');
+ end
+end
+
+if nargin < 1
+ pln = [];
+else
+ if ~(isstruct(pln) || isempty(pln))
+ matRad_cfg.dispError('Invalid pln!');
+ end
+end
+
+
+%Get available, valid classes through call to matRad helper function
+%for finding subclasses
+availableDoseEngines = matRad_findSubclasses('DoseEngines.matRad_DoseEngineBase','packages',{'DoseEngines'},'folders',optionalPaths,'includeAbstract',false);
+
+%Now filter for pln
+ix = [];
+
+if nargin >= 1 && ~isempty(pln)
+ machine = matRad_loadMachine(pln);
+ machineMode = machine.meta.radiationMode;
+
+ for cIx = 1:length(availableDoseEngines)
+ mc = availableDoseEngines{cIx};
+ availabilityFuncStr = [mc.Name '.isAvailable'];
+ %availabilityFunc = str2func(availabilityFuncStr); %str2func does not seem to work on static class functions in Octave 5.2.0
+ try
+ %available = availabilityFunc(pln,machine);
+ available = eval([availabilityFuncStr '(pln,machine)']);
+ catch
+ available = false;
+ mpList = mc.PropertyList;
+ if matRad_cfg.isMatlab
+ loc = find(arrayfun(@(x) strcmp('possibleRadiationModes',x.Name),mpList));
+ propValue = mpList(loc).DefaultValue;
+ else
+ loc = find(cellfun(@(x) strcmp('possibleRadiationModes',x.Name),mpList));
+ propValue = mpList{loc}.DefaultValue;
+ end
+
+ if any(strcmp(propValue, pln.radiationMode))
+ % get radiation mode from the in pln proposed basedata machine file
+ % add current class to return lists if the
+ % radiation mode is compatible
+ if(any(strcmp(propValue, machineMode)))
+ available = true;
+
+ end
+ end
+ end
+ if available
+ ix = [ix cIx];
+ end
+ end
+
+ availableDoseEngines = availableDoseEngines(ix);
+end
+
+classList = matRad_identifyClassesByConstantProperties(availableDoseEngines,'shortName','defaults',matRad_cfg.defaults.propDoseCalc.engine,'additionalPropertyNames',{'name'});
+
+end
+
+
diff --git a/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/getEngineFromPln.m b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/getEngineFromPln.m
new file mode 100644
index 000000000..f9920ac9d
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/getEngineFromPln.m
@@ -0,0 +1,66 @@
+function engine = getEngineFromPln(pln)
+%GETENGINE Summary of this function goes here
+% Detailed explanation goes here
+
+matRad_cfg = MatRad_Config.instance();
+
+engine = [];
+
+initDefaultEngine = false;
+%get all available engines for given pln struct, could be done conditional
+classList = DoseEngines.matRad_DoseEngineBase.getAvailableEngines(pln);
+
+% Check for a valid engine, and if the given engine isn't valid set boolean
+% to initiliaze default engine at the end of this function
+if isfield(pln,'propDoseCalc') && isa(pln.propDoseCalc, 'DoseEngines.matRad_DoseEngineBase')
+ engine = pln.propDoseCalc;
+elseif isfield(pln,'propDoseCalc') && isstruct(pln.propDoseCalc) && isfield(pln.propDoseCalc,'engine')
+
+ if ischar(pln.propDoseCalc.engine) || isstring(pln.propDoseCalc.engine)
+ matchEngines = strcmpi({classList(:).shortName},pln.propDoseCalc.engine);
+ if any(matchEngines)
+ %instantiate engine
+ engineHandle = classList(matchEngines).handle;
+ engine = engineHandle(pln);
+
+ %engine.assignPropertiesFromPln(pln); %TODO: could this be in the constructor?
+ else
+ initDefaultEngine = true;
+ end
+ else
+ initDefaultEngine = true;
+ matRad_cfg.dispWarning('pln.propDoseCalc.engine field not valid!');
+ end
+else
+ initDefaultEngine = true;
+end
+
+% trying to use a default engine which fits
+% the given radiation mode, when no valid engine was defined.
+% Default Engines are defined in matRad_Config.
+if initDefaultEngine
+ matchEngines = ismember({classList(:).shortName},matRad_cfg.defaults.propDoseCalc.engine);
+ if any(matchEngines)
+ engineHandle = classList(matchEngines).handle;
+
+ % unlikely event that multiple engines fit just take the first
+ if length(engineHandle) > 1
+ engineHandle = engineHandle{1};
+ end
+ engine = engineHandle(pln);
+ matRad_cfg.dispWarning('Using default dose calculation engine %s!', engine.name);
+ elseif ~isempty(classList)
+ engineHandle = classList(1).handle;
+ engine = engineHandle(pln);
+ matRad_cfg.dispWarning('Default dose calculation engine not available! Using %s.', engine.name);
+ else
+ matRad_cfg.dispError('Default dose engine not found!');
+ end
+end
+
+if isempty(engine)
+ matRad_cfg.dispError('No suitable dose engine found!');
+end
+
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/initDoseCalc.m b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/initDoseCalc.m
new file mode 100644
index 000000000..063894134
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/initDoseCalc.m
@@ -0,0 +1,201 @@
+function [dij] = initDoseCalc(this,ct,cst,stf)
+% matRad_DoseEngine.initDoseCalc: Interface for dose calculation
+% method for setting and preparing the inition parameters for the
+% dose calculation.
+% Should be called at the beginning of calcDose method.
+% Can be expanded or changed by overwriting this method and calling
+% the superclass method inside of it
+%
+% call:
+% [dij] = matRad_DoseEngine.initDoseCalc(this,ct,cst,stf)
+%
+% input:
+% ct: matRad ct struct
+% cst: matRad cst struct
+% stf: matRad stf struct
+%
+% returns:
+% dij: matRad dij struct
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+this.timers.full = tic;
+
+if this.calcDoseDirect
+ msg = sprintf('Forward dose calculation using ''%s'' Dose Engine...',this.name);
+else
+ msg = sprintf('Dose influence matrix calculation using ''%s'' Dose Engine...',this.name);
+end
+matRad_cfg.dispInfo('%s\n',msg);
+
+% initialize waitbar
+% TODO: This should be managed from the user interface instead
+if ~matRad_cfg.disableGUI
+ this.hWaitbar = waitbar(0,msg,'Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor);
+ matRad_applyThemeToWaitbar(this.hWaitbar);
+ % prevent closure of waitbar and show busy state
+ set(this.hWaitbar,'pointer','watch');
+end
+this.lastProgressUpdate = tic;
+
+machine = unique({stf.machine});
+radiationMode = unique({stf.radiationMode});
+if numel(machine) ~= 1 || numel(radiationMode) ~= 1
+ matRad_cfg.dispError('machine and radiation mode need to be unique within supplied stf!');
+end
+%extract strings from cell
+machine = machine{1};
+radiationMode = radiationMode{1};
+
+%Scenario Model
+if ~isa(this.multScen,'matRad_ScenarioModel')
+ this.multScen = matRad_multScen(ct,this.multScen);
+end
+
+if ~isa(this.bioParam,'matRad_BiologicalModel')
+ this.bioParam = matRad_bioModel(radiationMode,'physicalDose','none');
+end
+
+dij = struct();
+
+if ~isnan(this.bioParam.RBE)
+ dij.RBE = this.bioParam.RBE;
+end
+
+%store CT grid
+dij.ctGrid.resolution = ct.resolution;
+
+% to guarantee downwards compatibility with data that does not have
+% ct.x/y/z
+ct = matRad_getWorldAxes(ct);
+
+dij.ctGrid.x = ct.x;
+dij.ctGrid.y = ct.y;
+dij.ctGrid.z = ct.z;
+
+dij.ctGrid.dimensions = [numel(dij.ctGrid.y) numel(dij.ctGrid.x) numel(dij.ctGrid.z)];
+dij.ctGrid.numOfVoxels = prod(dij.ctGrid.dimensions);
+
+
+%Create Dose Grid
+dij.doseGrid = this.doseGrid;
+
+%One can provide the dose grid directly (in the future, variable grids would be possible with this)
+if ~all(isfield(dij.doseGrid,{'x','y','z'}))
+ dij.doseGrid.x = dij.ctGrid.x(1):this.doseGrid.resolution.x:dij.ctGrid.x(end);
+ dij.doseGrid.y = dij.ctGrid.y(1):this.doseGrid.resolution.y:dij.ctGrid.y(end);
+ dij.doseGrid.z = dij.ctGrid.z(1):this.doseGrid.resolution.z:dij.ctGrid.z(end);
+end
+
+dij.doseGrid.dimensions = [numel(dij.doseGrid.y) numel(dij.doseGrid.x) numel(dij.doseGrid.z)];
+dij.doseGrid.numOfVoxels = prod(dij.doseGrid.dimensions);
+matRad_cfg.dispInfo('Dose grid has dimensions %dx%dx%d\n',dij.doseGrid.dimensions(1),dij.doseGrid.dimensions(2),dij.doseGrid.dimensions(3));
+
+dij.doseGrid.cubeCoordOffset = [dij.doseGrid.resolution.x - dij.ctGrid.resolution.x ...
+ dij.doseGrid.resolution.y - dij.ctGrid.resolution.y ...
+ dij.doseGrid.resolution.z - dij.ctGrid.resolution.z];
+
+% meta information for dij
+dij.numOfBeams = numel(stf);
+dij.numOfScenarios = this.multScen.totNumScen;
+dij.numOfRaysPerBeam = [stf(:).numOfRays];
+dij.totalNumOfBixels = sum([stf(:).totalNumOfBixels]);
+dij.totalNumOfRays = sum(dij.numOfRaysPerBeam);
+
+% check if full dose influence data is required
+if this.calcDoseDirect
+ this.numOfColumnsDij = length(stf);
+else
+ this.numOfColumnsDij = dij.totalNumOfBixels;
+end
+
+% set up arrays for book keeping
+dij.bixelNum = NaN*ones(this.numOfColumnsDij,1);
+dij.rayNum = NaN*ones(this.numOfColumnsDij,1);
+dij.beamNum = NaN*ones(this.numOfColumnsDij,1);
+
+%Default MU calibration
+dij.minMU = zeros(this.numOfColumnsDij,1);
+dij.maxMU = inf(this.numOfColumnsDij,1);
+dij.numOfParticlesPerMU = 1e6*ones(this.numOfColumnsDij,1);
+
+if isempty(this.voxelSubIx)
+ % take only voxels inside patient
+ tmpVctGridScen = cell(1,ct.numOfCtScen);
+ for s = 1:ct.numOfCtScen
+ tmpScen = cellfun(@(c) c{s},cst(:,4),'UniformOutput',false);
+ tmpVctGridScen{s} = unique(vertcat(tmpScen{:}));
+ end
+else
+ if iscell(this.voxelSubIx)
+ tmpVctGridScen = cell(1,ct.numOfCtScen);
+ for s = 1:ct.numOfCtScen
+ tmpVctGridScen{s} = this.voxelSubIx;
+ end
+ else
+ tmpVctGridScen = this.voxelSubIx;
+ end
+end
+this.VctGrid = unique(vertcat(tmpVctGridScen{:}));
+% No we find the subindexes for the indivdual scenarios. This helps us
+% doing a subselection later on.
+this.VctGridScenIx = cellfun(@(c) ismember(this.VctGrid,c),tmpVctGridScen,'UniformOutput',false);
+
+
+tmpVdoseGridScen = cell(1,ct.numOfCtScen);
+for s = 1:ct.numOfCtScen
+ % receive linear indices and grid locations from the dose grid
+ tmpCube = zeros(ct.cubeDim);
+ tmpCube(tmpVctGridScen{s}) = 1;
+
+ % interpolate cube
+ tmpVdoseGridScen{s} = find(matRad_interp3(dij.ctGrid.x, dij.ctGrid.y, dij.ctGrid.z,tmpCube, ...
+ dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'nearest'));
+end
+this.VdoseGrid = unique(vertcat(tmpVdoseGridScen{:}));
+this.VdoseGridScenIx = cellfun(@(c) ismember(this.VdoseGrid,c), tmpVdoseGridScen,'UniformOutput',false);
+
+
+% Convert CT subscripts to world coordinates.
+this.voxWorldCoords = matRad_cubeIndex2worldCoords(this.VctGrid,dij.ctGrid);
+
+% Convert dosegrid subscripts to world coordinates
+this.voxWorldCoordsDoseGrid = matRad_cubeIndex2worldCoords(this.VdoseGrid,dij.doseGrid);
+
+%Create helper masks
+this.VdoseGridMask = false(dij.doseGrid.numOfVoxels,1);
+this.VdoseGridMask(this.VdoseGrid) = true;
+
+this.VctGridMask = false(prod(ct.cubeDim),1);
+this.VctGridMask(this.VctGrid) = true;
+
+% load machine file from base data folder
+this.machine = this.loadMachine(radiationMode,machine);
+
+this.doseGrid = dij.doseGrid;
+
+%Voxel selection for dose calculation
+% ser overlap prioriites
+cst = matRad_setOverlapPriorities(cst);
+
+% resizing cst to dose cube resolution
+cst = matRad_resizeCstToGrid(cst,dij.ctGrid.x,dij.ctGrid.y,dij.ctGrid.z,...
+ dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z);
+
+%structures that are selected here will be included in dose calculation over the robust scenarios
+this.robustVoxelsOnGrid = matRad_selectVoxelsFromCst(cst, dij.doseGrid, this.selectVoxelsInScenarios);
+
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/matRad_DoseEngineBase.m b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/matRad_DoseEngineBase.m
new file mode 100644
index 000000000..21bcf2c7e
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/matRad_DoseEngineBase.m
@@ -0,0 +1,366 @@
+classdef (Abstract) matRad_DoseEngineBase < handle
+% matRad_DoseEngine: Interface for dose calculation
+% This base class provides the structure for the basic initialization
+% functions and corresponding properties for e.g. particle and photon
+% based dose calc.
+% Implementations like particle, photon based dose calculation can be
+% found in the DoseEngine package
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ % Implement these properties in a subclass
+ properties (Constant, Abstract)
+ shortName; % short identifier by which matRad recognizes an engine
+ name; % user readable name for dose engine
+ possibleRadiationModes; % radiation modes the engine is meant to process
+ %supportedQuantities; % supported (influence) quantities. Does not include quantities that can be derived post-calculation.
+ end
+
+ % Public properties
+ properties
+ doseGrid; % doseGrid to use (struct with at least doseGrid.resolution.x/y/z set)
+ multScen; % scenario model to use
+ voxelSubIx; % selection of where to calculate / store dose, empty by default
+ selectVoxelsInScenarios; % which voxels to compute in robustness scenarios
+ bioParam; % Biological dose modeling
+ end
+
+ % Protected properties with public get access
+ properties (SetAccess = protected, GetAccess = public)
+ machine; % base data defined in machine file
+
+ timers; % timers of dose calc
+
+ numOfColumnsDij; % number of columns in the dij struct
+
+ voxWorldCoords; % ct voxel coordinates in world
+ voxWorldCoordsDoseGrid; % dose grid voxel coordinates in world
+
+ %offset; % offset adjustment for isocenter
+
+ VctGrid; % voxel grid inside patient
+ VdoseGrid; % voxel dose grid
+ VctGridScenIx; %logical subindexes of scenarios in ct grid
+ VdoseGridScenIx; %logical subindexes of scenarios in dose grid
+
+ VctGridMask; % voxel grid inside patient as logical mask
+ VdoseGridMask; % voxel dose grid inside patient as logical mask
+
+ robustVoxelsOnGrid; %voxels to be computed in robustness scenarios
+ end
+
+ % Fully protected properties
+ properties (Access = protected)
+ lastProgressUpdate;
+ calcDoseDirect = false; % switch for direct cube / dij calculation
+ directWeights = [];
+ end
+
+ properties (Constant)
+ isDoseEngine = true; % const boolean for checking inheritance
+ end
+
+ properties (SetAccess = private)
+ hWaitbar;
+ end
+
+ methods
+ %Constructor
+ function this = matRad_DoseEngineBase(pln)
+ this.setDefaults();
+ if nargin == 1 && ~isempty(pln)
+ this.assignPropertiesFromPln(pln);
+ end
+ end
+
+ function delete(this)
+ %Close Waitbar
+ if any(ishandle(this.hWaitbar))
+ delete(this.hWaitbar);
+ end
+ end
+
+ function warnDeprecatedEngineProperty(this,oldProp,msg,newProp)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 3 || isempty(msg)
+ msg = '';
+ end
+
+ if nargin < 4
+ dep2 = '';
+ else
+ dep2 = sprintf('Use Property ''%s'' instead!',newProp);
+ end
+
+ matRad_cfg.dispDeprecationWarning('Property ''%s'' of Dose Engine ''%s'' is deprecated! %s%s',oldProp,this.name,msg,dep2);
+ end
+
+ function assignPropertiesFromPln(this,pln,warnWhenPropertyChanged)
+ matRad_cfg = MatRad_Config.instance();
+
+ %Set Scenario Model
+ if isfield(pln,'multScen')
+ this.multScen = pln.multScen;
+ end
+
+ %Assign biological model
+ if isfield(pln,'bioParam')
+ this.bioParam = pln.bioParam;
+ end
+
+ if nargin < 3 || ~isscalar(warnWhenPropertyChanged) || ~islogical(warnWhenPropertyChanged)
+ warnWhenPropertyChanged = false;
+ end
+
+ %Overwrite default properties within the engine with the ones
+ %given in the propDoseCalc struct
+ if isfield(pln,'propDoseCalc') && isstruct(pln.propDoseCalc)
+ plnStruct = pln.propDoseCalc; %get remaining fields
+ if isfield(plnStruct,'engine') && ~isempty(plnStruct.engine) && ~any(strcmp(plnStruct.engine,this.shortName))
+ matRad_cfg.dispWarning('Inconsistent dose engines given! pln asks for ''%s'', but you are using ''%s''!',plnStruct.engine,this.shortName);
+ end
+ if isfield(plnStruct,'engine')
+ plnStruct = rmfield(plnStruct, 'engine'); % engine field is no longer needed and would throw an exception
+ end
+ else
+ plnStruct = struct();
+ end
+
+ fields = fieldnames(plnStruct);
+
+ %Set up warning message
+ if warnWhenPropertyChanged
+ warningMsg = 'Property in Dose Engine overwritten from pln.propDoseCalc';
+ else
+ warningMsg = '';
+ end
+
+ % iterate over all fieldnames and try to set the
+ % corresponding properties inside the engine
+ if matRad_cfg.isOctave
+ c2sWarningState = warning('off','Octave:classdef-to-struct');
+ end
+
+ for i = 1:length(fields)
+ try
+ field = fields{i};
+ if matRad_ispropCompat(this,field)
+ this.(field) = matRad_recursiveFieldAssignment(this.(field),plnStruct.(field),true,warningMsg);
+ else
+ matRad_cfg.dispWarning('Not able to assign property ''%s'' from pln.propDoseCalc to Dose Engine!',field);
+ end
+ catch ME
+ % catch exceptions when the engine has no properties,
+ % which are defined in the struct.
+ % When defining an engine with custom setter and getter
+ % methods, custom exceptions can be caught here. Be
+ % careful with Octave exceptions!
+ if ~isempty(warningMsg)
+ matRad_cfg = MatRad_Config.instance();
+ switch ME.identifier
+ case 'MATLAB:noPublicFieldForClass'
+ matRad_cfg.dispWarning('Not able to assign property from pln.propDoseCalc to Dose Engine: %s',ME.message);
+ otherwise
+ matRad_cfg.dispWarning('Problem while setting up engine from struct:%s %s',field,ME.message);
+ end
+ end
+ end
+ end
+
+ if matRad_cfg.isOctave
+ warning(c2sWarningState.state,'Octave:classdef-to-struct');
+ end
+ end
+
+
+
+ function resultGUI = calcDoseForward(this,ct,cst,stf,w)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 5 && ~isfield([stf.ray],'weight')
+ matRad_cfg.dispError('No weight vector available. Please provide w or add info to stf')
+ end
+
+ % copy bixel weight vector into stf struct
+ if nargin == 5
+ if sum([stf.totalNumOfBixels]) ~= numel(w) && ~isfield([stf.ray],'shapes')
+ matRad_cfg.dispError('weighting does not match steering information');
+ end
+ counter = 0;
+ for i = 1:size(stf,2)
+ for j = 1:stf(i).numOfRays
+ for k = 1:stf(i).numOfBixelsPerRay(j)
+ counter = counter + 1;
+ stf(i).ray(j).weight(k) = w(counter);
+ end
+ end
+ end
+ else % weights need to be in stf!
+ w = NaN*ones(sum([stf.totalNumOfBixels]),1);
+ counter = 0;
+ for i = 1:size(stf,2)
+ for j = 1:stf(i).numOfRays
+ for k = 1:stf(i).numOfBixelsPerRay(j)
+ counter = counter + 1;
+ w(counter) = stf(i).ray(j).weight(k);
+ end
+ end
+ end
+ end
+
+ %Set direct dose calculation and compute "dij"
+ this.directWeights = w;
+ this.calcDoseDirect = true;
+ dij = this.calcDose(ct,cst,stf);
+
+ % calculate cubes; use uniform weights here, weighting with actual fluence
+ % already performed in dij construction
+
+ resultGUI = [];
+
+ for i = 1:this.multScen.totNumScen
+ scenSubIx = this.multScen.linearMask(i,:);
+ resultGUItmp = matRad_calcCubes(ones(dij.numOfBeams,1),dij,this.multScen.sub2scenIx(scenSubIx(1),scenSubIx(2),scenSubIx(3)));
+ if i == 1
+ resultGUI = resultGUItmp;
+ end
+ resultGUI = matRad_appendResultGUI(resultGUI,resultGUItmp,false,sprintf('scen%d',i));
+ end
+
+ resultGUI.w = w;
+ end
+
+ function dij = calcDoseInfluence(this,ct,cst,stf)
+ this.calcDoseDirect = false;
+ dij = this.calcDose(ct,cst,stf);
+ end
+ function setDefaults(this)
+ % future code for property validation on creation here
+ matRad_cfg = MatRad_Config.instance();
+
+ %Assign default parameters from MatRad_Config
+ this.doseGrid = matRad_cfg.defaults.propDoseCalc.doseGrid;
+ this.multScen = 'nomScen';
+ this.selectVoxelsInScenarios = matRad_cfg.defaults.propDoseCalc.selectVoxelsInScenarios;
+ end
+ end
+
+ methods(Access = protected)
+
+ % method for setting and preparing the inition parameters for the
+ % dose calculation.
+ % Should be called at the beginning of calcDose method.
+ % Can be expanded or changed by overwriting this method and calling
+ % the superclass method inside of it
+ dij = initDoseCalc(this,ct,cst,stf)
+
+ % method for finalizing the dose calculation (e.g. postprocessing
+ % on dij or files
+ function dij = finalizeDose(this,dij)
+
+ matRad_cfg = MatRad_Config.instance();
+ %Close Waitbar
+ if any(ishandle(this.hWaitbar))
+ delete(this.hWaitbar);
+ end
+
+ this.timers.full = toc(this.timers.full);
+
+ matRad_cfg.dispInfo('Dose calculation finished in %g seconds!\n',this.timers.full);
+ end
+
+
+ function progressUpdate(this,pos,total)
+ % This function updates the progress of the dose calculation process.
+ % It can handle both absolute and relative progress updates.
+ % If only one argument is provided, it assumes a relative progress
+ % update from 0 to 1000. If two arguments are provided, it uses the
+ % actual values to calculate the progress percentage.
+
+ % Default total value handling
+ if nargin < 3
+ pos = pos*1000; % Assume pos is a relative progress if total is not provided
+ total=1000; % Set default total value
+ end
+
+ % Throttle progress updates to not overload the UI or log
+ % Only update if the last update was more than 0.1 seconds ago
+ % This prevents excessive updates that could slow down the calculation
+ if pos ~= total && toc(this.lastProgressUpdate) < 1e-1
+ return; % Skip the update if it's too soon
+ end
+
+ % Get the global configuration instance
+ matRad_cfg = MatRad_Config.instance();
+
+ % Log progress if the log level is high enough
+ % This allows for detailed tracking of the calculation progress in logs
+ if matRad_cfg.logLevel > 2
+ matRad_progress(pos,total); % Log the progress
+ end
+
+ % Update the waitbar with the current progress if it exists
+ % This provides a visual feedback of the calculation progress to the user
+ if any(ishandle(this.hWaitbar))
+ waitbar(pos/total,this.hWaitbar); % Update the waitbar
+ end
+
+ % Reset the timer for the next progress update
+ this.lastProgressUpdate = tic;
+ end
+ end
+
+ % Should be abstract methods but in order to satisfy the compatibility
+ % with OCTAVE we can't use abstract methods. If OCTAVE at some point
+ % in the far future implements this feature this should be abstract again.
+ methods (Access = protected) %(Abstract)
+ % the actual calculation method wich returns the final dij struct.
+ % Needs to be implemented in non abstract subclasses.
+ % (Internal logic is often split into multiple methods in order to make the whole calculation more modular)
+ function dij = calcDose(this,ct,cst,stf)
+ throw(MException('MATLAB:class:AbstractMember','Abstract function calcDose of your DoseEngine needs to be implemented!'));
+ end
+ end
+
+ methods(Static)
+ [nameList, classList, handleList] = getAvailableEngines(pln,optionalPath)
+
+ function [available,msg] = isAvailable(pln,machine)
+ % return a boolean if the engine is is available for the given pln
+ % struct. Needs to be implemented in non abstract subclasses
+ % input:
+ % - pln: matRad pln struct
+ % - machine: optional machine to avoid loading the machine from
+ % disk (makes sense to use if machine already loaded)
+ % output:
+ % - available: boolean value to check if the dose engine is
+ % available for the given pln/machine
+ % - msg: msg to elaborate on availability. If not available,
+ % a msg string indicates an error during the check
+ % if available, indicates a warning that not all
+ % information was present in the machine file and
+ % approximations need to be made
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('This is an Abstract Base class! Function needs to be called for instantiable subclasses!');
+ end
+
+ % static factory method to create/get correct dose engine from pln
+ engine = getEngineFromPln(pln);
+
+ % Machine Loader
+ % Currently just uses the matRad function that asks for pln
+ function machine = loadMachine(radiationMode,machineName)
+ machine = matRad_loadMachine(struct('radiationMode',radiationMode,'machine',machineName));
+ end
+ end
+end
diff --git a/matRad/doseCalc/+DoseEngines/matRad_MonteCarloEngineAbstract.m b/matRad/doseCalc/+DoseEngines/matRad_MonteCarloEngineAbstract.m
new file mode 100644
index 000000000..0219b202a
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_MonteCarloEngineAbstract.m
@@ -0,0 +1,74 @@
+classdef (Abstract) matRad_MonteCarloEngineAbstract < DoseEngines.matRad_DoseEngineBase
+% matRad_MonteCarloEngineAbstract: abstract superclass for all dose calculation
+% engines which are based on monte carlo calculation
+% for more informations see superclass
+% DoseEngines.matRad_DoseEngineBase
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (SetAccess = public, GetAccess = public)
+
+ numHistoriesPerBeamlet; %number of histories per beamlet
+ numHistoriesDirect; %number of histories for a forward dose calculation
+
+ outputMCvariance; %boolean value to decide if variance information for the MC calculation should be scored
+
+ relativeDosimetricCutOff; %relative dosimetric cut-off (i.e., 1 - minimum relative dose-value to be stored)
+ end
+
+ methods
+
+ function this = matRad_MonteCarloEngineAbstract(pln)
+ if nargin < 1
+ pln = [];
+ end
+
+ % call superclass constructor
+ this = this@DoseEngines.matRad_DoseEngineBase(pln);
+ end
+
+ function setDefaults(this)
+ setDefaults@DoseEngines.matRad_DoseEngineBase(this);
+
+ % create config instance
+ matRad_cfg = MatRad_Config.instance();
+
+ %set number of particles simulated per pencil beam
+ this.numHistoriesPerBeamlet = matRad_cfg.defaults.propDoseCalc.numHistoriesPerBeamlet;
+ this.numHistoriesDirect = matRad_cfg.defaults.propDoseCalc.numHistoriesDirect;
+ this.outputMCvariance = matRad_cfg.defaults.propDoseCalc.outputMCvariance;
+
+ this.relativeDosimetricCutOff = matRad_cfg.defaults.propDoseCalc.dosimetricLateralCutOff;
+ end
+ end
+
+
+ %% Deprecated properties
+ properties (Dependent)
+ relDoseCutOff;
+ end
+
+ methods
+ function set.relDoseCutOff(this,relDoseCutOff_)
+ this.relativeDosimetricCutOff = 1 - relDoseCutOff_;
+ this.warnDeprecatedEngineProperty('relDoseCutOff','','relativeDosimetricCutOff');
+ end
+ function relDoseCutOff_ = get.relDoseCutOff(this)
+ relDoseCutOff_ = 1 - this.relativeDosimetricCutOff;
+ this.warnDeprecatedEngineProperty('relDoseCutOff','','relativeDosimetricCutOff');
+ end
+ end
+
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_ParticleAnalyticalBortfeldEngine.m b/matRad/doseCalc/+DoseEngines/matRad_ParticleAnalyticalBortfeldEngine.m
new file mode 100644
index 000000000..a3c53873c
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_ParticleAnalyticalBortfeldEngine.m
@@ -0,0 +1,390 @@
+classdef matRad_ParticleAnalyticalBortfeldEngine < DoseEngines.matRad_ParticlePencilBeamEngineAbstract
+ % matRad_DoseEngineParticlePB:
+ % Implements an engine for particle based dose calculation
+ % For detailed information see superclass matRad_DoseEngine
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2022 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % help edit
+
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ possibleRadiationModes = {'protons'}
+ name = 'Analytical Bortfeld Pencil-Beam';
+ shortName = 'AnalyticalPB';
+ end
+
+ properties (SetAccess = public, GetAccess = public)
+
+ % target material parameters (Water)
+ massDensity = 0.997; % Mass Density of the medium in (g/cm^3)
+ p = 1.77; % Exponent in the Bragg-Kleemann rule
+ alpha = 2.2*10^(-3); % Material-dependent constant in the Bragg-Kleemann rule
+ beta = 0.012; % Slope parameter of the linear fluence reduction
+ gammaNuc = 0.6; % Fraction of locally absorbed energy in nuclear interactions
+ Z = 10; % N of electrons per molecule (water)
+ MM = 18.01; % Molar mass in g/mol (water)
+ radLength = 36.3; % Radiation Length of the assumed medium
+
+ % Beam parameters
+ phi0 = 1; % Primary proton fluence
+ epsilonTail = 0.1; % (Small) fraction of primary fluence \phi_0 contributing to the linear "tail" of the energy spectrum
+ sigmaEnergy = 0.01; % sigma of the gaussian energy spectrum
+
+ modeWidth = true; % Boolean which defines a monoenergetic (0) and gaussian (1) energy spectrum
+ end
+
+ properties (Access = private, Constant)
+ epsilon0 = 8.854e-12; % Vacuum dielectric constant in (C^2/(N*m^2))
+ electronCharge = 1.602e-19; % Electron charge in (C)
+ avogadroNum = 6.022e23; % Avogadro number
+ end
+
+ methods
+
+ function this = matRad_ParticleAnalyticalBortfeldEngine(pln)
+ % Constructor
+ %
+ % call
+ % engine = DoseEngines.matRad_ParticleAnalyticalPencilBeamDoseEngine(ct,stf,pln,cst)
+ %
+ % input
+ % pln: matRad plan meta information struct
+
+ if nargin < 1
+ pln = [];
+ end
+
+ this = this@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(pln);
+
+ this.calcLET = false;
+ end
+ end
+
+ methods (Access = protected)
+ function dij = initDoseCalc(this,ct,cst,stf)
+
+ matRad_cfg = MatRad_Config.instance();
+
+ if this.calcLET == true
+ matRad_cfg.dispWarning('Engine does not support LET calculation! Disabling!');
+ this.calcLET = false;
+ end
+
+ if this.calcBioDose == true
+ matRad_cfg.dispWarning('Engine does not support BioDose calculation! Disabling!');
+ this.calcBioDose = false;
+ end
+
+ dij = this.initDoseCalc@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(ct,cst,stf);
+ end
+
+ function chooseLateralModel(this)
+ %Now check if we need tho chose the lateral model because it
+ %was set to auto
+ matRad_cfg = MatRad_Config.instance();
+ if strcmp(this.lateralModel,'auto')
+ this.lateralModel = 'single';
+ elseif ~strcmp(this.lateralModel,'single')
+ matRad_cfg.dispWarning('Engine only supports analytically computed singleGaussian lateral Model!');
+ this.lateralModel = 'single';
+ end
+ matRad_cfg.dispInfo('Using an analytically computed %s Gaussian pencil-beam kernel model!\n');
+ end
+
+ function [currBixel] = getBixelIndicesOnRay(this,currBixel,currRay)
+
+ % create offset vector to account for additional offsets modelled in the base data and a potential
+ % range shifter. In the following, we only perform dose calculation for voxels having a radiological depth
+ % that is within the limits of the base data set (-> machine.data(i).dephts). By this means, we only allow
+ % interpolations in this.calcParticleDoseBixel() and avoid extrapolations.
+ %urrBixel.offsetRadDepth = currBixel.baseData.offset + currBixel.radDepthOffset;
+ tmpOffset = currBixel.baseData.offset - currBixel.radDepthOffset;
+
+ maxDepth = 1.15 * currBixel.baseData.range;
+
+ % find depth depended lateral cut off
+ if this.dosimetricLateralCutOff == 1
+ currIx = currRay.radDepths <= maxDepth + tmpOffset;
+ elseif this.dosimetricLateralCutOff < 1 && this.dosimetricLateralCutOff > 0
+ currIx = currRay.radDepths <= maxDepth + tmpOffset;
+ sigmaSq = this.calcSigmaLatMCS(currRay.radDepths(currIx) - tmpOffset, currBixel.baseData.energy).^2 + currBixel.sigmaIniSq;
+ currIx(currIx) = currRay.radialDist_sq(currIx) < currBixel.baseData.LatCutOff.numSig.^2*sigmaSq;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Cutoff must be a value between 0 and 1!')
+ end
+
+ currBixel.subRayIx = currIx;
+ currBixel.ix = currRay.ix(currIx);
+ end
+
+ function X = interpolateKernelsInDepth(this,bixel)
+ baseData = bixel.baseData;
+
+ % calculate particle dose for bixel k on ray j of beam i
+ % convert from MeV cm^2/g per primary to Gy mm^2 per 1e6 primaries
+ conversionFactor = 1.6021766208e-02;
+
+ radDepthOffset = bixel.radDepthOffset;
+
+ if isfield(baseData,'energySpectrum')
+ energyMean = baseData.energySpectrum.mean;
+ energySpread = baseData.energySpectrum.sigma/100 * baseData.energySpectrum.mean;
+ else
+ energyMean = baseData.energy;
+ energySpread = baseData.energy * this.sigmaEnergy;
+ end
+
+ if isfield(baseData,'offset') && ~isfield(baseData,'energySpectrum')
+ radDepthOffset = radDepthOffset - baseData.offset;
+ end
+
+ X.Z = conversionFactor * this.calcAnalyticalBragg(energyMean, bixel.radDepths + radDepthOffset, energySpread);
+ X.sigma = this.calcSigmaLatMCS(bixel.radDepths + radDepthOffset, baseData.energy);
+ end
+
+ function bixel = calcParticleBixel(this,bixel)
+
+ kernel = this.interpolateKernelsInDepth(bixel);
+
+ %compute lateral sigma
+ sigmaSq = kernel.sigma.^2 + bixel.sigmaIniSq;
+
+ % calculate dose
+ bixel.physicalDose = bixel.baseData.LatCutOff.CompFac * exp( -bixel.radialDist_sq ./ (2*sigmaSq)) .* kernel.Z ./(2*pi*sigmaSq);
+
+ % check if we have valid dose values
+ if any(isnan(bixel.physicalDose)) || any(bixel.physicalDose<0)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Error in particle dose calculation.');
+ end
+ end
+
+ function doseVector = calcAnalyticalBragg(this, primaryEnergy, depthZ, energySpread)
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % call
+ % this.calcAnalyticalBragg(PrimaryEnergy, depthz, WidthMod)
+ % ===========================================================
+ % Purpose: Compute depth-dose curve i.e. the Bragg Peak
+ % in 'Bortfeld 1998' formalism.
+ %
+ % Input : primaryEnergy -- Parameter (primaryEnergy > 0, it
+ % is the primary energy of the beam
+ % )
+ % depthZ --------- Argument (depthZ > 0,
+ % depth in the target material).
+ % energySpread -------
+ % Energy Spread
+ % Output : doseVector ----- Depth dose curve; same size of
+ % depthZ
+ % ===========================================================
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % This function was inspired by the paper from
+ % Thomas Bortfeld (1997) "An analytical approximation of the
+ % Bragg curve for therapeutic proton beams".
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ numberDensity = this.massDensity*this.avogadroNum/this.MM; % Number density of molecules per cm^3
+ alphaPrime = this.electronCharge^2*numberDensity*this.Z/(4*pi*this.epsilon0^2)/10^8; % Bohr's formula for d(sigmaE)^2/dz
+
+
+ % Conversion of depth value from mm to cm
+ depthZ = depthZ./10;
+
+ % Compute Range and sigma
+ range = this.alpha*primaryEnergy.^this.p; % Range-Energy relation, i.e. Bragg-Kleemann rule
+ sigmaMonoSquared = alphaPrime*this.p^2*this.alpha^(2/this.p)*range.^(3-2/this.p)./(3-2/this.p); % Squared Range straggling width
+ sigmaMono = sqrt(sigmaMonoSquared); % Range straggling width
+
+ % Compute the width of straggling, determined by widthMod
+ sigmaTot = sqrt( sigmaMonoSquared + (energySpread^2) .*(this.alpha^2) .*(this.p^2) .*(primaryEnergy.^(2*this.p-2)) ); % Total straggling contribution: range + energy
+ wid = sigmaTot;
+
+
+ % COEFFICIENTS IN THE BRAGG CURVE (WITHOUT STRAGGLING)
+ coeffA = this.phi0*(1-this.epsilonTail)./(this.massDensity*this.p*this.alpha^(1/this.p)*(1+this.beta*range));
+ coeffA1 = coeffA; % Coefficient of D1
+ coeffA2 = coeffA*this.beta*(1+this.gammaNuc*this.p); % Coefficient of D2
+ coeffA3 = coeffA*this.epsilonTail*this.p./((1-this.epsilonTail)*range); % Coefficient of Dtail
+ coeffA23 = coeffA2 + coeffA3;
+
+ % Definition of the Depth - Dose curve without straggling
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % =======================================================
+ % Purpose: compute the depth-dose curve without energy
+ % straggling hatD(z, E) (see Bortfeld 1997)
+ % Input : depthZ --- Argument: depth in the target
+ % material
+ % range --- Parameter: range dependent on beam
+ % primaryEnergy
+ % Output : hatD = hatD(z, E)
+ % =======================================================
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ hatD1 = coeffA1.*(range-depthZ).^(1/this.p-1); % MCS contribution
+ hatD2 = coeffA23.*(range-depthZ).^(1/this.p); % Nuclear and MCS contribution
+ hatD = (hatD1+hatD2) .*(depthZ<=range)... % Total depth-dose curve without straggling
+ + 0 .*(depthZ>range);
+
+ % Depth-Dose curve, i.e. the Bragg peak
+
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % ===================================================================
+ % Purpose: compute the depth-dose curve i.e. the Bragg
+ % Peak, as defined in Bortfeld 1997 (D(z) in the
+ % paper). Using the Matlab parabolic cylinder
+ % function pu(a, z) = D_(-a-1/2)(x). In the
+ % first step, the product of gauss and parabolic
+ % cylinder function is computed.
+ % Output : depthDose = D(z) ( D(z) in Bortfeld 1997 implici-
+ % tly depends on E and width. )
+ % ===================================================================
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ functionGaussXCyl = @(a, x) exp(-x.^2/4).*pu(-a-1/2, x); %product of gaussian
+ % and "pu"
+
+ coeffD1 = coeffA1/wid; % coefficient
+ coeffD2 = coeffA23/this.p; % coefficient
+ coeffD = wid.^(1/this.p)*gamma(1/this.p)/sqrt(2*pi); % coefficient
+ depthDose = coeffD.*(coeffD1.*functionGaussXCyl(-1/this.p, (depthZ-range)/wid ) ...
+ + coeffD2.*functionGaussXCyl(-1/this.p-1, (depthZ-range)/wid ) );
+
+ % OUTPUT: compute dose vector
+
+ % Dose is computed with hatD in the plateau region, and with
+ % the parabolic cylinder function in the peak region.
+
+ isPlateau = depthZ < range-10*wid;
+ isPeak = depthZ >= range-10*wid & depthZ <= range+5*wid;
+
+ dosePlateau = isPlateau .* hatD;
+ dosePlateau(isnan(dosePlateau)) = 0;
+ dosePeak = isPeak .* depthDose;
+ dosePeak(isnan(dosePeak)) = 0;
+ doseVector = dosePlateau + dosePeak;
+ end
+
+ function sigmaMCS = calcSigmaLatMCS(this, depthZ, primaryEnergy)
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %call
+ % this.SigmaLatMSC_H(depthz, En)
+ % ===================================================================
+ % Purpose: Compute the lateral displacement of a particle beam due to
+ % Multiple Coulomb Scattering, as function of the depth in
+ % the target material and in Highland approximation.
+ % Input : PrimaryEnergy -- Parameter (PrimaryEnergy > 0, it is the
+ % primary energy of the beam)
+ % z -------------- Argument (z > 0, it is the actual
+ % depth in the target material)
+ % Output : displ ---------- SigmaLatMCS_H(z, E)
+ % ===================================================================
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % This function was inspired by the paper from Gottschalk et al.1992.
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ % Conversion of depth value from mm to cm
+ depthZ = depthZ./10;
+ %z = depthz;
+
+ range = this.alpha*primaryEnergy.^this.p; % Range-Energy relation, i.e. Bragg-Kleemann rule
+
+ sigma1 = @(z) 14.1^2 /this.radLength * (1+1/9*log10(z./this.radLength)).^2;
+ sigma21 = @(z) 1 ./(1-2/this.p) .*( range.^(1-2/this.p).*(range-z).^2 - (range-z).^(3-2/this.p) );
+ sigma22 = @(z) -2*(range-z) ./(2-2/this.p) .*( range.^(2-2/this.p) - (range-z).^(2-2/this.p) );
+ sigma23 = @(z) 1 ./(3-2/this.p) .*( range.^(3-2/this.p) - (range-z).^(3-2/this.p) );
+ sigmaTot = @(z) this.alpha^(1/this.p)/2 *sqrt( sigma1(z) .*( sigma21(z) + sigma22(z) + sigma23(z) ) );
+ sigmaBeyond = sigmaTot(range);
+
+
+ isBelowR = depthZ<=range;
+ isBeyondR = depthZ>range;
+ sigmaBelowRange = sigmaTot(depthZ) .* isBelowR;
+ sigmaBeyondRange = sigmaBeyond .*isBeyondR ;
+
+ sigmaMCS = 10.* (sigmaBelowRange + sigmaBeyondRange); %output in mm
+
+ sigmaMCS(depthZ==0) = 0;
+ end
+
+ function calcLateralParticleCutOff(this,cutOffLevel,~)
+ calcRange = false;
+ if ~isfield(this.machine.data,'range')
+ calcRange = true;
+ end
+
+ if ~any(strcmp(this.cutOffMethod,{'integral','relative'}))
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('LateralParticleCutOff: Invalid Cutoff Method. Must be ''integral'' or ''relative''!');
+ end
+
+ for i = 1:numel(this.machine.data)
+ this.machine.data(i).LatCutOff.CompFac = 1/cutOffLevel;
+ this.machine.data(i).LatCutOff.numSig = sqrt(-2*log(1-cutOffLevel));
+ this.machine.data(i).LatCutOff.maxSigmaIni = max([this.machine.data(i).initFocus(:).SisFWHMAtIso]) ./ 2.3548;
+ if calcRange
+ this.machine.data(i).range = 10 * this.alpha*this.machine.data(i).energy.^this.p;
+ end
+ this.machine.data(i).LatCutOff.CutOff = this.machine.data(i).LatCutOff.numSig * sqrt(this.machine.data(i).LatCutOff.maxSigmaIni^2 + this.calcSigmaLatMCS(this.machine.data(i).range,this.machine.data(i).energy)^2);
+ end
+ end
+ end
+
+ methods (Static)
+
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ msg = [];
+ available = false;
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.possibleRadiationModes, machine.meta.radiationMode));
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ checkMeta = all(isfield(machine.meta,{'SAD','BAMStoIsoDist','LUT_bxWidthminFWHM','dataType'}));
+
+ dataType = machine.meta.dataType;
+ if strcmp(dataType,'singleGauss')
+ checkData = all(isfield(machine.data,{'energy','depths','Z','peakPos','sigma','offset','initFocus'}));
+ elseif strcmp(dataType,'doubleGauss')
+ checkData = all(isfield(machine.data,{'energy','depths','Z','peakPos','weight','sigma1','sigma2','offset','initFocus'}));
+ else
+ checkData = false;
+ end
+
+ available = checkMeta && checkData;
+ end
+ end
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_ParticleFineSamplingPencilBeamEngine.m b/matRad/doseCalc/+DoseEngines/matRad_ParticleFineSamplingPencilBeamEngine.m
new file mode 100644
index 000000000..4b47aa225
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_ParticleFineSamplingPencilBeamEngine.m
@@ -0,0 +1,442 @@
+classdef matRad_ParticleFineSamplingPencilBeamEngine < DoseEngines.matRad_ParticlePencilBeamEngineAbstract
+% matRad_ParticlePencilBeamEngineAbstractFineSampling:
+% Implements an engine for particle based dose calculation
+% For detailed information see superclass matRad_DoseEngine
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% help edit
+
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ possibleRadiationModes = {'protons', 'helium','carbon'}
+ name = 'Subsampling Particle Pencil-Beam';
+ shortName = 'SubsamplingPB';
+ end
+
+ properties (SetAccess = public, GetAccess = public)
+ fineSampling; % Struct with finesampling properties
+ end
+
+ methods
+
+ function this = matRad_ParticleFineSamplingPencilBeamEngine(pln)
+ % Constructor
+ %
+ % call
+ % engine = DoseEngines.matRad_ParticleAnalyticalPencilBeamDoseEngine(ct,stf,pln,cst)
+ %
+ % input
+ % pln: matRad plan meta information struct
+
+ if nargin < 1
+ pln = [];
+ end
+
+ this = this@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(pln);
+ end
+
+ function setDefaults(this)
+ setDefaults@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(this);
+
+ matRad_cfg = MatRad_Config.instance();
+ this.fineSampling = matRad_cfg.defaults.propDoseCalc.fineSampling;
+ this.fineSampling.method = 'fitCircle';
+ this.fineSampling.N = 2;
+ end
+ end
+
+ methods (Access = protected)
+
+ function ray = initRay(this,beam,j)
+ ray = initRay@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(this,beam,j);
+
+ %We need some more beam information here
+ ray.rotMat_system_T = beam.rotMat_system_T;
+ end
+
+ % We override this function to get full lateral distances
+ function ray = getRayGeometryFromBeam(this,ray,currBeam)
+ lateralRayCutOff = this.getLateralDistanceFromDoseCutOffOnRay(ray);
+
+ % Ray tracing for beam i and ray j
+ [ix,radialDist_sq,latDists] = this.calcGeoDists(currBeam.bevCoords, ...
+ ray.sourcePoint_bev, ...
+ ray.targetPoint_bev, ...
+ ray.SAD, ...
+ currBeam.validCoordsAll, ...
+ lateralRayCutOff);
+
+ ray.validCoords = cellfun(@(beamIx) beamIx & ix,currBeam.validCoords,'UniformOutput',false);
+ ray.ix = cellfun(@(ixInGrid) this.VdoseGrid(ixInGrid),ray.validCoords,'UniformOutput',false);
+
+ %subCoords = cellfun(@(beamIx) beamIx(ix),currBeam.validCoords,'UniformOutput',false);
+ %ray.radialDist_sq = cellfun(@(subix) radialDist_sq(subix),radialDist_sq,subCoords);
+ ray.radialDist_sq = cellfun(@(beamIx) radialDist_sq(beamIx(ix)),currBeam.validCoords,'UniformOutput',false);
+ ray.latDists = cellfun(@(beamIx) latDists(beamIx(ix),:),currBeam.validCoords,'UniformOutput',false);
+
+ ray.validCoordsAll = any(cell2mat(ray.validCoords),2);
+
+ ray.geoDepths = cellfun(@(rD,ix) rD(ix),currBeam.geoDepths,ray.validCoords,'UniformOutput',false); %usually not needed for particle beams
+ ray.radDepths = cellfun(@(rD,ix) rD(ix),currBeam.radDepths,ray.validCoords,'UniformOutput',false);
+ end
+
+ function bixel = initBixel(this,currRay,k)
+ bixel = initBixel@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(this,currRay,k);
+
+ bixel.latDists = currRay.latDists(bixel.subRayIx,:);
+
+ % Given the initial sigmas of the sampling ray, this
+ % function provides the weights for the sub-pencil beams,
+ % their positions and their sigma used for dose calculation
+ if (this.fineSampling.sigmaSub^2 < bixel.sigmaIniSq) && (this.fineSampling.sigmaSub > 0)
+ [bixel.finalSubWeight, bixel.sigmaSub, bixel.subPosX, bixel.subPosZ, bixel.numOfSub] = ...
+ this.calcFineSamplingMixture(sqrt(bixel.sigmaIniSq));
+ else
+ matRad_cfg = MatRad_Config.instance();
+ if (this.fineSampling.sigmaSub < 0)
+ matRad_cfg.dispError('Chosen fine sampling sigma cannot be negative!');
+ elseif (this.fineSampling.sigmaSub > sqrt(bixel.sigmaIniSq))
+ matRad_cfg.dispError('Chosen fine sampling sigma is too high for defined plan!');
+ end
+ end
+
+ % calculate projected coordinates for fine sampling of
+ % each beamlet
+ projCoords = matRad_projectOnComponents(bixel.ix, size(this.radDepthCubes{currRay.beamIndex}), currRay.sourcePoint_bev,...
+ currRay.targetPoint_bev, matRad_world2cubeCoords(currRay.isoCenter,this.doseGrid),...
+ [this.doseGrid.resolution.x this.doseGrid.resolution.y this.doseGrid.resolution.z],...
+ -bixel.subPosX, -bixel.subPosZ, currRay.rotMat_system_T);
+
+
+ % interpolate radiological depths at projected
+ % coordinates
+ % TODO: we get NaN's here - why? (They come from the
+ % radDepthCubes, but I don't know why we interpolate at these
+ % positions)
+ bixel.radDepths = interp3(this.radDepthCubes{currRay.beamIndex},projCoords(:,1,:)./this.doseGrid.resolution.x,...
+ projCoords(:,2,:)./this.doseGrid.resolution.y,projCoords(:,3,:)./this.doseGrid.resolution.z,'nearest',0);
+
+
+ % compute radial distances relative to pencil beam
+ % component
+ %bixel.radialDist_sq = reshape(bsxfun(@plus,bixel.latDists(:,1),bixel.subPosX'),[],1,bixel.numOfSub).^2 + reshape(bsxfun(@plus,bixel.latDists(:,2),bixel.subPosZ'),[],1,bixel.numOfSub).^2;
+ bixel.radialDist_sq = reshape((bixel.latDists(:,1) + bixel.subPosX').^2 + (bixel.latDists(:,2) + bixel.subPosZ').^2,[],1,bixel.numOfSub);
+ end
+
+ function currBeam = initBeam(this,dij,ct,cst,stf,i)
+ % Method for initializing the beams for analytical pencil beam
+ % dose calculation
+ %
+ % call
+ % this.initBeam(dij,ct,cst,stf,i)
+ %
+ % input
+ % dij: matRad dij struct
+ % ct: matRad ct struct
+ % cst: matRad cst struct
+ % stf: matRad steering information struct
+ % i: index of beam
+ %
+ % output
+ % dij: updated dij struct
+
+ if ~this.keepRadDepthCubes
+ this.keepRadDepthCubes = true;
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('Keeping radiological depth cubes for fine-sampling!');
+ end
+
+ currBeam = initBeam@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(this,dij,ct,cst,stf,i);
+ end
+
+ function kernels = interpolateKernelsInDepth(this,bixel)
+ kernels = interpolateKernelsInDepth@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(this,bixel);
+ kernels = structfun(@(x) reshape(x,size(bixel.radDepths)),kernels,'UniformOutput',false);
+ end
+
+ function bixel = calcParticleBixel(this,bixel)
+ kernel = this.interpolateKernelsInDepth(bixel);
+ % initialise empty dose array
+ bixel.physicalDose = zeros(size(bixel.ix,1),1);
+
+ if this.calcLET
+ bixel.mLETDose = zeros(size(bixel.physicalDose));
+ end
+
+ %We only have one bixel
+ if ~isfield(bixel,'numOfSub')
+ bixel.numOfSub = 1;
+ bixel.finalSubWeight = 1;
+ bixel.sigmaSub = sqrt(bixel.sigmaIniSq);
+ end
+
+ bixel.sigmaSubSq = bixel.sigmaSub.^2;
+
+ dg = ~isfield(bixel.baseData,'sigma');
+
+ if dg
+ % compute lateral sigmas
+ sigmaSqNarrow = squeeze(kernel.sigma1).^2 + bixel.sigmaSubSq';
+ sigmaSqBroad = squeeze(kernel.sigma2).^2 + bixel.sigmaSubSq';
+
+ % calculate lateral profile
+ L_Narr = exp( -squeeze(bixel.radialDist_sq) ./ (2*sigmaSqNarrow))./(2*pi*sigmaSqNarrow);
+ L_Bro = exp( -squeeze(bixel.radialDist_sq) ./ (2*sigmaSqBroad ))./(2*pi*sigmaSqBroad );
+ L = (1-squeeze(kernel.weight)).*L_Narr + squeeze(kernel.weight).*L_Bro;
+ else
+ %compute lateral sigma
+ sigmaSq = squeeze(kernel.sigma).^2 + bixel.sigmaSubSq';
+ L = exp( -squeeze(bixel.radialDist_sq) ./ (2*sigmaSq))./ (2*pi*sigmaSq);
+ end
+
+ tmpDose = (L .* squeeze(kernel.Z));
+
+ bixel.physicalDose = bixel.baseData.LatCutOff.CompFac*(tmpDose*bixel.finalSubWeight);
+
+ nanIx = isnan(bixel.physicalDose);
+ bixel.pyhsicalDose(nanIx) = 0;
+ if this.calcLET
+ tmpLET = bixel.baseData.LatCutOff.CompFac*((tmpDose .* squeeze(kernel.LET)) *bixel.finalSubWeight);
+ bixel.mLETDose(~nanIx) = bixel.mLETDose(~nanIx) + tmpLET(~nanIx);
+ end
+
+ if this.calcBioDose
+ bixel.mAlphaDose = bixel.physicalDose;
+ bixel.mSqrtBetaDose = bixel.physicalDose;
+ %From matRad_calcLQParameter
+ numOfTissueClass = size(bixel.baseData.alpha,2);
+ for i = 1:numOfTissueClass
+ mask = bixel.vTissueIndex == i;
+ if any(mask)
+ bixel.mAlphaDose(mask) = bixel.mAlphaDose(mask) .* X.alpha(mask);
+ bixel.mSqrtBetaDose(mask) = bixel.mSqrtBetaDose(mask) .* X.beta(mask);
+ end
+ end
+ end
+
+ end
+
+ function [finalWeight, sigmaBeamlet, posX, posY, numOfSub] = calcFineSamplingMixture(this,sigmaTot)
+ % This function creates a Gaussian Mixture Model on a Gaussian
+ % for Fine-Sampling
+ %
+ % call
+ % [finalWeight, sigmaBeamlet, posX, posY, numOfSub] = ...
+ % this.calcFineSamplingMixture(sigmaTot)
+ %
+ % input
+ % sigmaTot: the standard deviation of the lateral spread of the pencil
+ % beam
+ % output
+ % finalWeight: is the array of the weights of the sub-pencil beams. It
+ % runs over the same index as posx and posy
+ %
+ % posX & posY: are the positions of the sub-pencil beams, returned as
+ % meshgrid-matrices if method is 'square' and as vectors
+ % if method is 'circle'
+ % numOfSub: number of sub-pencil beams
+ %
+ % References
+ % [1] https://iopscience.iop.org/article/10.1088/0031-9155/61/1/183
+
+
+ % method: method of weight calculation
+ % 'russo' for integral calculation according to [1]
+ % 'fitCircle' for using square grid weights derived from a fit
+ % 'fitSquare' for using circular grid weights derived from a fit
+ method = this.fineSampling.method;
+
+ % N: if method == russo:
+ % number of subsample beams shells. Means we have a
+ % grid of NxN sub beams representing the total beam
+ % if method == fitCircle or fitSquare:
+ % number of subsample beams shells. n = 2 means we have two
+ % lines of points around the central ray. The number of
+ % sub-beams will be:
+ % #sb = (2*n +1)^2 for the square;
+ % #sb = (2^n -1)*6 +1 for the circle
+ N = this.fineSampling.N;
+
+ % sigmaSub: is the sigma of the gaussian of the sub-beams (only
+ % needed for russo method)
+ %
+ sigmaSub = this.fineSampling.sigmaSub;
+
+ if ~strcmp(method, 'russo') && ~strcmp(method, 'fitCircle') && ~strcmp(method, 'fitSquare')
+
+ error('method not supported');
+
+ elseif strcmp(method, 'russo')
+ % splitting into N^2 sub beams accoring to Russo et al (2016)
+
+ sigmaHead = sqrt(sigmaTot^2 - sigmaSub^2);
+
+ gauss = @(sigma, x, y, muX, muY) 1 / (2 * pi * sigma^2) .* exp(-((x + muX).^2 + (y + muY).^2) / (2 * sigma^2));
+
+ R = 3.5 * sigmaHead;
+ dR = 2 * R / N;
+
+ counter = 1;
+ for iX = -(N - 1) / 2 : (N - 1) / 2
+ for iY = -(N - 1) / 2 : (N - 1) / 2
+ if (iX*dR)^2 + (iY*dR)^2 > R^2
+ continue;
+ end
+
+ finalWeight(counter) = integral2(@(x,y) gauss(sigmaHead, x, y, 0, 0), ...
+ (iX - 0.5) * dR, (iX + 0.5) * dR, ...
+ (iY - 0.5) * dR, (iY + 0.5) * dR);
+ posX(counter) = iX * dR;
+ posY(counter) = iY * dR;
+ sigmaBeamlet(counter) = sigmaSub;
+
+ counter = counter + 1;
+ end
+ end
+
+ finalWeight = finalWeight';
+ finalWeight = finalWeight*1/sum(finalWeight);
+ posX = posX';
+ posY = posY';
+ sigmaBeamlet = sigmaBeamlet';
+
+
+ numOfSub = numel(finalWeight);
+
+ elseif strcmp(method, 'fitCircle') || strcmp(method, 'fitSquare')
+ % number of sub beams will be (2*n +1)^2 for the square;
+ % (2^n -1)*6 +1 for the circle
+ if N~=2 && N~=3 && N~=8
+ error('number of shells N not supported');
+ end
+
+ % This parameters come from simulations done previously
+ % see "Research on the dosimetric accuracy of pencil beam fine sampling
+ % for radiation proton dose calculation" by Giuseppe Pezzano (2018)
+ if N == 2
+ if strcmp(method,'fitCircle')
+ sigmaBeamlet = 0.8237 .* sigmaTot;
+ radius = 0.6212 .* sigmaTot;
+ X1(1,:) = 0.3866 .* sigmaTot.^2;
+ X1(2,:) = 0.6225 .* sigmaTot;
+ elseif strcmp(method,'fitSquare')
+ sigmaBeamlet = 0.8409 .* sigmaTot;
+ radius = 0.5519 .* sigmaTot;
+ X1(1,:) = 0.3099 .* sigmaTot.^2;
+ X1(2,:) = 0.5556 .* sigmaTot;
+ end
+ elseif N == 3
+ if strcmp(method,'fitCircle')
+ sigmaBeamlet = 0.7605 .* sigmaTot;
+ radius = 0.5000 .* sigmaTot;
+ X1(1,:) = 0.3006 .* sigmaTot.^2 - 1.3005 .* sigmaTot + 7.3097;
+ X1(2,:) = 0.6646 .* sigmaTot - 0.0044;
+ elseif strcmp(method,'fitSquare')
+ sigmaBeamlet = 0.8409 .* sigmaTot;
+ radius = 0.5391 .* sigmaTot + 0.0856;
+ X1(1,:) = 0.3245 .* sigmaTot.^2 + 0.0001 .* sigmaTot - 0.0004;
+ X1(2,:) = 0.6290 .* sigmaTot - 0.0403;
+ end
+ elseif N == 8 && strcmp(method,'fitCircle')
+ sigmaBeamlet = 0.5 .* sigmaTot;
+ radius = 0.25 .* sigmaTot;
+ X1(1,:) = 0.0334 .* sigmaTot.^2 - 4.1061e-06 .* sigmaTot + 1.5047e-06;
+ X1(2,:) = 0.6 .* sigmaTot + 3.3151e-06;
+ else
+ error('number of shells N not supported');
+ end
+
+ % setting positions of sub-beams
+ if strcmp(method,'fitSquare')
+ numOfSub = (2*N +1)^2;
+ points = linspace(-radius*N,radius*N,2*N +1);
+ posX = points'*ones(1,2*N +1);
+ posY = posX';
+ else
+ dim = size(radius,2);
+ numOfSub = (2^N -1)*6 +1;
+ ang = zeros(1,1);
+ posX = zeros(1,dim);
+ posY = zeros(1,dim);
+ radiusShell = zeros(1,dim);
+ for i = 1:N
+ subsInShell = 6 * 2^(i-1);
+ % this takes the sub-beams index in one shell
+ ang = cat(2, ang, pi .* linspace(0,2-2/subsInShell, subsInShell));
+ radiusShell = cat(1, radiusShell, ones(subsInShell,1)*(i.*radius));
+ end
+ posX = cat(1, posX, bsxfun(@times,cos(ang(2:end))',radiusShell(2:end,:)));
+ posY = cat(1, posY, bsxfun(@times,sin(ang(2:end))',radiusShell(2:end,:)));
+ end
+
+ % compute weights at positions
+ sig = ones(size(posX,1),1)*X1(2,:);
+ normSig = ones(size(posX,1),1)*X1(1,:);
+
+ finalWeight = normSig .* (2.*pi.*sig.^2).^(-1) .* exp(-(posX.^2+posY.^2)./(2.*(sig.^2)));
+ finalWeight = reshape(finalWeight, numel(finalWeight), 1);
+ sigmaBeamlet = repmat(reshape(sigmaBeamlet, numel(sigmaBeamlet), 1), numel(finalWeight),1);
+ posX =reshape(posX, numel(posX), 1);
+ posY =reshape(posY, numel(posY), 1);
+
+ end
+
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ msg = [];
+ available = false;
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(DoseEngines.matRad_ParticleFineSamplingPencilBeamEngine.possibleRadiationModes, machine.meta.radiationMode));
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ checkMeta = all(isfield(machine.meta,{'SAD','BAMStoIsoDist','LUT_bxWidthminFWHM','dataType'}));
+
+ dataType = machine.meta.dataType;
+ if strcmp(dataType,'singleGauss')
+ checkData = all(isfield(machine.data,{'energy','depths','Z','peakPos','sigma','offset','initFocus'}));
+ elseif strcmp(dataType,'doubleGauss')
+ checkData = all(isfield(machine.data,{'energy','depths','Z','peakPos','weight','sigma1','sigma2','offset','initFocus'}));
+ else
+ checkData = false;
+ end
+
+ available = checkMeta & checkData;
+ end
+ end
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_ParticleHongPencilBeamEngine.m b/matRad/doseCalc/+DoseEngines/matRad_ParticleHongPencilBeamEngine.m
new file mode 100644
index 000000000..51575de4c
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_ParticleHongPencilBeamEngine.m
@@ -0,0 +1,147 @@
+classdef matRad_ParticleHongPencilBeamEngine < DoseEngines.matRad_ParticlePencilBeamEngineAbstract
+% matRad_ParticlePencilBeamEngineAbstractGaussian:
+% Implements an engine for particle based dose calculation
+% For detailed information see superclass matRad_DoseEngine
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% help edit
+
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ possibleRadiationModes = {'protons', 'helium','carbon'}
+ name = 'Hong Particle Pencil-Beam';
+ shortName = 'HongPB';
+ end
+
+ methods
+
+ function this = matRad_ParticleHongPencilBeamEngine(pln)
+ % Constructor
+ %
+ % call
+ % engine = DoseEngines.matRad_ParticleAnalyticalPencilBeamDoseEngine(ct,stf,pln,cst)
+ %
+ % input
+ % pln: matRad plan meta information struct
+
+ if nargin < 1
+ pln = [];
+ end
+
+ this = this@DoseEngines.matRad_ParticlePencilBeamEngineAbstract(pln);
+ end
+
+ end
+
+ methods (Access = protected)
+
+ function bixel = calcParticleBixel(this,bixel)
+ kernels = this.interpolateKernelsInDepth(bixel);
+
+ %Lateral Component
+ switch this.lateralModel
+ case 'single'
+ %compute lateral sigma
+ sigmaSq = kernels.sigma.^2 + bixel.sigmaIniSq;
+ L = exp( -bixel.radialDist_sq ./ (2*sigmaSq))./ (2*pi*sigmaSq);
+ case 'double'
+ % compute lateral sigmas
+ sigmaSqNarrow = kernels.sigma1.^2 + bixel.sigmaIniSq;
+ sigmaSqBroad = kernels.sigma2.^2 + bixel.sigmaIniSq;
+
+ % calculate lateral profile
+ L_Narr = exp( -bixel.radialDist_sq ./ (2*sigmaSqNarrow))./(2*pi*sigmaSqNarrow);
+ L_Bro = exp( -bixel.radialDist_sq./ (2*sigmaSqBroad ))./(2*pi*sigmaSqBroad );
+ L = (1-kernels.weight).*L_Narr + kernels.weight.*L_Bro;
+ case 'multi'
+ sigmaSq = kernels.sigmaMulti.^2 + bixel.sigmaIniSq;
+ L = sum([1 - sum(kernels.weightMulti,2), kernels.weightMulti] .* exp(-bixel.radialDist_sq ./ (2*sigmaSq))./(2*pi*sigmaSq),2);
+ otherwise
+ %Sanity check
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid Lateral Model');
+ end
+
+ if length(bixel.baseData.LatCutOff.CompFac) > 1
+ bixel.baseData.LatCutOff.CompFac = matRad_interp1(bixel.baseData.LatCutOff.depths', bixel.baseData.LatCutOff.CompFac', bixel.radDepths);
+ end
+ bixel.physicalDose = bixel.baseData.LatCutOff.CompFac .* L .* kernels.Z;
+
+ % check if we have valid dose values
+ if any(isnan(bixel.physicalDose)) || any(bixel.physicalDose<0)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Error in particle dose calculation.');
+ end
+
+ if this.calcLET
+ bixel.mLETDose = bixel.physicalDose.*kernels.LET;
+ end
+
+ if this.calcBioDose
+ [bixelAlpha,bixelBeta] = this.bioParam.calcLQParameterForKernel(bixel,kernels);
+
+ bixel.mAlphaDose = bixel.physicalDose .* bixelAlpha;
+ bixel.mSqrtBetaDose = bixel.physicalDose .* sqrt(bixelBeta);
+ end
+ end
+
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ msg = [];
+ available = false;
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(DoseEngines.matRad_ParticleHongPencilBeamEngine.possibleRadiationModes, machine.meta.radiationMode));
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ checkMeta = all(isfield(machine.meta,{'SAD','BAMStoIsoDist','LUT_bxWidthminFWHM','dataType'}));
+
+ dataType = machine.meta.dataType;
+ if strcmp(dataType,'singleGauss')
+ checkData = all(isfield(machine.data,{'energy','depths','Z','peakPos','sigma','offset','initFocus'}));
+ elseif strcmp(dataType,'doubleGauss')
+ checkData = all(isfield(machine.data,{'energy','depths','Z','peakPos','weight','sigma1','sigma2','offset','initFocus'}));
+ elseif strcmp(dataType,'multipleGauss')
+ checkData = all(isfield(machine.data,{'energy','depths','Z','peakPos','weightMulti','sigmaMulti','offset','initFocus'}));
+ else
+ checkData = false;
+ end
+
+ available = checkMeta && checkData;
+ end
+ end
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_ParticleMCsquareEngine.m b/matRad/doseCalc/+DoseEngines/matRad_ParticleMCsquareEngine.m
new file mode 100644
index 000000000..51b7984b2
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_ParticleMCsquareEngine.m
@@ -0,0 +1,952 @@
+classdef matRad_ParticleMCsquareEngine < DoseEngines.matRad_MonteCarloEngineAbstract
+% Engine for particle dose calculation using monte carlo calculation
+% specificly the mc square method
+% for more informations see superclass
+% DoseEngines.matRad_MonteCarloEngineAbstract
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ possibleRadiationModes = {'protons'};
+ name = 'MCsquare';
+ shortName = 'MCsquare';
+ end
+
+ properties
+ config; %Holds an instance of all configurable parameters (matRad_MCsquareConfig)
+ MCsquareFolder; %Folder to the MCsquare installation
+ workingDir; %Working directory for simulation
+ forceBDL = []; %Specify an existing BDL file to load
+
+ %Other Dose Calculation Properties
+ calcLET = true;
+ end
+
+ properties (SetAccess = protected, GetAccess = public)
+
+ currFolder = pwd; %folder path when set
+
+ mcSquareBinary; %Executable for mcSquare simulation
+ nbThreads; %number of threads for MCsquare, 0 is all available
+
+ constantRBE = NaN; % constant RBE value
+ end
+
+ methods
+
+ function this = matRad_ParticleMCsquareEngine(pln)
+ % Constructor
+ %
+ % call
+ % engine = DoseEngines.matRad_DoseEngineMCsquare(ct,stf,pln,cst)
+ %
+ % input
+ % ct: matRad ct struct
+ % stf: matRad steering information struct
+ % pln: matRad plan meta information struct
+ % cst: matRad cst struct
+
+ if nargin < 1
+ pln = [];
+ end
+
+ % call superclass constructor
+ this = this@DoseEngines.matRad_MonteCarloEngineAbstract(pln);
+
+ this.config = matRad_MCsquareConfig();
+
+ % check if bio optimization is needed and set the
+ % coresponding boolean accordingly
+ % TODO:
+ % This should not be handled here as an optimization property
+ % We should rather make optimization dependent on what we have
+ % decided to calculate here.
+ if nargin > 0
+ if (isfield(pln,'bioParam')&& isfield(pln.bioParam,'quantityOpt')&& ...
+ (isequal(pln.bioParam.quantityOpt,'effect') ||...
+ isequal(pln.bioParam.quantityOpt,'RBExD')) && ...
+ strcmp(pln.radiationMode,'carbon'))
+ this.calcBioDose = true;
+ elseif strcmp(pln.radiationMode,'protons') && isfield(pln,'bioParam') && ...
+ isfield(pln.bioParam,'quantityOpt') && isequal(pln.bioParam.quantityOpt,'RBExD')...
+ && isequal(pln.bioParam.model,'constRBE')
+ this.constantRBE = 1.1;
+ end
+ end
+ end
+
+ function setDefaults(this)
+ this.setDefaults@DoseEngines.matRad_MonteCarloEngineAbstract();
+
+ % future code for property validation on creation here
+ matRad_cfg = MatRad_Config.instance();
+ %Set Default MCsquare path
+ %Set folder
+ this.workingDir = fullfile(matRad_cfg.primaryUserFolder,'MCsquare');
+ this.MCsquareFolder = fullfile(matRad_cfg.matRadRoot,'thirdParty','MCsquare','bin');
+ end
+ end
+
+ methods(Access = protected)
+
+ function dij = calcDose(this,ct,cst,stf)
+ % matRad MCsqaure monte carlo photon dose calculation wrapper
+ % can be automaticly called through matRad_calcDose or
+ % matRad_calcParticleDoseMC
+ %
+ % nCase per Bixel and be either set by hand after creating the
+ % engine or over the matRad_calcPhotonDoseMC function while
+ % calling the calculation
+ %
+ % call
+ % dij = this.calcDose(ct,stf,pln,cst)
+ %
+ % input
+ % ct: matRad ct struct
+ % cst: matRad cst struct
+ % stf: atRad steering information struct
+ %
+ % output
+ % dij: matRad dij struct
+ %
+ % References
+ %
+ % https://aapm.onlinelibrary.wiley.com/doi/abs/10.1118/1.4943377
+ % http://www.openmcsquare.org/
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2019 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Now we can run initDoseCalc as usual
+ dij = this.initDoseCalc(ct,cst,stf);
+
+ % switch for using existing BDL file (e.g. to fit matRad basedata),
+ % or generate BDL file from matRad base data using MCsquareBDL
+ if ~isempty(this.forceBDL)
+ % use existing BDL file
+ bdFile = this.forceBDL;
+
+ else
+ % fit and create BDL file using selected machine file
+ bdFile = [this.machine.meta.machine '.txt'];
+
+ % Calculate MCsquare base data
+ % Argument stf is optional, if given, calculation only for energies given in stf
+ MCsquareBDL = matRad_MCsquareBaseData(this.machine);
+
+ %matRad_createMCsquareBaseDataFile(bdFile,machine,1);
+ bdlFolder = fullfile(this.workingDir,'BDL');
+ if ~exist(bdlFolder,'dir')
+ mkdir(bdlFolder);
+ end
+ bdFile = fullfile(bdlFolder,bdFile);
+
+ MCsquareBDL = MCsquareBDL.writeMCsquareData(bdFile);
+ MCsquareBDL = MCsquareBDL.saveMatradMachine('savedMatRadMachine');
+
+ end
+
+
+ % The offset of the dose grid of MCsquare
+ mcSquareAddIsoCenterOffset = [dij.doseGrid.resolution.x/2 dij.doseGrid.resolution.y/2 dij.doseGrid.resolution.z/2] ...
+ - [dij.ctGrid.resolution.x dij.ctGrid.resolution.y dij.ctGrid.resolution.z];
+
+ % for MCsquare we explicitly downsample the ct to the dose grid (might not
+ % be necessary in future MCsquare versions with separated grids)
+ for s = 1:dij.numOfScenarios
+ HUcube{s} = matRad_interp3(dij.ctGrid.x, dij.ctGrid.y', dij.ctGrid.z,ct.cubeHU{s}, ...
+ dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'linear');
+ end
+
+ % set absolute calibration factor
+ % convert from eV/g/primary to Gy 1e6 primaries
+ absCalibrationFactorMC2 = 1.602176e-19 * 1.0e+9;
+
+ MCsquareConfigFile = fullfile(this.workingDir,'MCsquareConfig.txt');
+ plnFile = fullfile(this.workingDir,'currBixels.txt');
+ ctFile = fullfile(this.workingDir,'MC2patientCT.mhd');
+ outputDir = fullfile(this.workingDir,'output');
+ HU_Density_Conversion_File = fullfile(this.MCsquareFolder,'Scanners','matRad_default','HU_Density_Conversion.txt'); % Name of the file containing HU to density conversion data. Default: HU_Density_Conversion.txt
+ HU_Material_Conversion_File = fullfile(this.MCsquareFolder,'Scanners','matRad_default','HU_Material_Conversion.txt'); % Name of the filecontaining HU to material conversion data. Default: HU_Material_Conversion.txt
+
+
+ %Format paths to always have slashes
+ if isequal(filesep,'\')
+ bdFileWrite = strrep(bdFile,'\','/');
+ plnFileWrite = strrep(plnFile,'\','/');
+ ctFileWrite = strrep(ctFile,'\','/');
+ MCsquareConfigFileWrite = strrep(MCsquareConfigFile,'\','/');
+ outputDirWrite = strrep(outputDir,'\','/');
+ HU_Density_Conversion_File_write = strrep(HU_Density_Conversion_File,'\','/');
+ HU_Material_Conversion_File_write = strrep(HU_Material_Conversion_File,'\','/');
+ else
+ bdFileWrite = bdFile;
+ plnFileWrite = plnFile;
+ ctFileWrite = ctFile;
+ MCsquareConfigFileWrite = MCsquareConfigFile;
+ outputDirWrite = outputDir;
+ HU_Density_Conversion_File_write = HU_Density_Conversion_File;
+ HU_Material_Conversion_File_write = HU_Material_Conversion_File;
+ end
+
+ % MCsquare settings
+ MCsquareConfigFile = MCsquareConfigFileWrite;
+
+ this.config.BDL_Plan_File = plnFileWrite;
+ this.config.BDL_Machine_Parameter_File = bdFileWrite;
+ this.config.Output_Directory = outputDirWrite;
+ this.config.HU_Density_Conversion_File = HU_Density_Conversion_File_write;
+ this.config.HU_Material_Conversion_File = HU_Material_Conversion_File_write;
+
+ this.config.CT_File = ctFileWrite;
+ this.config.Num_Threads = this.nbThreads;
+ this.config.RNG_Seed = 1234;
+ if this.calcDoseDirect
+ this.config.Num_Primaries = this.numHistoriesDirect;
+ else
+ this.config.Num_Primaries = this.numHistoriesPerBeamlet;
+ end
+
+ % turn simulation of individual beamlets
+ this.config.Beamlet_Mode = ~this.calcDoseDirect;
+ % turn of writing of full dose cube
+ this.config.Dose_MHD_Output = this.calcDoseDirect;
+ % turn on sparse output
+ this.config.Dose_Sparse_Output = ~this.calcDoseDirect;
+ % set threshold of sparse matrix generation
+ this.config.Dose_Sparse_Threshold = 1 - this.relativeDosimetricCutOff;
+
+ %Matrices for LET
+ if this.calcLET
+ this.config.LET_MHD_Output = this.calcDoseDirect;
+ this.config.LET_Sparse_Output = ~this.calcDoseDirect;
+ end
+
+ %Create X Y Z vectors if not present
+ ct = matRad_getWorldAxes(ct);
+
+
+ if this.multScen.totNumRangeScen > 1
+ matRad_cfg.dispWarning('Range shift scenarios are not yet implemented for Monte Carlo simulations.');
+ end
+
+
+ for scenarioIx = 1:this.multScen.totNumScen
+ %For direct dose calculation
+ totalWeights = 0;
+
+ % manipulate isocenter
+ isoCenterShift = this.multScen.isoShift(scenarioIx,:) + mcSquareAddIsoCenterOffset;
+
+ ctScen = this.multScen.linearMask(scenarioIx,1);
+ shiftScen = this.multScen.linearMask(scenarioIx,2);
+ rangeShiftScen = this.multScen.linearMask(scenarioIx,3);
+
+ if this.multScen.scenMask(ctScen,shiftScen,rangeShiftScen)
+
+
+ counter = 0;
+ for i = 1:length(stf)
+ %Create new stf for MCsquare with energy layer ordering and
+ %shifted scenario isocenter
+ stfMCsquare(i).isoCenter = matRad_world2cubeCoords(stf(i).isoCenter, ct) + isoCenterShift; %MCsquare uses the isoCenter in cubeCoords
+ stfMCsquare(i).gantryAngle = mod(180-stf(i).gantryAngle,360); %Different MCsquare geometry
+ stfMCsquare(i).couchAngle = stf(i).couchAngle;
+ stfMCsquare(i).energies = unique([stf(i).ray.energy]);
+ stfMCsquare(i).SAD = stf(i).SAD;
+
+ %Let's check if we have a unique or no range shifter, because MCsquare
+ %only allows one range shifter type per field which can be IN or OUT
+ %per spot
+ raShiField = [];
+ for j = 1:stf(i).numOfRays
+ if isfield(stf(i).ray(j),'rangeShifter')
+ raShiField = [raShiField stf(i).ray(j).rangeShifter(:).ID];
+ else
+ raShiField = [raShiField zeros(size(stf(i).ray(j).energies))];
+ end
+ end
+
+ raShiField = unique(raShiField); %unique range shifter
+ raShiField(raShiField == 0) = []; %no range shifter
+ if numel(raShiField) > 1
+ matRad_cfg.dispError('MCsquare does not support different range shifter IDs per field! Aborting.\n');
+ end
+
+ if ~isempty(raShiField)
+ stfMCsquare(i).rangeShifterID = raShiField;
+ stfMCsquare(i).rangeShifterType = 'binary';
+ else
+ stfMCsquare(i).rangeShifterID = 0;
+ stfMCsquare(i).rangeShifterType = 'binary';
+ end
+
+ % allocate empty target point container
+ for j = 1:numel(stfMCsquare(i).energies)
+ stfMCsquare(i).energyLayer(j).targetPoints = [];
+ stfMCsquare(i).energyLayer(j).numOfPrimaries = [];
+ stfMCsquare(i).energyLayer(j).MU = [];
+ stfMCsquare(i).energyLayer(j).rayNum = [];
+ stfMCsquare(i).energyLayer(j).bixelNum = [];
+ end
+
+ for j = 1:stf(i).numOfRays
+ for k = 1:stf(i).numOfBixelsPerRay(j)
+ counter = counter + 1;
+ dij.beamNum(counter) = i;
+ dij.rayNum(counter) = j;
+ dij.bixelNum(counter) = k;
+ end
+
+ for k = 1:numel(stfMCsquare(i).energies)
+ %Check if ray has a spot in the current energy layer
+ if any(stf(i).ray(j).energy == stfMCsquare(i).energies(k))
+ energyIx = find(stf(i).ray(j).energy == stfMCsquare(i).energies(k));
+ stfMCsquare(i).energyLayer(k).rayNum = [stfMCsquare(i).energyLayer(k).rayNum j];
+ stfMCsquare(i).energyLayer(k).bixelNum = [stfMCsquare(i).energyLayer(k).bixelNum energyIx];
+ stfMCsquare(i).energyLayer(k).targetPoints = [stfMCsquare(i).energyLayer(k).targetPoints; ...
+ -stf(i).ray(j).rayPos_bev(1) stf(i).ray(j).rayPos_bev(3)];
+
+ %Number of primaries depending on beamlet-wise or field-based compuation (direct dose calculation)
+ if this.calcDoseDirect
+ stfMCsquare(i).energyLayer(k).numOfPrimaries = [stfMCsquare(i).energyLayer(k).numOfPrimaries ...
+ round(stf(i).ray(j).weight(stf(i).ray(j).energy == stfMCsquare(i).energies(k))*this.numHistoriesDirect)];
+
+ stfMCsquare(i).energyLayer(k).MU = [stfMCsquare(i).energyLayer(k).MU ...
+ round(stf(i).ray(j).weight(stf(i).ray(j).energy == stfMCsquare(i).energies(k))*this.numHistoriesDirect)];
+
+ totalWeights = totalWeights + stf(i).ray(j).weight(stf(i).ray(j).energy == stfMCsquare(i).energies(k));
+ else
+ stfMCsquare(i).energyLayer(k).numOfPrimaries = [stfMCsquare(i).energyLayer(k).numOfPrimaries ...
+ this.numHistoriesPerBeamlet];
+
+ stfMCsquare(i).energyLayer(k).MU = [stfMCsquare(i).energyLayer(k).MU ...
+ this.numHistoriesPerBeamlet];
+ end
+
+ %Now add the range shifter
+ raShis = stf(i).ray(j).rangeShifter(energyIx);
+
+ %sanity check range shifters
+ raShiIDs = unique([raShis.ID]);
+ %raShiIDs = raShiIDs(raShiIDs ~= 0);
+
+ if ~isscalar(raShiIDs)
+ matRad_cfg.dispError('MCsquare only supports one range shifter setting (on or off) per energy! Aborting.\n');
+ end
+
+ stfMCsquare(i).energyLayer(k).rangeShifter = raShis(1);
+ end
+ end
+ end
+ end
+
+ % remember order
+ counterMCsquare = 0;
+ MCsquareOrder = NaN * ones(dij.totalNumOfBixels,1);
+ for i = 1:length(stf)
+ for j = 1:numel(stfMCsquare(i).energies)
+ for k = 1:numel(stfMCsquare(i).energyLayer(j).numOfPrimaries)
+ counterMCsquare = counterMCsquare + 1;
+ ix = find(i == dij.beamNum & ...
+ stfMCsquare(i).energyLayer(j).rayNum(k) == dij.rayNum & ...
+ stfMCsquare(i).energyLayer(j).bixelNum(k) == dij.bixelNum);
+
+ MCsquareOrder(ix) = counterMCsquare;
+ end
+ end
+ end
+
+ if any(isnan(MCsquareOrder))
+ matRad_cfg.dispError('Invalid ordering of Beamlets for MCsquare computation!');
+ end
+
+ %% Write config files
+ % write patient data
+ MCsquareBinCubeResolution = [dij.doseGrid.resolution.x ...
+ dij.doseGrid.resolution.y ...
+ dij.doseGrid.resolution.z];
+
+ this.writeMhd(HUcube{ctScen},MCsquareBinCubeResolution);
+
+ % write config file
+ this.writeInputFiles(MCsquareConfigFile,stfMCsquare);
+
+ %% MC computation and dij filling
+
+ % run MCsquare
+ mcSquareCall = [this.mcSquareBinary ' ' MCsquareConfigFile];
+ matRad_cfg.dispInfo(['Calling Monte Carlo Engine: ' mcSquareCall]);
+ if matRad_cfg.logLevel >= 3
+ [status,cmdout] = system(mcSquareCall,'-echo');
+ else
+ [status,cmdout] = system(mcSquareCall);
+ matRad_cfg.dispInfo(cmdout);
+ end
+ if status == 0
+ matRad_cfg.dispInfo('MCsquare exited successfully with status %d!',status);
+ else
+ matRad_cfg.dispInfo('MCsquare did not exit successfully with status %d! Results might be compromised!',status);
+ end
+
+ mask = false(dij.doseGrid.numOfVoxels,1);
+ mask(this.VdoseGrid) = true;
+
+ if this.calcDoseDirect
+ if abs(totalWeights-sum(this.directWeights)) > 1e-2
+ matRad_cfg.dispWarning('Sum of provided weights and weights used in MCsquare inconsistent!');
+ end
+ finalResultWeight = absCalibrationFactorMC2 * totalWeights;
+ else
+ finalResultWeight = absCalibrationFactorMC2;
+ end
+
+ % read sparse matrix
+ if ~this.calcDoseDirect
+ dij.physicalDose{ctScen,shiftScen,rangeShiftScen} = finalResultWeight * matRad_sparseBeamletsReaderMCsquare ( ...
+ [this.config.Output_Directory filesep 'Sparse_Dose.bin'], ...
+ dij.doseGrid.dimensions, ...
+ dij.totalNumOfBixels, ...
+ mask);
+
+ %Read sparse LET
+ if this.calcLET
+ dij.mLETDose{ctScen,shiftScen,rangeShiftScen} = dij.physicalDose{ctScen,shiftScen,rangeShiftScen} .* matRad_sparseBeamletsReaderMCsquare ( ...
+ [this.config.Output_Directory filesep 'Sparse_LET.bin'], ...
+ dij.doseGrid.dimensions, ...
+ dij.totalNumOfBixels, ...
+ mask);
+ end
+
+ % reorder influence matrix to comply with matRad default ordering
+ dij.physicalDose = cellfun(@(mx) mx(:,MCsquareOrder),dij.physicalDose,'UniformOutput',false);
+ if this.calcLET
+ dij.mLETDose = cellfun(@(mx) mx(:,MCsquareOrder),dij.mLETDose,'UniformOutput',false);
+ end
+ else
+ cube = this.readMhd('Dose.mhd');
+ dij.physicalDose{ctScen,shiftScen,rangeShiftScen} = sparse(this.VdoseGrid,ones(numel(this.VdoseGrid),1), ...
+ finalResultWeight * cube(this.VdoseGrid), ...
+ dij.doseGrid.numOfVoxels,1);
+
+ %Read LET cube
+ if this.calcLET
+ cube = this.readMhd('LET.mhd');
+ dij.mLETDose{ctScen,shiftScen,rangeShiftScen} = dij.physicalDose{ctScen,shiftScen,rangeShiftScen} .* sparse(this.VdoseGrid,ones(numel(this.VdoseGrid),1), ...
+ cube(this.VdoseGrid), ...
+ dij.doseGrid.numOfVoxels,1);
+ end
+
+ % Postprocessing for dij:
+ % This is already the combined dose over all bixels, so all parameters are 1 in this case
+ dij = rmfield(dij,'MCsquareCalcOrder');
+
+ dij.numOfBeams = 1;
+ dij.beamNum = 1;
+ dij.bixelNum = 1;
+ dij.rayNum = 1;
+ dij.totalNumOfBixels = 1;
+ dij.totalNumOfRays = 1;
+ dij.numOfRaysPerBeam = 1;
+ end
+
+
+ if this.config.Beamlet_Mode
+
+ end
+
+ matRad_cfg.dispInfo('Scenario %d of %d finished!\n',scenarioIx,this.multScen.totNumScen);
+
+ %% clear all data
+ %could also be moved to the "finalize" function
+ delete([this.config.CT_File(1:end-4) '.*']);
+ fullfile(this.workingDir,'currBixels.txt');
+ fullfile(this.workingDir,'MCsquareConfig.txt');
+
+ %For Octave temporarily disable confirmation for recursive rmdir
+ if strcmp(matRad_cfg.env,'OCTAVE')
+ rmdirConfirmState = confirm_recursive_rmdir(0);
+ end
+ rmdir(this.config.Output_Directory,'s');
+
+ %Reset to old confirmatoin state
+ if strcmp(matRad_cfg.env,'OCTAVE')
+ confirm_recursive_rmdir(rmdirConfirmState);
+ end
+
+ % cd back
+ cd(this.currFolder);
+ end
+ end
+
+ matRad_cfg.dispInfo('matRad: Simulation finished!\n');
+ %Finalize dose calculation
+ dij = this.finalizeDose(dij);
+
+ end
+
+ function setBinaries(this)
+ % setBinaries check if the binaries are available on the current
+ % machine and sets to the mcsquarebinary object property
+ %
+
+ [~,binaryFile] = this.checkBinaries();
+ this.mcSquareBinary = binaryFile;
+ end
+
+ function dij = initDoseCalc(this,ct,cst,stf)
+ %% Assingn and check parameters
+ matRad_cfg = MatRad_Config.instance();
+
+ % check if binaries are available
+ % Executables for simulation
+ this.setBinaries();
+
+ %Mex interface for import of sparse matrix
+ if ~this.calcDoseDirect && ~matRad_checkMexFileExists('matRad_sparseBeamletsReaderMCsquare')
+ matRad_cfg.dispWarning('Compiled sparse reader interface not found. Trying to compile it on the fly!');
+ try
+ matRad_compileMCsquareSparseReader();
+ catch MException
+ matRad_cfg.dispError('Could not find/generate mex interface for reading the sparse matrix. \nCause of error:\n%s\n Please compile it yourself.',MException.message);
+ end
+ end
+
+ % set and change to MCsquare binary folder
+ this.currFolder = pwd;
+ %fullfilename = mfilename('fullpath');
+
+ % cd to MCsquare folder (necessary for binary)
+ % TODO: Could be checked in a property setter function
+ if ~exist(this.MCsquareFolder,'dir')
+ matRad_cfg.dispError('MCsquare Folder does not exist!');
+ end
+ cd(this.MCsquareFolder);
+
+ %Check Materials
+ if ~exist([this.MCsquareFolder filesep 'Materials'],'dir') || ~exist(fullfile(this.MCsquareFolder,'Materials','list.dat'),'file')
+ matRad_cfg.dispInfo('First call of MCsquare: unzipping Materials...');
+ unzip('Materials.zip');
+ matRad_cfg.dispInfo('Done');
+ end
+
+ %% Call Superclass init function
+ % Since MCsquare 1.1 only allows similar resolution in x&y, we do some
+ % extra checks on that before calling the normal initDoseCalc. First, we make sure a
+ % dose grid resolution is set in the pln struct
+
+ % Now we check for different x/y
+ if this.doseGrid.resolution.x ~= this.doseGrid.resolution.y
+ this.doseGrid.resolution.x = mean([this.doseGrid.resolution.x this.doseGrid.resolution.y]);
+ this.doseGrid.resolution.y = this.doseGrid.resolution.x;
+ matRad_cfg.dispWarning('Anisotropic resolution in axial plane for dose calculation with MCsquare not possible\nUsing average x = y = %g mm\n',this.doseGrid.resolution.x);
+ end
+
+ dij = initDoseCalc@DoseEngines.matRad_MonteCarloEngineAbstract(this,ct,cst,stf);
+
+ %% Validate and preset some additional dij variables
+
+ % Explicitly setting the number of threads for MCsquare, 0 is all available
+ this.nbThreads = 0;
+
+ %Issue a warning when we have more than 1 scenario
+ if dij.numOfScenarios ~= 1
+ matRad_cfg.dispWarning('MCsquare is only implemented for single scenario use at the moment. Will only use the first Scenario for Monte Carlo calculation!');
+ end
+
+ % prefill ordering of MCsquare bixels
+ dij.MCsquareCalcOrder = NaN*ones(dij.totalNumOfBixels,1);
+
+ if ~isnan(this.constantRBE)
+ dij.RBE = this.constantRBE;
+ end
+ end
+
+ function writeInputFiles(obj,filename,stf)
+ % generate input files for MCsquare dose calcualtion from matRad
+ %
+ % call
+ % obj.writeInputFiles(filename,filename,stf)
+ %
+ % input
+ % filename: filename of the Configuration file
+ % stf: matRad steering information struct
+ %
+ % output
+ % -
+ %
+ % References
+ % [1] https://openreggui.org/git/open/REGGUI/blob/master/functions/io/convert_Plan_PBS.m
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2019 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ %% write overall configuration file
+ fileHandle = fopen(filename,'w');
+ obj.config.write(fileHandle);
+ fclose(fileHandle);
+
+ %% prepare steering file writing
+ numOfFields = length(stf);
+ if obj.config.Beamlet_Mode
+ totalMetersetWeightOfAllFields = 1;
+ else
+ totalMetersetWeightOfFields = NaN*ones(numOfFields,1);
+ for i = 1:numOfFields
+ totalMetersetWeightOfFields(i) = sum([stf(i).energyLayer.numOfPrimaries]);
+ end
+ totalMetersetWeightOfAllFields = sum(totalMetersetWeightOfFields);
+ end
+
+ %% write steering file
+
+ fileHandle = fopen(obj.config.BDL_Plan_File,'w');
+
+ fprintf(fileHandle,'#TREATMENT-PLAN-DESCRIPTION\n');
+ fprintf(fileHandle,'#PlanName\n');
+ fprintf(fileHandle,'matRad_bixel\n');
+ fprintf(fileHandle,'#NumberOfFractions\n');
+ fprintf(fileHandle,'1\n');
+ fprintf(fileHandle,'##FractionID\n');
+ fprintf(fileHandle,'1\n');
+ fprintf(fileHandle,'##NumberOfFields\n');
+ fprintf(fileHandle,[num2str(numOfFields) '\n']);
+ for i = 1:numOfFields
+ fprintf(fileHandle,'###FieldsID\n');
+ fprintf(fileHandle,[num2str(i) '\n']);
+ end
+ fprintf(fileHandle,'\n#TotalMetersetWeightOfAllFields\n');
+ fprintf(fileHandle,[num2str(totalMetersetWeightOfAllFields) '\n']);
+
+ for i = 1:numOfFields
+ fprintf(fileHandle,'\n#FIELD-DESCRIPTION\n');
+ fprintf(fileHandle,'###FieldID\n');
+ fprintf(fileHandle,[num2str(i) '\n']);
+ fprintf(fileHandle,'###FinalCumulativeMeterSetWeight\n');
+ if obj.config.Beamlet_Mode
+ finalCumulativeMeterSetWeight = 1/numOfFields;
+ else
+ finalCumulativeMeterSetWeight = totalMetersetWeightOfFields(i);
+ end
+ fprintf(fileHandle,[num2str(finalCumulativeMeterSetWeight) '\n']);
+ fprintf(fileHandle,'###GantryAngle\n');
+ fprintf(fileHandle,[num2str(stf(i).gantryAngle) '\n']);
+ fprintf(fileHandle,'###PatientSupportAngle\n');
+ fprintf(fileHandle,[num2str(stf(i).couchAngle) '\n']);
+ fprintf(fileHandle,'###IsocenterPosition\n');
+ fprintf(fileHandle,[num2str(stf(i).isoCenter) '\n']);
+ fprintf(fileHandle,'###NumberOfControlPoints\n');
+ numOfEnergies = numel(stf(i).energies);
+ fprintf(fileHandle,[num2str(numOfEnergies) '\n']);
+
+ %Range shfiter
+ if stf(i).rangeShifterID ~= 0
+ fprintf(fileHandle,'###RangeShifterID\n%d\n',stf(i).rangeShifterID);
+ fprintf(fileHandle,'###RangeShifterType\n%s\n',stf(i).rangeShifterType);
+ end
+
+ metersetOffset = 0;
+ fprintf(fileHandle,'\n#SPOTS-DESCRIPTION\n');
+ for j = 1:numOfEnergies
+ fprintf(fileHandle,'####ControlPointIndex\n');
+ fprintf(fileHandle,[num2str(j) '\n']);
+ fprintf(fileHandle,'####SpotTunnedID\n');
+ fprintf(fileHandle,['1\n']);
+ fprintf(fileHandle,'####CumulativeMetersetWeight\n');
+ if obj.config.Beamlet_Mode
+ cumulativeMetersetWeight = j/numOfEnergies * 1/numOfFields;
+ else
+ cumulativeMetersetWeight = metersetOffset + sum([stf(i).energyLayer(j).numOfPrimaries]);
+ metersetOffset = cumulativeMetersetWeight;
+ end
+ fprintf(fileHandle,[num2str(cumulativeMetersetWeight) '\n']);
+ fprintf(fileHandle,'####Energy (MeV)\n');
+ fprintf(fileHandle,[num2str(stf(i).energies(j)) '\n']);
+
+ %Range shfiter
+ if stf(i).rangeShifterID ~= 0
+ rangeShifter = stf(i).energyLayer(j).rangeShifter;
+ if rangeShifter.ID ~= 0
+ fprintf(fileHandle,'####RangeShifterSetting\n%s\n','IN');
+ pmma_rsp = 1.165; %TODO: hardcoded for now
+ rsWidth = rangeShifter.eqThickness / pmma_rsp;
+ isoToRaShi = stf(i).SAD - rangeShifter.sourceRashiDistance + rsWidth;
+ fprintf(fileHandle,'####IsocenterToRangeShifterDistance\n%f\n',-isoToRaShi/10); %in cm
+ fprintf(fileHandle,'####RangeShifterWaterEquivalentThickness\n%f\n',rangeShifter.eqThickness);
+ else
+ fprintf(fileHandle,'####RangeShifterSetting\n%s\n','OUT');
+ end
+ end
+
+ fprintf(fileHandle,'####NbOfScannedSpots\n');
+ numOfSpots = size(stf(i).energyLayer(j).targetPoints,1);
+ fprintf(fileHandle,[num2str(numOfSpots) '\n']);
+ fprintf(fileHandle,'####X Y Weight\n');
+ for k = 1:numOfSpots
+ %{
+ if obj.config.Beamlet_Mode
+ n = stf(i).energyLayer(j).numOfPrimaries(k);
+ else
+ n = stf(i).energyLayer(j).numOfPrimaries(k) / obj.mcSquare_magicFudge(stf(i).energies(j));
+ end
+ %}
+ n = stf(i).energyLayer(j).numOfPrimaries(k);
+ fprintf(fileHandle,[num2str(stf(i).energyLayer(j).targetPoints(k,:)) ' ' num2str(n) '\n']);
+ end
+ end
+ end
+
+ fclose(fileHandle);
+
+ end
+
+ function cube = readMhd(obj,filename)
+ % TODO: This should become a binary export function in matRads
+ % IO folde
+ % matRad mhd file reader
+ %
+ % call
+ % cube = matRad_readMhd(folder,filename)
+ %
+ % input
+ % folder: folder where the *raw and *mhd file are located
+ % filename: filename
+ %
+ % output
+ % cube: 3D array
+ %
+ % References
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2019 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ %% read header
+ headerFileHandle = fopen([obj.config.Output_Directory, filesep filename],'r');
+
+ s = textscan(headerFileHandle, '%s', 'delimiter', '\n');
+
+ % read dimensions
+ idx = find(~cellfun(@isempty,strfind(s{1}, 'DimSize')),1,'first');
+ dimensions = cell2mat(textscan(s{1}{idx},'DimSize = %f %f %f'));
+
+ % read filename of data
+ idx = find(~cellfun(@isempty,strfind(s{1}, 'ElementDataFile')),1,'first');
+ tmp = textscan(s{1}{idx},'ElementDataFile = %s');
+ dataFilename = cell2mat(tmp{1});
+
+ % get data type
+ idx = find(~cellfun(@isempty,strfind(s{1}, 'ElementType')),1,'first');
+ tmp = textscan(s{1}{idx},'ElementType = MET_%s');
+ type = lower(cell2mat(tmp{1}));
+
+ fclose(headerFileHandle);
+
+ %% read data
+ dataFileHandle = fopen([obj.config.Output_Directory filesep dataFilename],'r');
+ cube = reshape(fread(dataFileHandle,inf,type),dimensions);
+ cube = permute(cube,[2 1 3]);
+ cube = flip(cube,2);
+ fclose(dataFileHandle);
+ end
+
+ function writeMhd(obj,cube,resolution)
+ % TODO: This should become a binary export function in matRads
+ % IO folder
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ %% write header file
+ fileHandle = fopen(obj.config.CT_File,'w');
+
+ fprintf(fileHandle,'ObjectType = Image\n');
+ fprintf(fileHandle,'NDims = 3\n');
+ fprintf(fileHandle,'BinaryData = True\n');
+ fprintf(fileHandle,'BinaryDataByteOrderMSB = False\n');
+ fprintf(fileHandle,'CompressedData = False\n');
+ fprintf(fileHandle,'TransformMatrix = 1 0 0 0 1 0 0 0 1\n');
+ fprintf(fileHandle,'Offset = 0 0 0\n');
+ fprintf(fileHandle,'CenterOfRotation = 0 0 0\n');
+ fprintf(fileHandle,'AnatomicalOrientation = RAI\n');
+ fprintf(fileHandle,'ElementSpacing = %f %f %f\n',resolution);
+ fprintf(fileHandle,'DimSize = %d %d %d\n',size(cube,2),size(cube,1),size(cube,3));
+ fprintf(fileHandle,'ElementType = MET_DOUBLE\n');
+ [fPath,fName,~] = fileparts(obj.config.CT_File);
+ filenameRaw = [fName '.raw'];
+ fprintf(fileHandle,'ElementDataFile = %s\n',filenameRaw);
+ fclose(fileHandle);
+
+ %% write data file
+ filenameRaw = fullfile(fPath,filenameRaw);
+ dataFileHandle = fopen(filenameRaw,'w');
+
+ cube = flip(cube,2);
+ cube = permute(cube,[2 1 3]);
+
+ fwrite(dataFileHandle,cube(:),'double');
+ fclose(dataFileHandle);
+ end
+
+ end
+
+ methods (Access = private)
+ function gain = mcSquare_magicFudge(~,energy)
+ % mcSquare will scale the spot intensities in
+ % https://gitlab.com/openmcsquare/MCsquare/blob/master/src/data_beam_model.c#L906
+ % by this factor so we need to divide up front to make things work. The
+ % original code can be found at https://gitlab.com/openmcsquare/MCsquare/blob/master/src/compute_beam_model.c#L16
+
+ K = 35.87; % in eV (other value 34.23 ?)
+
+ % // Air stopping power (fit ICRU) multiplied by air density
+ SP = (9.6139e-9*energy^4 - 7.0508e-6*energy^3 + 2.0028e-3*energy^2 - 2.7615e-1*energy + 2.0082e1) * 1.20479E-3 * 1E6; % // in eV / cm
+
+ % // Temp & Pressure correction
+ PTP = 1.0;
+
+ % // MU calibration (1 MU = 3 nC/cm)
+ % // 1cm de gap effectif
+ C = 3.0E-9; % // in C / cm
+
+ % // Gain: 1eV = 1.602176E-19 J
+ gain = (C*K) / (SP*PTP*1.602176E-19);
+
+ % divide by 1e7 to not get tiny numbers...
+ gain = gain/1e7;
+
+ end
+ end
+
+
+ methods (Static)
+
+ function [binaryFound,binaryFile] = checkBinaries()
+ % checkBinaries check if the binaries are available on the current
+ % machine and sets to the mcsquarebinary object property
+ %
+ %
+ matRad_cfg = MatRad_Config.instance();
+
+ binaryFile = [];
+ binaryFound = false;
+
+ if ispc
+ if exist('MCSquare_windows.exe','file') ~= 2
+ matRad_cfg.dispWarning('Could not find MCsquare binary.\n');
+ else
+ binaryFile = 'MCSquare_windows.exe';
+ end
+ elseif ismac
+ if exist('MCsquare_mac','file') ~= 2
+ matRad_cfg.dispWarning('Could not find MCsquare binary.\n');
+ else
+ binaryFile = './MCsquare_mac';
+ end
+ %error('MCsquare binaries not available for mac OS.\n');
+ elseif isunix
+ if exist('MCsquare_linux','file') ~= 2
+ matRad_cfg.dispWarning('Could not find MCsquare binary.\n');
+ else
+ binaryFile = './MCsquare_linux';
+ end
+ end
+
+ if ~isempty(binaryFile)
+ binaryFound = true;
+ end
+
+ end
+
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ msg = [];
+ available = false;
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(DoseEngines.matRad_ParticleMCsquareEngine.possibleRadiationModes, machine.meta.radiationMode));
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ %Check the binaries
+ hasBinaries = DoseEngines.matRad_ParticleMCsquareEngine.checkBinaries();
+
+ available = preCheck & hasBinaries;
+ end
+
+ end
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_ParticlePencilBeamEngineAbstract.m b/matRad/doseCalc/+DoseEngines/matRad_ParticlePencilBeamEngineAbstract.m
new file mode 100644
index 000000000..f725b56ef
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_ParticlePencilBeamEngineAbstract.m
@@ -0,0 +1,988 @@
+classdef (Abstract) matRad_ParticlePencilBeamEngineAbstract < DoseEngines.matRad_PencilBeamEngineAbstract
+ % matRad_DoseEngineParticlePB:
+ % Implements an engine for particle based dose calculation
+ % For detailed information see superclass matRad_DoseEngine
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2022 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % help edit
+
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (SetAccess = public, GetAccess = public)
+
+ calcLET = true; % Boolean which defines if LET should be calculated
+ calcBioDose = false; % Boolean which defines if biological dose calculation shoudl be performed (alpha*dose and sqrt(beta)*dose)
+ airOffsetCorrection = true; % Corrects WEPL for SSD difference to kernel database
+ lateralModel = 'auto'; % Lateral Model used. 'auto' uses the most accurate model available (i.e. multiple Gaussians). 'single','double','multi' try to force a singleGaussian or doubleGaussian model, if available
+
+ cutOffMethod = 'integral'; % or 'relative' - describes how to calculate the lateral dosimetric cutoff
+
+ visBoolLateralCutOff = false; % Boolean switch for visualization during+ LeteralCutOff calculation
+ end
+
+ properties (SetAccess = protected, GetAccess = public)
+ constantRBE = NaN; % constant RBE value
+ vTissueIndex; % Stores tissue indices available in the matRad base data
+ vAlphaX; % Stores Photon Alpha
+ vBetaX; % Stores Photon Beta
+ end
+
+ methods
+ function this = matRad_ParticlePencilBeamEngineAbstract(pln)
+ if nargin < 1
+ pln = [];
+ end
+
+ this = this@DoseEngines.matRad_PencilBeamEngineAbstract(pln);
+ end
+
+
+ end
+
+ % Should be abstract methods but in order to satisfy the compatibility
+ % with OCTAVE we can't use abstract methods. If OCTAVE at some point
+ % in the far future implements this feature this should be abstract again.
+ methods (Access = protected) %Abstract
+ function bixel = calcParticleBixel(this,bixel)
+ throw(MException('MATLAB:class:AbstractMember','Abstract function calcParticleBixel of your Particle PencilBeam DoseEngine needs to be implemented!'));
+ end
+ end
+
+ methods (Access = protected)
+ function chooseLateralModel(this)
+ fValidateMulti = @(bd) isfield(bd,'sigmaMulti') && isfield(bd,'weightMulti') && ~isempty(bd.sigmaMulti) && ~isempty(bd.weightMulti);
+ fValidateDouble = @(bd) isfield(bd,'sigma1') && isfield(bd,'sigma2') && isfield(bd,'weight') && ~isempty(bd.sigma1) && ~isempty(bd.sigma2) && ~isempty(bd.weight);
+ fValidateSingle = @(bd) isfield(bd,'sigma') && ~isempty(bd.sigma);
+
+ matRad_cfg = MatRad_Config.instance();
+
+ switch this.lateralModel
+ case 'single'
+ if ~all(arrayfun(fValidateSingle,this.machine.data))
+ matRad_cfg.dispWarning('Chosen Machine does not support a singleGaussian Pencil-Beam model!');
+ this.lateralModel = 'auto';
+ end
+ case 'double'
+ if ~all(arrayfun(fValidateDouble,this.machine.data))
+ matRad_cfg.dispWarning('Chosen Machine does not support a doubleGaussian Pencil-Beam model!');
+ this.lateralModel = 'auto';
+ end
+ case 'multi'
+ if ~all(arrayfun(fValidateMulti,this.machine.data))
+ matRad_cfg.dispWarning('Chosen Machine does not support a multiGaussian Pencil-Beam model!');
+ this.lateralModel = 'auto';
+ end
+ case 'auto'
+ %Do nothing, will be handled below
+ otherwise
+ matRad_cfg.dispError('Lateral model ''%s'' not known!',this.lateralModel);
+ end
+
+ %Now check if we need tho chose the lateral model because it
+ %was set to auto
+ if strcmp(this.lateralModel,'auto')
+ if all(arrayfun(fValidateMulti,this.machine.data))
+ this.lateralModel = 'multi';
+ elseif all(arrayfun(fValidateDouble,this.machine.data))
+ this.lateralModel = 'double';
+ elseif all(arrayfun(fValidateSingle,this.machine.data))
+ this.lateralModel = 'single';
+ else
+ matRad_cfg.dispError('Invalid kernel model!');
+ end
+ end
+
+ matRad_cfg.dispInfo('Using a %s Gaussian pencil-beam kernel model!\n');
+ end
+
+ function bixel = computeBixel(this,currRay,k)
+ %Initialize Bixel Geometry
+ bixel = this.initBixel(currRay,k);
+
+ %Compute Bixel
+ bixel = this.calcParticleBixel(bixel);
+ end
+
+ function bixel = initBixel(this,currRay,k)
+ % matRad initialize general bixel geometry for particle dose calc
+ %
+ % call
+ % bixel = this.initBixel(currRay,k)
+
+ bixel = struct();
+ bixel.beamIndex = currRay.beamIndex;
+ bixel.rayIndex = currRay.rayIndex;
+ bixel.bixelIndex = k;
+
+ %First we get metadata: MU, corresponding base data entry, etc.
+ % extract MU data if present (checks for downwards compatability)
+ bixel.minMU = 0;
+ if isfield(currRay,'minMU')
+ bixel.minMU = currRay.minMU(k);
+ end
+
+ bixel.maxMU = Inf;
+ if isfield(currRay,'maxMU')
+ bixel.maxMU = currRay.maxMU(k);
+ end
+
+ bixel.numParticlesPerMU = 1e6;
+ if isfield(currRay,'numParticlesPerMU')
+ bixel.numParticlesPerMU = currRay.numParticlesPerMU(k);
+ end
+
+ % find energy index in base data
+ energyIx = find(this.round2(currRay.energy(k),4) == this.round2([this.machine.data.energy],4));
+ bixel.energyIx = energyIx;
+ bixel.baseData = this.machine.data(energyIx);
+
+ bixel.rangeShifter = currRay.rangeShifter(k);
+
+ bixel.SSD = currRay.SSD;
+
+ bixel.radDepthOffset = currRay.radDepthOffset;
+
+ % Compute initial spotWidth
+ bixel.sigmaIniSq = currRay.sigmaIni(k).^2;
+
+ % Apply beam modifiers
+ bixel = this.getBeamModifiers(bixel);
+
+ %Gets bixel.ix (voxel indices) and bixel.subIx (logical
+ %indices to be used) after cutoff. Storing these allows us to
+ %use indexing for performance and avoid to many copies
+ bixel = this.getBixelIndicesOnRay(bixel,currRay);
+
+ if isempty(bixel.ix)
+ return;
+ end
+
+ % Get quantities 1:1 from ray. Here we trust Matlab's memory
+ % management to not copy the arrays until they are modified.
+ % This allows us to efficiently access them by indexing in the
+ % bixel computation
+ bixel.radialDist_sq = currRay.radialDist_sq(bixel.subRayIx);
+ bixel.radDepths = currRay.radDepths(bixel.subRayIx);
+ if this.calcBioDose
+ bixel.vTissueIndex = currRay.vTissueIndex(bixel.subRayIx);
+ bixel.vAlphaX = currRay.vAlphaX(bixel.subRayIx);
+ bixel.vBetaX = currRay.vBetaX(bixel.subRayIx);
+ end
+
+ end
+
+ function X = interpolateKernelsInDepth(this,bixel)
+ baseData = bixel.baseData;
+
+ depths = baseData.depths;
+
+ % add potential offset
+ if isfield(baseData,'offset')
+ depths = depths + baseData.offset - bixel.radDepthOffset;
+ end
+
+ % calculate particle dose for bixel k on ray j of beam i
+ % convert from MeV cm^2/g per primary to Gy mm^2 per 1e6 primaries
+ conversionFactor = 1.6021766208e-02;
+
+ %Find all values we need to interpolate
+ X.Z = conversionFactor*baseData.Z;
+
+ %Lateral Kernel Model
+ switch this.lateralModel
+ case 'single'
+ X.sigma = baseData.sigma;
+ case 'double'
+ X.sigma1 = baseData.sigma1;
+ X.sigma2 = baseData.sigma2;
+ X.weight = baseData.weight;
+ case 'multi'
+ X.weightMulti = baseData.weightMulti;
+ X.sigmaMulti = baseData.sigmaMulti;
+ otherwise
+ %Sanity check
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid Lateral Model');
+ end
+
+ % LET
+ if this.calcLET
+ X.LET = baseData.LET;
+ end
+
+ % bioDose
+ % TODO: Improve isfield check by better model management
+ if this.calcBioDose && strcmp(this.bioParam.model,'LEM')
+ X.alpha = baseData.alpha;
+ X.beta = baseData.beta;
+ end
+
+ X = structfun(@(v) matRad_interp1(depths,v,bixel.radDepths,'nearest'),X,'UniformOutput',false); %Extrapolate to zero?
+ end
+
+ % We override this function to boost efficiency a bit (latDistX & Z
+ % not needed)
+ function ray = getRayGeometryFromBeam(this,ray,currBeam)
+ lateralRayCutOff = this.getLateralDistanceFromDoseCutOffOnRay(ray);
+
+ % Ray tracing for beam i and ray j
+ [ix,radialDist_sq] = this.calcGeoDists(currBeam.bevCoords, ...
+ ray.sourcePoint_bev, ...
+ ray.targetPoint_bev, ...
+ ray.SAD, ...
+ currBeam.validCoordsAll, ...
+ lateralRayCutOff);
+
+ ray.validCoords = cellfun(@(beamIx) beamIx & ix,currBeam.validCoords,'UniformOutput',false);
+ ray.ix = cellfun(@(ixInGrid) this.VdoseGrid(ixInGrid),ray.validCoords,'UniformOutput',false);
+
+ %subCoords = cellfun(@(beamIx) beamIx(ix),currBeam.validCoords,'UniformOutput',false);
+ %ray.radialDist_sq = cellfun(@(subix) radialDist_sq(subix),radialDist_sq,subCoords);
+ ray.radialDist_sq = cellfun(@(beamIx) radialDist_sq(beamIx(ix)),currBeam.validCoords,'UniformOutput',false);
+
+ ray.validCoordsAll = any(cell2mat(ray.validCoords),2);
+
+ ray.geoDepths = cellfun(@(rD,ix) rD(ix),currBeam.geoDepths,ray.validCoords,'UniformOutput',false); %usually not needed for particle beams
+ ray.radDepths = cellfun(@(rD,ix) rD(ix),currBeam.radDepths,ray.validCoords,'UniformOutput',false);
+ %ray.ix = currBeam.ixRadDepths(ix);
+ %ray.subIxVdoseGrid = currBeam.subIxVdoseGrid(ix);
+ end
+
+ function [currBixel] = getBixelIndicesOnRay(this,currBixel,currRay)
+
+ % create offset vector to account for additional offsets modelled in the base data and a potential
+ % range shifter. In the following, we only perform dose calculation for voxels having a radiological depth
+ % that is within the limits of the base data set (-> machine.data(i).dephts). By this means, we only allow
+ % interpolations in this.calcParticleDoseBixel() and avoid extrapolations.
+ %urrBixel.offsetRadDepth = currBixel.baseData.offset + currBixel.radDepthOffset;
+ tmpOffset = currBixel.baseData.offset - currBixel.radDepthOffset;
+
+ % find depth depended lateral cut off
+ if this.dosimetricLateralCutOff == 1
+ currIx = currRay.radDepths <= currBixel.baseData.depths(end) + tmpOffset;
+ elseif this.dosimetricLateralCutOff < 1 && this.dosimetricLateralCutOff > 0
+
+ %{
+ % Old two step clipping. Seems to be slower than doing it
+ at once using nearest neighbor extrapolation
+ % perform rough 2D clipping
+ currIx = currRay.radDepths <= currBixel.baseData.depths(end) + tmpOffset & ...
+ currRay.radialDist_sq <= max(currBixel.baseData.LatCutOff.CutOff.^2);
+
+ % peform fine 2D clipping
+ if length(currBixel.baseData.LatCutOff.CutOff) > 1
+ currIx(currIx) = matRad_interp1((currBixel.baseData.LatCutOff.depths + tmpOffset)',...
+ (currBixel.baseData.LatCutOff.CutOff.^2)', currRay.radDepths(currIx)) >= currRay.radialDist_sq(currIx);
+ end
+ %}
+
+ if length(currBixel.baseData.LatCutOff.CutOff) > 1
+ %currIx = matRad_interp1((currBixel.baseData.LatCutOff.depths + tmpOffset)',(currBixel.baseData.LatCutOff.CutOff.^2)', currRay.radDepths,'nearest') >= currRay.radialDist_sq & currRay.radDepths <= currBixel.baseData.depths(end) + tmpOffset;
+ currIx = matRad_interp1((currBixel.baseData.LatCutOff.depths + tmpOffset)',(currBixel.baseData.LatCutOff.CutOff.^2)', currRay.radDepths) >= currRay.radialDist_sq & currRay.radDepths <= currBixel.baseData.depths(end) + tmpOffset;
+ else
+ currIx = currBixel.baseData.LatCutOff.CutOff.^2 >= currRay.radialDist_sq & currRay.radDepths <= currBixel.baseData.depths(end) + tmpOffset;
+ end
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Cutoff must be a value between 0 and 1!')
+ end
+
+ currBixel.subRayIx = currIx;
+ currBixel.ix = currRay.ix(currIx);
+ end
+
+ function currBixel = getBeamModifiers(this,currBixel)
+ addSigmaSq = 0;
+ radDepthOffset = 0;
+ % consider range shifter for protons if applicable
+ if currBixel.rangeShifter.eqThickness > 0
+ %TODO: We should do this check in dose calc initialization
+ %instead to spare some time
+ if ~strcmp(this.machine.meta.radiationMode,'protons')
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Range shifter not valid for irradiation with particle other than protons!');
+ end
+
+ % compute!
+ sigmaRashi = matRad_calcSigmaRashi(currBixel.baseData.energy, ...
+ currBixel.rangeShifter, ...
+ currBixel.SSD);
+
+ % add to initial sigma in quadrature
+ addSigmaSq = addSigmaSq + sigmaRashi.^2;
+ radDepthOffset = radDepthOffset + currBixel.rangeShifter.eqThickness;
+
+ end
+
+ currBixel.addSigmaSq = addSigmaSq;
+ currBixel.radDepthOffset = currBixel.radDepthOffset + radDepthOffset;
+ end
+
+ function dij = initDoseCalc(this,ct,cst,stf)
+ % modified inherited method of the superclass DoseEngine,
+ % containing intialization which are specificly needed for
+ % pencil beam calculation and not for other engines
+
+ dij = initDoseCalc@DoseEngines.matRad_PencilBeamEngineAbstract(this,ct,cst,stf);
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Choose the lateral pencil beam model
+ this.chooseLateralModel();
+
+ %Toggles correction of small difference of current SSD to distance used
+ %in generation of base data (e.g. phantom surface at isocenter)
+ if this.airOffsetCorrection
+ if ~isfield(this.machine.meta, 'fitAirOffset')
+ this.machine.meta.fitAirOffset = 0; %By default we assume that the base data was fitted to a phantom with surface at isocenter
+ matRad_cfg.dispDebug('Asked for correction of Base Data Air Offset, but no value found. Using default value of %f mm.\n',this.machine.meta.fitAirOffset);
+ end
+ else
+ this.machine.meta.fitAirOffset = 0;
+ end
+
+ if ~isfield(this.machine.meta, 'BAMStoIsoDist')
+ this.machine.meta.BAMStoIsoDist = 1000;
+ matRad_cfg.dispWarning('Machine data does not contain BAMStoIsoDist. Using default value of %f mm.',this.machine.meta.BAMStoIsoDist);
+ end
+
+ %Biology
+ if ~isnan(this.constantRBE)
+ dij.RBE = this.constantRBE;
+ end
+
+ % TODO: this is clumsy and needs to be changed with the
+ % biomodel update
+ if this.bioParam.bioOpt
+ this.calcBioDose = true;
+ end
+
+ % Load biologicla base data if needed
+ if this.calcBioDose
+ dij = this.loadBiologicalBaseData(cst,dij);
+ % allocate alpha and beta dose container and sparse matrices in the dij struct,
+ % for more informations see corresponding method
+ dij = this.allocateBioDoseContainer(dij);
+ end
+
+ % allocate LET containner and let sparse matrix in dij struct
+ if this.calcLET
+ if isfield(this.machine.data,'LET')
+ dij = this.allocateLETContainer(dij);
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('LET not available and will not be computed!');
+ this.calcLET = false;
+ end
+ end
+
+ % lateral cutoff for raytracing and geo calculations
+ this.effectiveLateralCutOff = this.geometricLateralCutOff;
+ end
+
+ function currBeam = initBeam(this,dij,ct,cst,stf,i)
+ currBeam = initBeam@DoseEngines.matRad_PencilBeamEngineAbstract(this,dij,ct,cst,stf,i);
+
+ %currBeam.rot_coordsVdoseGrid = currBeam.rot_coordsVdoseGrid(~isnan(currBeam.radDepthVdoseGrid{1}),:);
+ maxEnergy = max([currBeam.ray.energy]);
+ maxEnergyIx = find(this.round2(maxEnergy,4) == this.round2([this.machine.data.energy],4));
+ raShis = [currBeam.ray.rangeShifter];
+ minRaShi = min([raShis.eqThickness]);
+ radDepthOffset = this.machine.data(maxEnergyIx).offset + minRaShi;
+
+ % apply limit in depth
+ %subSelectIx = currBeam.radDepths{1} < (this.machine.data(maxEnergyIx).depths(end) - radDepthOffset);
+
+ subSelectIx = cellfun(@(rD) rD < (this.machine.data(maxEnergyIx).depths(end) - radDepthOffset),currBeam.radDepths,'UniformOutput',false);
+ currBeam.validCoords = cellfun(@and,subSelectIx,currBeam.validCoords,'UniformOutput',false);
+ currBeam.validCoordsAll = any(cell2mat(currBeam.validCoords),2);
+
+ %currBeam.ixRadDepths = currBeam.ixRadDepths(subSelectIx);
+ %currBeam.subIxVdoseGrid = currBeam.subIxVdoseGrid(subSelectIx);
+ %currBeam.radDepths = cellfun(@(rd) rd(subSelectIx),currBeam.radDepths,'UniformOutput',false);
+ %currBeam.bevCoords = currBeam.bevCoords(subSelectIx,:);
+
+ %Precompute CutOff
+ this.calcLateralParticleCutOff(this.dosimetricLateralCutOff,currBeam);
+ end
+
+ function dij = loadBiologicalBaseData(this,cst,dij)
+ matRad_cfg = MatRad_Config.instance();
+
+ matRad_cfg.dispInfo('Initializing biological dose calculation...\n');
+
+ numOfCtScen = numel(this.VdoseGridScenIx);
+
+
+ cstDownsampled = matRad_setOverlapPriorities(cst);
+
+ % resizing cst to dose cube resolution
+ cstDownsampled = matRad_resizeCstToGrid(cstDownsampled,dij.ctGrid.x,dij.ctGrid.y,dij.ctGrid.z,...
+ dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z);
+
+ tmpScenVdoseGrid = cell(numOfCtScen,1);
+
+ [dij.ax,dij.bx] = matRad_getPhotonLQMParameters(cstDownsampled,dij.doseGrid.numOfVoxels,this.VdoseGrid);
+
+ for s = 1:numOfCtScen
+ tmpScenVdoseGrid{s} = this.VdoseGrid(this.VdoseGridScenIx{s});
+ % retrieve photon LQM parameter for the current dose grid voxels
+
+ % vAlphaX and vBetaX for parameters in VdoseGrid
+ this.vAlphaX{s} = dij.ax{s}(tmpScenVdoseGrid{s});
+ this.vBetaX{s} = dij.bx{s}(tmpScenVdoseGrid{s});
+ this.vTissueIndex{s} = zeros(size(tmpScenVdoseGrid{s},1),1);
+ end
+
+ if strcmp(this.bioParam.model,'LEM')
+ matRad_cfg.dispInfo('\tUsing LEM model with precomputed kernels\n');
+
+ if isfield(this.machine.data,'alphaX') && isfield(this.machine.data,'betaX')
+ for i = 1:size(cstDownsampled,1)
+
+ % check if cst is compatiable
+ if ~isempty(cstDownsampled{i,5}) && isfield(cstDownsampled{i,5},'alphaX') && isfield(cstDownsampled{i,5},'betaX')
+
+ % check if base data contains alphaX and betaX
+ IdxTissue = find(ismember(this.machine.data(1).alphaX,cstDownsampled{i,5}.alphaX) & ...
+ ismember(this.machine.data(1).betaX,cstDownsampled{i,5}.betaX));
+
+ % check consitency of biological baseData and cst settings
+ if ~isempty(IdxTissue)
+ for s = 1:numOfCtScen
+ tmpScenVdoseGrid = this.VdoseGrid(this.VdoseGridScenIx{s});
+ isInVdoseGrid = ismember(tmpScenVdoseGrid,cstDownsampled{i,4}{s});
+ this.vTissueIndex{s}(isInVdoseGrid) = IdxTissue;
+ end
+ else
+ matRad_cfg.dispError('Biological base data and cst are inconsistent!');
+ end
+
+ else
+ for s = 1:numOfCtScen
+ this.vTissueIndex{s}(:) = 1;
+ end
+ matRad_cfg.dispWarning('\tTissue type of %s was set to 1\n',cstDownsampled{i,2});
+ end
+ end
+ dij.vTissueIndex = this.vTissueIndex;
+ matRad_cfg.dispInfo('done.\n');
+ else
+ matRad_cfg.dispError('Base data is missing alphaX and/or betaX!');
+ end
+ elseif any(strcmp(this.bioParam.model,{'HEL','MCN','WED'}))
+ matRad_cfg.dispInfo('\tUsing LET-dependent model\n');
+ if ~this.calcLET
+ matRad_cfg.dispWarning('Forcing LET calculation as it is required for LET-dependent models!');
+ this.calcLET = true;
+ end
+ else
+ matRad_cfg.dispError('Unknown Biological Model!');
+ end
+
+ end
+
+ function dij = allocateBioDoseContainer(this,dij)
+ % allocate space for container used in bio optimization
+ dij = this.allocateQuantityMatrixContainers(dij,{'mAlphaDose','mSqrtBetaDose'});
+ end
+
+ function dij = allocateLETContainer(this,dij)
+ % allocate space for container used in LET calculation
+
+ % get MatLab Config instance for displaying warings
+ matRad_cfg = MatRad_Config.instance();
+ if isfield(this.machine.data,'LET')
+ dij = this.allocateQuantityMatrixContainers(dij,{'mLETDose'});
+ else
+ matRad_cfg.dispWarning('LET not available in the machine data. LET will not be calculated.');
+ end
+
+ end
+
+ function calcLateralParticleCutOff(this,cutOffLevel,stfElement)
+ % matRad function to calculate a depth dependend lateral cutoff
+ % for each pristine particle beam
+ %
+ % call
+ % this.calcLateralParticleCutOff(cutOffLevel,stf)
+ %
+ % input
+ % this: current engine object includes machine base data file
+ % cutOffLevel: cut off level - number between 0 and 1
+ % stfElement: matRad steering information struct for a single beam
+ %
+ % output
+ % machine: changes in the object property machine base data file including an additional field representing the lateral
+ % cutoff
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2015 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Sanity Checks
+ if numel(stfElement) > 1
+ matRad_cfg.dispError('CutOff can only be precalculated for a single element, but you provided steering information for multiple beams!');
+ end
+
+ if cutOffLevel <= 0.98
+ matRad_cfg.dispWarning('a lateral cut off below 0.98 may result in an inaccurate dose calculation');
+ end
+
+ if (cutOffLevel < 0 || cutOffLevel > 1)
+ matRad_cfg.dispWarning('lateral cutoff is out of range - using default cut off of 0.99')
+ cutOffLevel = 0.99;
+ end
+
+
+ matRad_cfg.dispInfo('matRad: calculate lateral cutoff...');
+ conversionFactor = 1.6021766208e-02;
+
+ % function handle for calculating depth dose for APM
+ sumGauss = @(x,mu,SqSigma,w) ((1./sqrt(2*pi*ones(numel(x),1) * SqSigma') .* ...
+ exp(-bsxfun(@minus,x,mu').^2 ./ (2* ones(numel(x),1) * SqSigma' ))) * w);
+
+ % define some variables needed for the cutoff calculation
+ vX = [0 logspace(-1,3,1200)]; % [mm]
+
+ % integration steps
+ r_mid = 0.5*(vX(1:end-1) + vX(2:end))'; % [mm]
+ dr = (vX(2:end) - vX(1:end-1))';
+ radialDist_sq = r_mid.^2;
+
+ % number of depth points for which a lateral cutoff is determined
+ numDepthVal = 35;
+
+ % helper function for energy selection
+ round2 = @(a,b)round(a*10^b)/10^b;
+
+ % extract SSD for each bixel
+ vSSD = ones(1,length([stfElement.ray(:).energy]));
+ cnt = 1;
+ for i = 1:length(stfElement.ray)
+ vSSD(cnt:cnt+numel([stfElement.ray(i).energy])-1) = stfElement.ray(i).SSD;
+ cnt = cnt + numel(stfElement.ray(i).energy);
+ end
+
+ % setup energy, focus index, sigma look up table - only consider unique rows
+ [energySigmaLUT,ixUnique] = unique([[stfElement.ray(:).energy]; [stfElement.ray(:).focusIx] ; vSSD]','rows');
+ rangeShifterLUT = [stfElement.ray(:).rangeShifter];
+ rangeShifterLUT = rangeShifterLUT(1,ixUnique);
+
+ % find the largest inital beam width considering focus index, SSD and range shifter for each individual energy
+ for i = 1:size(energySigmaLUT,1)
+
+ % find index of maximum used energy (round to keV for numerical reasons
+ energyIx = max(round2(energySigmaLUT(i,1),4)) == round2([this.machine.data.energy],4);
+
+ currFoci = energySigmaLUT(i,2);
+ sigmaIni = matRad_interp1(this.machine.data(energyIx).initFocus.dist(currFoci,:)',...
+ this.machine.data(energyIx).initFocus.sigma(currFoci,:)',...
+ energySigmaLUT(i,3));
+ sigmaIni_sq = sigmaIni^2;
+
+ % consider range shifter for protons if applicable
+ if strcmp(this.machine.meta.radiationMode,'protons') && rangeShifterLUT(i).eqThickness > 0 && ~strcmp(this.machine.meta.machine,'Generic')
+
+ %get max range shift
+ sigmaRashi = matRad_calcSigmaRashi(this.machine.data(energyIx).energy, ...
+ rangeShifterLUT(i), ...
+ energySigmaLUT(i,3));
+
+ % add to initial sigma in quadrature
+ sigmaIni_sq = sigmaIni_sq + sigmaRashi.^2;
+
+ end
+
+ energySigmaLUT(i,4) = sigmaIni_sq;
+
+ end
+
+ % find for each individual energy the broadest inital beam width
+ uniqueEnergies = unique(energySigmaLUT(:,1));
+ largestSigmaSq4uniqueEnergies = NaN * ones(numel(uniqueEnergies),1);
+ ix_Max = NaN * ones(numel(uniqueEnergies),1);
+ for i = 1:numel(uniqueEnergies)
+ [largestSigmaSq4uniqueEnergies(i), ix_Max(i)] = max(energySigmaLUT(uniqueEnergies(i) == energySigmaLUT(:,1),4));
+ end
+
+ % get energy indices for looping
+ vEnergiesIx = find(ismember([this.machine.data(:).energy],uniqueEnergies(:,1)));
+ cnt = 0;
+
+ % loop over all entries in the machine.data struct
+ for energyIx = vEnergiesIx
+
+ % set default depth cut off - finite value will be set during first iteration
+ depthDoseCutOff = inf;
+
+ % get the current integrated depth dose profile
+ if isstruct(this.machine.data(energyIx).Z)
+ idd_org = sumGauss(this.machine.data(energyIx).depths,this.machine.data(energyIx).Z.mean,...
+ this.machine.data(energyIx).Z.width.^2,...
+ this.machine.data(energyIx).Z.weight) * conversionFactor;
+ else
+ idd_org = this.machine.data(energyIx).Z * conversionFactor;
+ end
+
+ [~,peakIxOrg] = max(idd_org);
+
+ % get indices for which a lateral cutoff should be calculated
+ cumIntEnergy = cumtrapz(this.machine.data(energyIx).depths,idd_org);
+
+ peakTailRelation = 0.5;
+ numDepthValToPeak = ceil(numDepthVal*peakTailRelation); % number of depth values from 0 to peak position
+ numDepthValTail = ceil(numDepthVal*(1-peakTailRelation)); % number of depth values behind peak position
+ energyStepsToPeak = cumIntEnergy(peakIxOrg)/numDepthValToPeak;
+ energyStepsTail = (cumIntEnergy(end)-cumIntEnergy(peakIxOrg))/numDepthValTail;
+ % make sure to include 0, peak position and end position
+ vEnergySteps = unique([0:energyStepsToPeak:cumIntEnergy(peakIxOrg) cumIntEnergy(peakIxOrg) ...
+ cumIntEnergy(peakIxOrg+1):energyStepsTail:cumIntEnergy(end) cumIntEnergy(end)]);
+
+ [cumIntEnergy,ix] = unique(cumIntEnergy);
+ depthValues = matRad_interp1(cumIntEnergy,this.machine.data(energyIx).depths(ix),vEnergySteps);
+
+ if isstruct(this.machine.data(energyIx).Z)
+ idd = sumGauss(depthValues,this.machine.data(energyIx).Z.mean,...
+ this.machine.data(energyIx).Z.width.^2,...
+ this.machine.data(energyIx).Z.weight) * conversionFactor;
+ else
+ idd = matRad_interp1(this.machine.data(energyIx).depths,this.machine.data(energyIx).Z,depthValues) * conversionFactor;
+ end
+
+ cnt = cnt +1 ;
+ % % calculate dose in spot
+ baseData = this.machine.data(energyIx);
+ baseData.LatCutOff.CompFac = 1;
+
+ for j = 1:numel(depthValues)
+
+ % save depth value
+ this.machine.data(energyIx).LatCutOff.depths(j) = depthValues(j);
+
+ if cutOffLevel == 1
+ this.machine.data(energyIx).LatCutOff.CompFac = 1;
+ this.machine.data(energyIx).LatCutOff.CutOff(j) = Inf;
+ else
+ bixel.energyIx = energyIx;
+ bixel.baseData = baseData;
+ bixel.radialDist_sq = radialDist_sq;
+ bixel.sigmaIniSq = largestSigmaSq4uniqueEnergies(cnt);
+ bixel.radDepths = (depthValues(j) + baseData.offset) * ones(size(radialDist_sq));
+ bixel.vTissueIndex = ones(size(bixel.radDepths));
+ bixel.vAlphaX = 0.5*ones(size(bixel.radDepths));
+ bixel.vBetaX = 0.05*ones(size(bixel.radDepths));
+ bixel.subRayIx = true(size(bixel.radDepths));
+ bixel.ix = find(bixel.subRayIx);
+ bixel.radDepthOffset = 0;
+ bixel.addSigmaSq = 0;
+
+ % calculate dose
+ bixel = this.calcParticleBixel(bixel);
+ dose_r = bixel.physicalDose;
+
+ cumArea = cumsum(2*pi.*r_mid.*dose_r.*dr);
+ relativeTolerance = 0.5; %in [%]
+ if abs((cumArea(end)./(idd(j)))-1)*100 > relativeTolerance
+ matRad_cfg.dispWarning('LateralParticleCutOff: shell integration is wrong !')
+ end
+
+ % Find radius at which integrated dose becomes
+ % bigger than cutoff * IDD
+
+ switch this.cutOffMethod
+ case 'integral'
+ IX = find(cumArea >= idd(j) * cutOffLevel,1, 'first');
+ this.machine.data(energyIx).LatCutOff.CompFac = cutOffLevel^-1;
+ case 'relative'
+ IX = find(dose_r <= (1-cutOffLevel) * max(dose_r), 1, 'first');
+ relFac = cumArea(IX)./cumArea(end); % (or idd(j)) to find the appropriate integral of dose
+ this.machine.data(energyIx).LatCutOff.CompFac(j) = relFac^-1;
+ otherwise
+ matRad_cfg.dispError('LateralParticleCutOff: Invalid Cutoff Method. Must be ''integral'' or ''relative''!');
+ end
+
+ if isempty(IX)
+ depthDoseCutOff = Inf;
+ matRad_cfg.dispWarning('LateralParticleCutOff: Couldnt find lateral cut off!')
+ elseif isnumeric(IX)
+ depthDoseCutOff = r_mid(IX);
+ end
+
+ this.machine.data(energyIx).LatCutOff.CutOff(j) = depthDoseCutOff;
+ end
+ end
+ end
+
+ matRad_cfg.dispInfo('done.\n');
+
+ %% visualization
+ if this.visBoolLateralCutOff
+
+ % determine which pencil beam should be plotted
+ subIx = ceil(numel(vEnergiesIx)/2);
+ energyIx = vEnergiesIx(subIx);
+
+ baseData = this.machine.data(energyIx);
+ focusIx = energySigmaLUT(ix_Max(subIx),2);
+ maxSSD = energySigmaLUT(ix_Max(subIx),3);
+ rangeShifter = rangeShifterLUT(ix_Max(subIx));
+ TmpCompFac = baseData.LatCutOff.CompFac;
+ baseData.LatCutOff.CompFac = 1;
+
+ % plot 3D cutoff at one specific depth on a rather sparse grid
+ sStep = 0.5;
+ vLatX = -100 : sStep : 100; % [mm]
+ dimX = numel(vLatX);
+ midPos = round(length(vLatX)/2);
+ [X,Y] = meshgrid(vLatX,vLatX);
+
+ radDepths = [0:sStep:this.machine.data(energyIx).depths(end)] + this.machine.data(energyIx).offset;
+ radialDist_sq = (X.^2 + Y.^2);
+ radialDist_sq = radialDist_sq(:);
+ mDose = zeros(dimX,dimX,numel(radDepths));
+ vDoseInt = zeros(numel(radDepths),1);
+
+ for kk = 1:numel(radDepths)
+
+ % calculate initial focus sigma
+ sigmaIni = matRad_interp1(this.machine.data(energyIx).initFocus.dist(focusIx,:)', ...
+ this.machine.data(energyIx).initFocus.sigma(focusIx,:)',maxSSD);
+ sigmaIni_sq = sigmaIni^2;
+
+ % consider range shifter for protons if applicable
+ if rangeShifter.eqThickness > 0 && strcmp(pln.radiationMode,'protons')
+
+ % compute!
+ sigmaRashi = matRad_calcSigmaRashi(this.machine.data(energyIx).energy,rangeShifter,maxSSD);
+
+ % add to initial sigma in quadrature
+ sigmaIni_sq = sigmaIni_sq + sigmaRashi^2;
+
+ end
+
+ bixel.energyIx = energyIx;
+ bixel.baseData = baseData;
+ bixel.radialDist_sq = radialDist_sq;
+ bixel.sigmaIniSq = sigmaIni_sq;
+ bixel.radDepths = radDepths(kk)*ones(size(bixel.radialDist_sq));
+ bixel.vTissueIndex = ones(size(bixel.radDepths));
+ bixel.subRayIx = true(size(bixel.radDepths));
+ bixel.ix = find(bixel.subRayIx);
+ bixel.radDepthOffset = 0;
+ bixel.addSigmaSq = 0;
+
+ bixel = this.calcParticleBixel(bixel);
+
+ mDose(:,:,kk) = reshape(bixel.physicalDose,[dimX dimX]);
+
+ [~,IX] = min(abs((this.machine.data(energyIx).LatCutOff.depths + this.machine.data(energyIx).offset) - radDepths(kk)));
+ TmpCutOff = this.machine.data(energyIx).LatCutOff.CutOff(IX);
+ vXCut = vX(vX<=TmpCutOff);
+
+ % integration steps
+ r_mid_Cut = (0.5*(vXCut(1:end-1) + vXCut(2:end)))'; % [mm]
+ dr_Cut = (vXCut(2:end) - vXCut(1:end-1))';
+ radialDist_sqCut = r_mid_Cut.^2;
+
+ bixel.radialDist_sq = radialDist_sqCut(:);
+ bixel.radDepths = radDepths(kk)*ones(size(bixel.radialDist_sq));
+ bixel.vTissueIndex = ones(size(bixel.radDepths));
+ bixel.subRayIx = true(size(bixel.radDepths));
+
+ bixel = this.calcParticleBixel(bixel);
+ dose_r_Cut = bixel.physicalDose;
+
+ cumAreaCut = cumsum(2*pi.*r_mid_Cut.*dose_r_Cut.*dr_Cut);
+
+ if ~isempty(cumAreaCut)
+ vDoseInt(kk) = cumAreaCut(end);
+ end
+ end
+
+ % obtain maximum dose
+ if isstruct(this.machine.data(energyIx).Z)
+ idd = sumGauss(depthValues,this.machine.data(energyIx).Z.mean,...
+ this.machine.data(energyIx).Z.width.^2,...
+ this.machine.data(energyIx).Z.weight) * conversionFactor;
+ else
+ idd = matRad_interp1(this.machine.data(energyIx).depths,this.machine.data(energyIx).Z,depthValues) * conversionFactor;
+ end
+
+ [~,peakixDepth] = max(idd);
+
+ bixel.energyIx = energyIx;
+ bixel.baseData = baseData;
+ bixel.radialDist_sq = 0;
+ bixel.sigmaIniSq = sigmaIni_sq;
+ bixel.radDepths = baseData.depths(peakixDepth);
+ bixel.vTissueIndex = ones(size(bixel.radDepths));
+ bixel.subIx = true;
+ bixel.radDepthOffset = 0;
+ bixel.addSigmaSq = 0;
+
+ bixel = this.calcParticleBixel(bixel);
+ dosePeakPos = bixel.physicalDose;
+
+ vLevelsDose = dosePeakPos.*[0.01 0.05 0.1 0.9];
+ doseSlice = squeeze(mDose(midPos,:,:));
+ figure,set(gcf,'Color',[1 1 1]);
+ subplot(311),h=imagesc(squeeze(mDose(midPos,:,:)));hold on;
+ set(h,'AlphaData', .8*double(doseSlice>0));
+ contour(doseSlice,vLevelsDose,'LevelListMode','manual','LineWidth',2);hold on
+
+ ax = gca;
+ ax.XTickLabelMode = 'manual';
+ ax.XTickLabel = strsplit(num2str(ax.XTick*sStep + this.machine.data(energyIx).offset),' ')';
+ ax.YTickLabelMode = 'manual';
+ ax.YTickLabel = strsplit(num2str(ax.YTick*sStep + this.machine.data(energyIx).offset),' ')';
+
+ plot(1+(this.machine.data(energyIx).LatCutOff.depths)*sStep^-1,...
+ this.machine.data(energyIx).LatCutOff.CutOff * sStep^-1 + midPos,'rx');
+
+ legend({'isodose 1%,5%,10% 90%','calculated cutoff'}) ,colorbar,set(gca,'FontSize',12),xlabel('z [mm]'),ylabel('x [mm]');
+
+ entry = this.machine.data(energyIx);
+ if isstruct(entry.Z)
+ idd = sumGauss(entry.depths,entry.Z.mean,entry.Z.width.^2,entry.Z.weight);
+ else
+ idd = this.machine.data(energyIx).Z;
+ end
+ if length(TmpCompFac)>1
+ TmpCompFac = matRad_interp1(depthValues, TmpCompFac', radDepths);
+ end
+ subplot(312),plot(this.machine.data(energyIx).depths,idd*conversionFactor,'k','LineWidth',2),grid on,hold on
+ plot(radDepths - this.machine.data(energyIx).offset,vDoseInt,'r--','LineWidth',2),hold on,
+ plot(radDepths - this.machine.data(energyIx).offset,vDoseInt .* TmpCompFac,'bx','LineWidth',1),hold on,
+ legend({'original IDD',['cut off IDD at ' num2str(cutOffLevel) '%'],'cut off IDD with compensation'},'Location','northwest'),
+ xlabel('z [mm]'),ylabel('[MeV cm^2 /(g * primary)]'),set(gca,'FontSize',12)
+
+ totEnergy = trapz(this.machine.data(energyIx).depths,idd*conversionFactor) ;
+ totEnergyCutOff = trapz(radDepths,vDoseInt .* TmpCompFac) ;
+ relDiff = ((totEnergy/totEnergyCutOff)-1)*100;
+ title(['rel diff of integral dose ' num2str(relDiff) '%']);
+ baseData.LatCutOff.CompFac = TmpCompFac;
+
+ subplot(313),
+ if isfield(this.machine.data(energyIx),'sigma1')
+ yyaxis left;
+ plot(this.machine.data(energyIx).LatCutOff.depths,this.machine.data(energyIx).LatCutOff.CutOff,'LineWidth',2),hold on
+ plot(this.machine.data(energyIx).depths,(this.machine.data(energyIx).sigma1),':','LineWidth',2),grid on,hold on,ylabel('mm')
+ yyaxis right;
+ plot(this.machine.data(energyIx).depths,(this.machine.data(energyIx).sigma2),'-.','LineWidth',2),grid on,hold on,ylabel('mm')
+ legend({'Cutoff','sigma1','sigma2'});
+ else
+ yyaxis left;plot(this.machine.data(energyIx).LatCutOff.depths,this.machine.data(energyIx).LatCutOff.CutOff,'LineWidth',2),hold on,ylabel('mm')
+ yyaxis right;subplot(313),plot(this.machine.data(energyIx).depths,this.machine.data(energyIx).sigma,'LineWidth',2),grid on,hold on
+ legend({'Cutoff','sigma'});ylabel('mm')
+ end
+
+ set(gca,'FontSize',12),xlabel('z [mm]'), ylabel('mm')
+
+ % plot cutoff of different energies
+ figure,set(gcf,'Color',[1 1 1]);
+ cnt = 1;
+ for i = vEnergiesIx
+ plot(this.machine.data(i).LatCutOff.depths,this.machine.data(i).LatCutOff.CutOff,'LineWidth',1.5),hold on
+ cellLegend{cnt} = [num2str(this.machine.data(i).energy) ' MeV'];
+ cnt = cnt + 1;
+ end
+ grid on, grid minor,xlabel('depth in [mm]'),ylabel('lateral cutoff in [mm]')
+ title(['cutoff level = ' num2str(cutOffLevel)]),
+ ylim = get(gca,'Ylim'); set(gca,'Ylim',[0 ylim(2)+3]), legend(cellLegend)
+ end
+ end
+
+ function ray = initRay(this,beam,j)
+ ray = initRay@DoseEngines.matRad_PencilBeamEngineAbstract(this,beam,j);
+
+ % calculate initial sigma for all bixel on current ray
+ ray.sigmaIni = matRad_calcSigmaIni(this.machine.data,ray,ray.SSD);
+
+ % Since matRad's ray cast starts at the skin and base data
+ % is generated at soume source to phantom distance
+ % we can explicitly correct for the nozzle to air WEPL in
+ % the current case.
+ if this.airOffsetCorrection
+ nozzleToSkin = (ray.SSD + this.machine.meta.BAMStoIsoDist) - this.machine.meta.SAD;
+ ray.radDepthOffset = 0.0011 * (nozzleToSkin - this.machine.meta.fitAirOffset);
+ else
+ ray.radDepthOffset = 0;
+ end
+
+ % just use tissue classes of voxels found by ray tracer
+ if this.calcBioDose
+ for s = 1:numel(this.vTissueIndex)
+ ray.vTissueIndex{s} = this.vTissueIndex{s}(ray.validCoords{s},:);
+ ray.vAlphaX{s} = this.vAlphaX{s}(ray.validCoords{s});
+ ray.vBetaX{s} = this.vBetaX{s}(ray.validCoords{s});
+ end
+ end
+ end
+
+ function scenRay = extractSingleScenarioRay(this,ray,scenIdx)
+ scenRay = extractSingleScenarioRay@DoseEngines.matRad_PencilBeamEngineAbstract(this,ray,scenIdx);
+
+ %Gets number of scenario
+ scenNum = this.multScen.scenNum(scenIdx);
+ ctScen = this.multScen.linearMask(scenNum,1);
+
+ if isfield(scenRay,'vTissueIndex')
+ scenRay.vTissueIndex = scenRay.vTissueIndex{ctScen};
+ scenRay.vAlphaX = scenRay.vAlphaX{ctScen};
+ scenRay.vBetaX = scenRay.vBetaX{ctScen};
+ end
+ end
+
+ function dij = fillDij(this,bixel,dij,stf,scenIdx,currBeamIdx,currRayIdx,currBixelIdx,bixelCounter)
+ dij = this.fillDij@DoseEngines.matRad_PencilBeamEngineAbstract(bixel,dij,stf,scenIdx,currBeamIdx,currRayIdx,currBixelIdx,bixelCounter);
+
+ % Add MU information
+ if ~this.calcDoseDirect
+ dij.minMU(bixelCounter,1) = bixel.minMU;
+ dij.maxMU(bixelCounter,1) = bixel.maxMU;
+ dij.numParticlesPerMU(bixelCounter,1) = bixel.numParticlesPerMU;
+ end
+ end
+
+ function lateralRayCutOff = getLateralDistanceFromDoseCutOffOnRay(this,ray)
+ % find index of maximum used energy (round to keV for numerical
+ % reasons
+ maxEnergyIx = max(this.round2(ray.energy,4)) == this.round2([this.machine.data.energy],4);
+
+ lateralRayCutOff = max(this.machine.data(maxEnergyIx).LatCutOff.CutOff);
+ end
+
+ function r2 = round2(~,a,b)
+ % helper function for energy selection
+ r2 = round(a*10^b)/10^b;
+ end
+
+ end
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_PencilBeamEngineAbstract.m b/matRad/doseCalc/+DoseEngines/matRad_PencilBeamEngineAbstract.m
new file mode 100644
index 000000000..be64d33e9
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_PencilBeamEngineAbstract.m
@@ -0,0 +1,680 @@
+classdef (Abstract) matRad_PencilBeamEngineAbstract < DoseEngines.matRad_DoseEngineBase
+ % matRad_PencilBeamEngineAbstract: abstract superclass for all dose calculation engines which are based on
+ % analytical pencil beam calculation
+ % for more informations see superclass
+ % DoseEngines.matRad_DoseEngine
+ % MatRad_Config MatRad Configuration class
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2019 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ keepRadDepthCubes = false;
+
+ geometricLateralCutOff; %lateral geometric cut-off id mm, used for raytracing and geometry
+ dosimetricLateralCutOff; %relative dosimetric cut-off (in fraction of values calculated)
+
+ ssdDensityThreshold; % Threshold for SSD computation
+ useGivenEqDensityCube; % Use the given density cube ct.cube and omit conversion from cubeHU.
+ ignoreOutsideDensities; % Ignore densities outside of cst contours
+
+ numOfDijFillSteps = 10; % Number of times during dose calculation the temporary containers are moved to a sparse matrix
+ end
+
+ properties (SetAccess = protected)
+ effectiveLateralCutOff; %internal cutoff to be used, computed from machine/pencil-beam kernel properties and geometric/dosimetric cutoff settings
+ end
+
+ properties (SetAccess = protected, GetAccess = public)
+ tmpMatrixContainers; % temporary containers for
+ numOfBixelsContainer; % number of used bixel container
+
+ radDepthCubes = {}; % only stored if property set accordingly
+
+ cubeWED; % relative electron density / stopping power cube
+ hlut; % hounsfield lookup table to craete relative electron density cube
+ end
+
+ methods
+ function this = matRad_PencilBeamEngineAbstract(pln)
+ if nargin < 1
+ pln = [];
+ end
+
+ this = this@DoseEngines.matRad_DoseEngineBase(pln);
+ end
+
+ function setDefaults(this)
+ setDefaults@DoseEngines.matRad_DoseEngineBase(this);
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Set defaults
+ this.geometricLateralCutOff = matRad_cfg.defaults.propDoseCalc.geometricLateralCutOff;
+ this.dosimetricLateralCutOff = matRad_cfg.defaults.propDoseCalc.dosimetricLateralCutOff;
+ this.useGivenEqDensityCube = matRad_cfg.defaults.propDoseCalc.useGivenEqDensityCube;
+ this.ignoreOutsideDensities = matRad_cfg.defaults.propDoseCalc.ignoreOutsideDensities;
+ this.ssdDensityThreshold = matRad_cfg.defaults.propDoseCalc.ssdDensityThreshold;
+ end
+ end
+
+ % Should be abstract methods but in order to satisfy the compatibility
+ % with OCTAVE we can't use abstract methods. If OCTAVE at some point
+ % in the far future implements this feature this should be abstract again.
+ methods (Access = protected) %Abstract
+ function bixel = computeBixel(this,currRay,k)
+ throw(MException('MATLAB:class:AbstractMember','Abstract function computeBixel of your PencilBeam DoseEngine needs to be implemented!'));
+ end
+ end
+
+ methods (Access = protected)
+
+ function dij = calcDose(this,ct,cst,stf)
+ matRad_cfg = MatRad_Config.instance();
+
+ % initialize
+ dij = this.initDoseCalc(ct,cst,stf);
+
+ %Create X Y Z vectors if not present
+ ct = matRad_getWorldAxes(ct);
+
+
+ for shiftScen = 1:this.multScen.totNumShiftScen
+
+ %Find first instance of the shift to select the shift values
+ ixShiftScen = find(this.multScen.linearMask(:,2) == shiftScen,1);
+
+ scenStf = stf;
+ % manipulate isocenter
+ for k = 1:numel(scenStf)
+ scenStf(k).isoCenter = scenStf(k).isoCenter + this.multScen.isoShift(ixShiftScen,:);
+ end
+
+ if this.multScen.totNumShiftScen > 1
+ matRad_cfg.dispInfo('\tShift scenario %d of %d: \n',shiftScen,this.multScen.totNumShiftScen);
+ end
+
+ bixelCounter = 0;
+
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ for i = 1:dij.numOfBeams % loop over all beams
+
+ %Initialize Beam Geometry
+ currBeam = this.initBeam(dij,ct,cst,scenStf,i);
+
+ %Keep tabs on bixels computed in this beam
+ bixelBeamCounter = 0;
+
+ %Ray calculation
+ for j = 1:currBeam.numOfRays % loop over all rays / for photons we only have one bixel per ray! For field based dose calc, a ray equals a shape
+
+ %Initialize Ray Geometry
+ currRay = this.initRay(currBeam,j);
+
+ for ctScen = 1:this.multScen.numOfCtScen
+ for rangeShiftScen = 1:this.multScen.totNumRangeScen
+ fullScenIdx = this.multScen.sub2scenIx(ctScen,shiftScen,rangeShiftScen);
+
+ if this.multScen.scenMask(fullScenIdx)
+ %TODO: This shows we probably need
+ %better scenario management
+ %Gets linear index in scenario cell array
+
+
+ scenRay = this.extractSingleScenarioRay(currRay,fullScenIdx);
+
+ for k = 1:currRay.numOfBixels
+ %Bixel Computation
+ currBixel = this.computeBixel(scenRay,k);
+
+ % save computation time and memory
+ % by sequentially filling the sparse matrix dose.dij from the cell array
+ dij = this.fillDij(currBixel,dij,scenStf,fullScenIdx,i,j,k,bixelCounter + k);
+ end
+ end
+ end
+ end
+
+ % Progress Update & Bookkeeping
+ bixelCounter = bixelCounter + currRay.numOfBixels;
+ bixelBeamCounter = bixelBeamCounter + currRay.numOfBixels;
+ this.progressUpdate(bixelCounter,dij.totalNumOfBixels);
+ end
+ end
+ end
+
+ %Finalize dose calculation
+ dij = this.finalizeDose(dij);
+ end
+
+ function dij = initDoseCalc(this,ct,cst,stf)
+ % modified inherited method of the superclass DoseEngine,
+ % containing intialization which are specificly needed for
+ % pencil beam calculation and not for other engines
+
+ dij = initDoseCalc@DoseEngines.matRad_DoseEngineBase(this,ct,cst,stf);
+
+ matRad_cfg = MatRad_Config.instance();
+
+ % calculate rED or rSP from HU or take provided wedCube
+ if this.useGivenEqDensityCube && ~isfield(ct,'cube')
+ matRad_cfg.dispWarning('HU Conversion requested to be omitted but no ct.cube exists! Will override and do the conversion anyway!');
+ this.useGivenEqDensityCube = false;
+ end
+
+ if this.useGivenEqDensityCube
+ matRad_cfg.dispInfo('Omitting HU to rED/rSP conversion and using existing ct.cube!\n');
+ else
+ ct = matRad_calcWaterEqD(ct, stf); % Maybe we can avoid duplicating the CT here?
+ end
+
+ this.cubeWED = ct.cube;
+ if isfield(ct,'hlut')
+ this.hlut = ct.hlut;
+ end
+
+ % ignore densities outside of contours
+ if this.ignoreOutsideDensities
+ eraseCtDensMask = ones(prod(ct.cubeDim),1);
+ eraseCtDensMask(this.VctGrid) = 0;
+ for i = 1:ct.numOfCtScen
+ this.cubeWED{i}(eraseCtDensMask == 1) = 0;
+ end
+ end
+
+ % Allocate memory for quantity containers
+ dij = this.allocateQuantityMatrixContainers(dij,{'physicalDose'});
+ end
+
+ function dij = allocateQuantityMatrixContainers(this,dij,names)
+ if this.calcDoseDirect
+ this.numOfBixelsContainer = 1;
+ else
+ this.numOfBixelsContainer = ceil(dij.totalNumOfBixels/this.numOfDijFillSteps);
+ end
+
+ %Loop over all requested quantities
+ for n = 1:numel(names)
+ %Create Cell arrays for container and dij
+ szContainer = [this.numOfBixelsContainer size(this.multScen.scenMask)];
+ this.tmpMatrixContainers.(names{n}) = cell(szContainer);
+ dij.(names{n}) = cell(size(this.multScen.scenMask));
+
+ %Now preallocate a matrix in each active scenario using the
+ %scenmask
+ if this.calcDoseDirect
+ dij.(names{n})(this.multScen.scenMask) = {zeros(dij.doseGrid.numOfVoxels,this.numOfColumnsDij)};
+ else
+ %We preallocate a sparse matrix with sparsity of
+ %1e-3 to make the filling slightly faster
+ %TODO: the preallocation could probably
+ %have more accurate estimates
+ dij.(names{n})(this.multScen.scenMask) = {spalloc(dij.doseGrid.numOfVoxels,this.numOfColumnsDij,round(prod(dij.doseGrid.numOfVoxels,this.numOfColumnsDij)*1e-3))};
+ end
+ end
+ end
+
+ function currBeam = initBeam(this,dij,ct,cst,stf,i)
+ % Method for initializing the beams for analytical pencil beam
+ % dose calculation
+ %
+ % call
+ % this.initBeam(ct,stf,dij,i)
+ %
+ % input
+ % ct: matRad ct struct
+ % cst: matRad cst struct
+ % stf: matRad steering information struct
+ % i: index of beam
+ %
+ % output
+ % dij: updated dij struct
+
+ matRad_cfg = MatRad_Config.instance();
+ if numel(stf) > 1
+ matRad_cfg.dispInfo('Beam %d of %d:\n',i,numel(stf));
+ end
+
+ currBeam = stf(i);
+ currBeam.beamIndex = i;
+
+ % convert voxel indices to real coordinates using iso center of beam i
+ coordsV = this.voxWorldCoords - currBeam.isoCenter;
+ coordsVdoseGrid = this.voxWorldCoordsDoseGrid - currBeam.isoCenter;
+
+ % Get Rotation Matrix
+ % Do not transpose matrix since we usage of row vectors &
+ % transformation of the coordinate system need double transpose
+
+ currBeam.rotMat_system_T = matRad_getRotationMatrix(currBeam.gantryAngle,currBeam.couchAngle);
+
+ % Rotate coordinates (1st couch around Y axis, 2nd gantry movement)
+ rot_coordsV = coordsV*currBeam.rotMat_system_T;
+ rot_coordsVdoseGrid = coordsVdoseGrid*currBeam.rotMat_system_T;
+
+ rot_coordsV = rot_coordsV - currBeam.sourcePoint_bev;
+ rot_coordsVdoseGrid = rot_coordsVdoseGrid - currBeam.sourcePoint_bev;
+
+ % calculate geometric distances
+ geoDistVdoseGrid(1:ct.numOfCtScen)= {sqrt(sum(rot_coordsVdoseGrid.^2,2))};
+
+ % Calculate radiological depth cube
+ matRad_cfg.dispInfo('matRad: calculate radiological depth cube... ');
+
+ ct.cube = this.cubeWED;
+ if this.keepRadDepthCubes
+ [radDepthVctGrid, currBeam.radDepthCube] = matRad_rayTracing(currBeam,ct,this.VctGrid,rot_coordsV,this.effectiveLateralCutOff);
+
+ currBeam.radDepthCube = cellfun(@(rD) matRad_interp3(dij.ctGrid.x, dij.ctGrid.y, dij.ctGrid.z, rD, ...
+ dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'nearest'),currBeam.radDepthCube,'UniformOutput',false);
+ this.radDepthCubes(i,:) = currBeam.radDepthCube(:);
+ else
+ radDepthVctGrid = matRad_rayTracing(currBeam,ct,this.VctGrid,rot_coordsV,this.effectiveLateralCutOff);
+ end
+
+ % interpolate radiological depth cube to dose grid resolution
+ radDepthVdoseGrid = this.interpRadDepth(ct,1:ct.numOfCtScen,this.VctGrid,this.VdoseGrid,dij.ctGrid,dij.doseGrid,radDepthVctGrid);
+
+ % limit rotated coordinates to positions where ray tracing is availabe
+ %radDepthsMat = cellfun(@(radDepthCube) matRad_interp3(dij.ctGrid.x, dij.ctGrid.y, dij.ctGrid.z,radDepthCube,dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'nearest'),radDepthsMat,'UniformOutput',false);
+
+ %Find valid coordinates
+ coordIsValid = cellfun(@isfinite, radDepthVdoseGrid,'UniformOutput',false); %Reduce coordinates for finite values
+ currBeam.validCoords = cellfun(@and,coordIsValid,this.VdoseGridScenIx,'UniformOutput',false); %Reduce coordinates according to scenario
+ currBeam.validCoordsAll = any(cell2mat(coordIsValid),2);
+
+ currBeam.radDepths = radDepthVdoseGrid;
+ currBeam.geoDepths = geoDistVdoseGrid;
+ currBeam.bevCoords = rot_coordsVdoseGrid;
+
+ % compute SSDs
+ currBeam = matRad_computeSSD(currBeam,ct,'densityThreshold',this.ssdDensityThreshold);
+
+ matRad_cfg.dispInfo('done.\n');
+
+ %Reinitialize Progress:
+ %matRad_progress(1,1000);
+ end
+
+ function radDepthVdoseGrid = interpRadDepth(~,ct,ctScen,V,Vcoarse,ctGrid,doseGrid,radDepthVctGrid)
+ for i = 1:numel(ctScen)
+ ctScenNum = ctScen(i);
+
+ radDepthCube = NaN*ones(ct.cubeDim);
+ radDepthCube(V(~isnan(radDepthVctGrid{1}))) = radDepthVctGrid{ctScenNum}(~isnan(radDepthVctGrid{1}));
+
+ % interpolate cube - cube is now stored in Y X Z
+ coarseRadDepthCube = matRad_interp3(ctGrid.x,ctGrid.y',ctGrid.z,radDepthCube,doseGrid.x,doseGrid.y',doseGrid.z);
+ radDepthVdoseGrid{ctScenNum} = coarseRadDepthCube(Vcoarse);
+ end
+ end
+
+ function ray = initRay(this,currBeam,j)
+ ray = currBeam.ray(j);
+
+ ray.beamIndex = currBeam.beamIndex;
+ ray.rayIndex = j;
+ ray.isoCenter = currBeam.isoCenter;
+
+ if ~isfield(currBeam,'numOfBixelsPerRay')
+ ray.numOfBixels = 1;
+ else
+ ray.numOfBixels = currBeam.numOfBixelsPerRay(j);
+ end
+
+ ray.sourcePoint_bev = currBeam.sourcePoint_bev;
+ ray.SAD = currBeam.SAD;
+ ray.bixelWidth = currBeam.bixelWidth;
+
+ ray = this.getRayGeometryFromBeam(ray,currBeam);
+
+ %ray = this.computeRaySSD(this,ray); %Is already done in the
+ %initBeam function
+ end
+
+ function scenRay = extractSingleScenarioRay(this,ray,scenIdx)
+
+ %Gets number of scenario
+ scenNum = this.multScen.scenNum(scenIdx);
+ ctScen = this.multScen.linearMask(scenNum,1);
+
+ %First, create a ray of the
+ %specific scenario to adapt rad
+ %depths
+ scenRay = ray;
+ scenRay.radDepths = scenRay.radDepths{ctScen};
+ scenRay.radDepths = (1+this.multScen.relRangeShift(scenNum))*scenRay.radDepths + this.multScen.absRangeShift(scenNum);
+ scenRay.radialDist_sq = scenRay.radialDist_sq{ctScen};
+ scenRay.ix = scenRay.ix{ctScen};
+
+ if this.multScen.absRangeShift(scenNum) < 0
+ %TODO: better way to handle this?
+ scenRay.radDepths(scenRay.radDepths < 0) = 0;
+ end
+
+ if isfield(scenRay,'geoDepths')
+ scenRay.geoDepths = scenRay.geoDepths{ctScen};
+ end
+
+ if isfield(scenRay,'latDists')
+ scenRay.latDists = scenRay.latDists{ctScen};
+ end
+
+ if isfield(scenRay,'isoLatDists')
+ scenRay.isoLatDists = scenRay.isoLatDists{ctScen};
+ end
+ end
+
+ function ray = getRayGeometryFromBeam(this,ray,currBeam)
+ lateralRayCutOff = this.getLateralDistanceFromDoseCutOffOnRay(ray);
+
+ % Ray tracing for beam i and ray j
+ [ix,radialDist_sq,latDists,isoLatDists] = this.calcGeoDists(currBeam.bevCoords, ...
+ ray.sourcePoint_bev, ...
+ ray.targetPoint_bev, ...
+ ray.SAD, ...
+ currBeam.validCoordsAll, ...
+ lateralRayCutOff);
+
+ %Subindex given the relevant indices from the geometric
+ %distance calculation
+ ray.validCoords = cellfun(@(beamIx) beamIx & ix,currBeam.validCoords,'UniformOutput',false);
+ ray.ix = cellfun(@(ixInGrid) this.VdoseGrid(ixInGrid),ray.validCoords,'UniformOutput',false);
+
+ %subCoords = cellfun(@(beamIx) beamIx(ix),currBeam.validCoords,'UniformOutput',false);
+ %ray.radialDist_sq = cellfun(@(subix) radialDist_sq(subix),radialDist_sq,subCoords);
+ ray.radialDist_sq = cellfun(@(beamIx) radialDist_sq(beamIx(ix)),currBeam.validCoords,'UniformOutput',false);
+ ray.latDists = cellfun(@(beamIx) latDists(beamIx(ix),:),currBeam.validCoords,'UniformOutput',false);
+ ray.isoLatDists = cellfun(@(beamIx) isoLatDists(beamIx(ix),:),currBeam.validCoords,'UniformOutput',false);
+
+ ray.validCoordsAll = any(cell2mat(ray.validCoords),2);
+
+ ray.geoDepths = cellfun(@(rD,ix) rD(ix),currBeam.geoDepths,ray.validCoords,'UniformOutput',false); %usually not needed for particle beams
+ ray.radDepths = cellfun(@(rD,ix) rD(ix),currBeam.radDepths,ray.validCoords,'UniformOutput',false);
+ %ray.ix = currBeam.ixRadDepths(ix);
+ %ray.subIxVdoseGrid = currBeam.subIxVdoseGrid(ix);
+ end
+
+ function lateralRayCutOff = getLateralDistanceFromDoseCutOffOnRay(this,ray)
+ lateralRayCutOff = this.effectiveLateralCutOff;
+ end
+
+ function dij = fillDij(this,bixel,dij,stf,scenIdx,currBeamIdx,currRayIdx,currBixelIdx,counter)
+ % method for filling the dij struct with the computed dose cube
+ % last step in bixel dose calculation
+
+ %Only fill if we actually had bixel (indices) to compute
+ if ~isempty(bixel) || ~isempty(bixel.ix)
+ % Store in temporary containers to limit matrix filling
+ names = fieldnames(this.tmpMatrixContainers);
+ bixelContainerColIx = mod(counter-1,this.numOfBixelsContainer)+1;
+ subScenIdx = cell(ndims(this.multScen.scenMask),1);
+ [subScenIdx{:}] = ind2sub(size(this.multScen.scenMask),scenIdx);
+ for q = 1:numel(names)
+ qName = names{q};
+ if this.calcDoseDirect
+ %We can omit the resetting to zero because we will
+ %use only the indices we write into
+ %this.tmpMatrixContainers.(qName){bixelContainerColIx,1} = zeros(dij.doseGrid.numOfVoxels,1);
+ %this.tmpMatrixContainers.(qName){bixelContainerColIx,1}(this.VdoseGrid(bixel.ix)) = bixel.(qName);
+ else
+ this.tmpMatrixContainers.(qName){bixelContainerColIx,subScenIdx{:}} = sparse(bixel.ix,1,bixel.(qName),dij.doseGrid.numOfVoxels,1);
+ end
+ end
+
+ % Check if we write to the matrix
+ if mod(counter,this.numOfBixelsContainer) == 0 || counter == dij.totalNumOfBixels
+ if ~this.calcDoseDirect
+ if nargin < 8
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Total bixel counter not provided in fillDij');
+ end
+
+ dijColIx = (ceil(counter/this.numOfBixelsContainer)-1)*this.numOfBixelsContainer+1:counter;
+ containerIx = 1:bixelContainerColIx;
+ weight = 1;
+ else
+ dijColIx = currBeamIdx;
+ containerIx = 1;
+ if isfield(stf(currBeamIdx).ray(currRayIdx),'weight') && numel(stf(currBeamIdx).ray(currRayIdx).weight) >= currBixelIdx
+ weight = stf(currBeamIdx).ray(currRayIdx).weight(currBixelIdx);
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('No weight available for beam %d, ray %d, bixel %d',currBeamIdx,currRayIdx,currBixelIdx);
+ end
+ end
+
+ % Iterate through all quantities
+ for q = 1:numel(names)
+ qName = names{q};
+ if ~this.calcDoseDirect
+ dij.(qName){scenIdx}(:,dijColIx) = [this.tmpMatrixContainers.(qName){containerIx,subScenIdx{:}}];
+ %Clean container
+ this.tmpMatrixContainers.(qName)(containerIx,subScenIdx{:}) = cell(numel(containerIx,subScenIdx{:}));
+ else
+ %dij.(qName){1}(this.VdoseGrid(bixel.ix),dijColIx) = dij.(qName){1}(this.VdoseGrid(bixel.ix),dijColIx) + weight * this.tmpMatrixContainers.(qName){containerIx,1}(this.VdoseGrid(bixel.ix));
+ dij.(qName){scenIdx}(bixel.ix,dijColIx) = dij.(qName){scenIdx}(bixel.ix,dijColIx) + weight * bixel.(qName);
+ end
+ end
+ end
+ end
+
+ %Bookkeeping of bixel numbers
+ % remember beam and bixel number
+ if this.calcDoseDirect
+ dij.beamNum(currBeamIdx) = currBeamIdx;
+ dij.rayNum(currBeamIdx) = currBeamIdx;
+ dij.bixelNum(currBeamIdx) = currBeamIdx;
+ else
+ dij.beamNum(counter) = currBeamIdx;
+ dij.rayNum(counter) = currRayIdx;
+ dij.bixelNum(counter) = currBixelIdx;
+ end
+ end
+
+ %{
+ function ray = computeRaySSD(this,ray)
+ [alpha,~,rho,d12,~] = matRad_siddonRayTracer(ray.isoCenter, ...
+ ct.resolution, ...
+ ray.sourcePoint, ...
+ ray.targetPoint, ...
+ this.cubeWED(1));
+ ixSSD = find(rho{1} > this.ssdDensityThreshold,1,'first');
+
+
+ if isempty(ixSSD)
+ matRad_cfg.dispError('ray does not hit patient. Trying to fix afterwards...');
+ boolShowWarning = false;
+ elseif ixSSD(1) == 1
+ matRad_cfg.dispWarning('Surface for SSD calculation starts directly in first voxel of CT!');
+ boolShowWarning = false;
+ end
+
+ % calculate SSD
+ ray.SSD = double(d12* alpha(ixSSD));
+ end
+ %}
+
+ function dij = finalizeDose(this,dij)
+ %TODO: We could also do this by default for all engines, but
+ %this would require to add some additional quantity management
+ %(i.e., which quantity an engine could compute)
+ % remove dose influence for voxels outside of segmentations for every ct
+ % scenario
+ for i = 1:this.multScen.numOfCtScen
+ % generate index set to erase
+ ix = setdiff(1:dij.doseGrid.numOfVoxels,this.VdoseGrid);
+
+ for j = 1:this.multScen.totNumShiftScen
+ for k = 1:this.multScen.totNumRangeScen
+
+ if this.multScen.scenMask(i,j,k)
+
+ %loop over all used quantities
+ qNames = fieldnames(this.tmpMatrixContainers);
+ for qIx = 1:numel(qNames)
+ dij.(qNames{qIx}){i,j,k}(ix,:) = 0;
+ end
+ end
+
+ end
+ end
+ end
+
+ if this.keepRadDepthCubes && ~isempty(this.radDepthCubes)
+ dij.radDepthCubes = this.radDepthCubes;
+ end
+
+ dij = this.finalizeDose@DoseEngines.matRad_DoseEngineBase(dij);
+ end
+ end
+
+ methods (Static)
+
+ function [ix,rad_distancesSq,latDists,isoLatDists] = ...
+ calcGeoDists(rot_coords_bev, sourcePoint_bev, targetPoint_bev, SAD, radDepthIx, lateralCutOff)
+ % matRad calculation of lateral distances from central ray
+ % used for dose calculation
+ %
+ % call
+ % [ix,rad_distancesSq,isoLatDistsX,isoLatDistsZ] = ...
+ % this.calcGeoDists(rot_coords_bev, ...
+ % sourcePoint_bev, ...
+ % targetPoint_bev, ...
+ % SAD, ...
+ % radDepthIx, ...
+ % lateralCutOff)
+ %
+ % input
+ % rot_coords_bev: coordinates in bev of the voxels with index V,
+ % where also ray tracing results are availabe
+ % sourcePoint_bev: source point in voxel coordinates in beam's eye view
+ % targetPoint_bev: target point in voxel coordinated in beam's eye view
+ % SAD: source-to-axis distance
+ % radDepthIx: sub set of voxels for which radiological depth
+ % calculations are available
+ % lateralCutOff: lateral cutoff specifying the neighbourhood for
+ % which dose calculations will actually be performed
+ %
+ % output
+ % ix: indices of voxels where we want to compute dose
+ % influence data
+ % rad_distancesSq: squared radial distance to the central ray (where the
+ % actual computation of the radiological depth takes place)
+ % isoLatDistsX: lateral x-distance to the central ray projected to
+ % iso center plane
+ % isoLatDistsZ: lateral z-distance to the central ray projected to
+ % iso center plane
+ % latDistsX: lateral x-distance to the central ray
+ % latDistsZ: lateral z-distance to the central ray
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2015 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % ROTATE A SINGLE BEAMLET AND ALIGN WITH BEAMLET WHO PASSES THROUGH
+ % ISOCENTER
+
+ % Put [0 0 0] position in the source point for beamlet who passes through
+ % isocenter
+ a = -sourcePoint_bev';
+
+ % Normalize the vector
+ a = a/norm(a);
+
+ % Put [0 0 0] position in the source point for a single beamlet
+ b = (targetPoint_bev - sourcePoint_bev)';
+
+ % Normalize the vector
+ b = b/norm(b);
+
+ % Define function for obtain rotation matrix.
+ if all(a==b) % rotation matrix corresponds to eye matrix if the vectors are the same
+ rot_coords_temp = rot_coords_bev(radDepthIx,:);
+ else
+ % Define rotation matrix
+ ssc = @(v) [0 -v(3) v(2); v(3) 0 -v(1); -v(2) v(1) 0];
+ R = eye(3) + ssc(cross(a,b)) + ssc(cross(a,b))^2*(1-dot(a,b))/(norm(cross(a,b))^2);
+
+ % Rotate every CT voxel
+ rot_coords_temp = rot_coords_bev(radDepthIx,:)*R;
+ end
+
+ % Put [0 0 0] position CT in center of the beamlet.
+ %latDistsX = rot_coords_temp(:,1) + sourcePoint_bev(1);
+ %latDistsZ = rot_coords_temp(:,3) + sourcePoint_bev(3);
+ latDists = rot_coords_temp(:,[1 3]) + sourcePoint_bev([1 3]);
+
+ % check of radial distance exceeds lateral cutoff (projected to iso center)
+ %rad_distancesSq = latDistsX.^2 + latDistsZ.^2;
+ %subsetMask = rad_distancesSq <= (lateralCutOff/SAD)^2 * rot_coords_temp(:,2).^2;
+
+ rad_distancesSq = sum(latDists.^2,2);
+ subsetMask = rad_distancesSq <= (lateralCutOff/SAD)^2 * rot_coords_temp(:,2).^2;
+
+ %Apply mask for return quantities
+
+ % index list within considered voxels
+ %ix = radDepthIx(subsetMask);
+ ix = radDepthIx;
+ ix(ix) = subsetMask;
+
+ % return radial distances squared
+ if nargout > 1
+ rad_distancesSq = rad_distancesSq(subsetMask);
+ end
+
+ %lateral distances in X & Z
+ if nargout > 2
+ latDists = latDists(subsetMask,:);
+ end
+
+ % lateral distances projected onto isocenter
+ if nargout > 3
+ isoLatDists = latDists./rot_coords_temp(subsetMask,2)*SAD;
+ end
+ end
+ end
+
+ %% deprecated properties
+ properties (Dependent)
+ geometricCutOff; %deprecated property, replaced with geometricLateralCutOff
+ end
+
+ methods
+ function set.geometricCutOff(this,geoCutOff)
+ this.geometricLateralCutOff = geoCutOff;
+ this.warnDeprecatedEngineProperty('geometricCutOff','','geometricLateralCutOff');
+ end
+ function geoCutOff = get.geometricCutOff(this)
+ geoCutOff = this.geometricLateralCutOff;
+ this.warnDeprecatedEngineProperty('geometricCutOff','','geometricLateralCutOff');
+ end
+ end
+
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_PhotonOmpMCEngine.m b/matRad/doseCalc/+DoseEngines/matRad_PhotonOmpMCEngine.m
new file mode 100644
index 000000000..fc5d53ca2
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_PhotonOmpMCEngine.m
@@ -0,0 +1,631 @@
+classdef matRad_PhotonOmpMCEngine < DoseEngines.matRad_MonteCarloEngineAbstract
+ % Engine for photon dose calculation based on monte carlo
+ % for more informations see superclass
+ % DoseEngines.matRad_MonteCarloEngineAbstract
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2019 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ possibleRadiationModes = 'photons';
+ name = 'ompMC';
+ shortName = 'ompMC';
+ end
+
+ properties (SetAccess = public, GetAccess = public)
+ visBool = false; %binary switch to en/disable visualitzation
+ useCornersSCD = true; %false -> use ISO corners
+
+ % This factor calibrates to 1 Gy in a %(5x5)cm^2 open field (1 bixel) at
+ % 5cm depth for SSD = 900 which corresponds to the calibration for the
+ % analytical base data.
+ absCalibrationFactor = 3.49056 * 1e12; %Approximate!
+
+ omcFolder;
+ end
+
+ properties (SetAccess = protected, GetAccess = public)
+ ompMCoptions;
+ ompMCgeo;
+ ompMCsource;
+
+ cubeHU; %resamples HU cube
+ cubeRho; %density cube
+ cubeMatIx; %material assignment
+ end
+
+ properties (Constant)
+ scale = 10;
+ end
+
+ methods
+ function this = matRad_PhotonOmpMCEngine(pln)
+ % Constructor
+ %
+ % call
+ % engine = DoseEngines.matRad_DoseEnginePhotonsOmpMCct,stf,pln,cst)
+ %
+ % input
+ % pln: matRad plan meta information struct
+
+ if nargin < 1
+ pln = [];
+ end
+
+ % call superclass constructor
+ this = this@DoseEngines.matRad_MonteCarloEngineAbstract(pln);
+
+ matRad_cfg = MatRad_Config.instance();
+ this.omcFolder = [matRad_cfg.matRadRoot filesep 'thirdParty' filesep 'ompMC'];
+
+ if ~matRad_checkMexFileExists('omc_matrad') %exist('matRad_ompInterface','file') ~= 3
+ matRad_cfg.dispWarning('Compiled mex interface not found. Trying to compile the ompMC interface on the fly!');
+ try
+ this.compileOmpMCInterface(this.omcFolder);
+ catch MException
+ matRad_cfg.dispError('Could not find/generate mex interface for MC dose calculation.\nCause of error:\n%s\n Please compile it yourself (preferably with OpenMP support).',MException.message);
+ end
+ end
+ end
+ end
+
+ methods (Access = protected)
+ function dij = calcDose(this,ct,cst,stf)
+ % matRad ompMC monte carlo photon dose calculation wrapper
+ % can be automaticly called through matRad_calcDose or
+ % matRad_calcPhotonDoseMC
+ %
+ % call
+ % dij = this.calcDose(ct,stf,pln,cst)
+ %
+ % input
+ % ct: matRad ct struct
+ % stf: matRad steering information struct
+ % cst: matRad cst struct
+ % output
+ % dij: matRad dij struct
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2018 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %run initDoseCalc as usual
+ dij = this.initDoseCalc(ct,cst,stf);
+
+ %ompMC for matRad returns dose/history * nHistories.
+
+ bixelWidth = unique([stf.bixelWidth]);
+
+ if numel(bixelWidth) > 1
+ matRad_cfg.dispWarning('Varying bixel width in stf, calibartion might be wrong!')
+ bixelWidth = mean(bixelWidth);
+ end
+
+ %Now we have to calibrate to the the beamlet width.
+ calibrationFactor = this.absCalibrationFactor * (bixelWidth/50)^2;
+
+ %Create X Y Z vectors if not present
+ ct = matRad_getWorldAxes(ct);
+
+
+ scenCount = 0;
+ %run over all scenarios
+ for scenarioIx = 1:this.multScen.totNumScen
+ ctScen = this.multScen.linearMask(scenarioIx,1);
+ shiftScen = this.multScen.linearMask(scenarioIx,2);
+ rangeShiftScen = this.multScen.linearMask(scenarioIx,3);
+
+ if this.multScen.scenMask(ctScen,shiftScen,rangeShiftScen)
+ scenCount = scenCount + 1;
+
+ % manipulate isocenter
+ shiftedIsoCenter = matRad_world2cubeCoords(vertcat(stf(:).isoCenter), this.doseGrid) + this.multScen.isoShift(scenarioIx,:);
+
+ this.ompMCgeo.isoCenter = shiftedIsoCenter;
+ tmpStf = stf;
+
+ for k = 1:length(tmpStf)
+ tmpStf(k).isoCenter = shiftedIsoCenter(k,:);
+ end
+
+ % load ompMC source
+ this.getOmpMCsource(tmpStf);
+
+ % Book keeping for dij
+ counter = 0;
+ for i = 1:dij.numOfBeams
+ for j = 1:tmpStf(i).numOfRays
+ counter = counter + 1;
+ dij.beamNum(counter) = i;
+ dij.rayNum(counter) = j;
+ dij.bixelNum(counter) = j;
+ end
+ end
+
+ if this.multScen.totNumScen == 1
+ matRad_cfg.dispInfo('matRad: OmpMC photon dose calculation... \n');
+ else
+ matRad_cfg.dispInfo('matRad: OmpMC photon dose calculation for scenario %d of %d... \n',scenCount,this.multScen.totNumScen);
+ end
+
+ %Call the Monte Carlo simulation and catch possible mex
+ %interface issues
+ try
+ %If we ask for variance, a field in the dij will be filled
+ if this.outputMCvariance
+ [dij.physicalDose{ctScen,shiftScen,rangeShiftScen},dij.physicalDose_MCvar{ctScen,shiftScen,rangeShiftScen}] = omc_matrad(this.cubeRho{ctScen},this.cubeMatIx{ctScen},this.ompMCgeo,this.ompMCsource,this.ompMCoptions);
+ else
+ [dij.physicalDose{ctScen,shiftScen,rangeShiftScen}] = omc_matrad(this.cubeRho{ctScen},this.cubeMatIx{ctScen},this.ompMCgeo,this.ompMCsource,this.ompMCoptions);
+ end
+ catch ME
+ errorString = [ME.message '\nThis error was thrown by the MEX-interface of ompMC.\nMex interfaces can raise compatability issues which may be resolved by compiling them by hand directly on your particular system.'];
+ matRad_cfg.dispError(errorString);
+ end
+
+ %Calibrate the dose with above factor
+ dij.physicalDose{scenarioIx} = dij.physicalDose{scenarioIx} * calibrationFactor;
+ if isfield(dij,'physicalDose_MCvar')
+ dij.physicalDose_MCvar{scenarioIx} = dij.physicalDose_MCvar{scenarioIx} * calibrationFactor^2;
+ end
+
+ if this.calcDoseDirect
+ dij.physicalDose{scenarioIx} = dij.physicalDose{scenarioIx} .* this.directWeights';
+
+ if isfield(dij,'physicalDose_MCvar')
+ dij.physicalDose_MCvar{scenarioIx} = dij.physicalDose_MCvar{scenarioIx} .* (this.directWeights').^2;
+ end
+ end
+ end
+ end
+
+ %Finalize dose calculation
+ dij = this.finalizeDose(dij);
+ end
+
+ function dij = initDoseCalc(this,ct,cst,stf)
+
+ dij = initDoseCalc@DoseEngines.matRad_MonteCarloEngineAbstract(this,ct,cst,stf);
+
+ % set up arrays for book keeping
+ dij.bixelNum = NaN*ones(dij.totalNumOfBixels,1);
+ dij.rayNum = NaN*ones(dij.totalNumOfBixels,1);
+ dij.beamNum = NaN*ones(dij.totalNumOfBixels,1);
+
+ if this.calcDoseDirect
+ this.numHistoriesPerBeamlet = ceil(this.numHistoriesDirect / dij.totalNumOfBixels); %Use ceil to avoid 0 when number of histories is small
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('The ompMC engine implements beamlet-wise calculation only at the moment, so we will set the histories per bemlet to numHistoriesDirect/numBeamlets: %d!');
+ end
+
+ dij.numHistoriesPerBeamlet = this.numHistoriesPerBeamlet;
+
+ %% Setup OmpMC options / parameters
+ this.setOmpMCoptions();
+
+ % conversion from HU to densities & materials
+ this.materialConversion(dij.ctGrid,dij.doseGrid,ct);
+
+ % Create the Geometry
+ this.getOmpMCgeometry(dij.doseGrid);
+
+ %% Create Beamlet source
+ %Get Isocenter in cube coordinates on the dose grid
+ tmpStf = stf;
+ for k = 1:length(stf)
+ shiftedIsoCenter = matRad_world2cubeCoords(vertcat(stf(:).isoCenter),this.doseGrid);
+ tmpStf(k).isoCenter = shiftedIsoCenter(k,:);
+ end
+
+ this.getOmpMCsource(tmpStf);
+ end
+ end
+
+ methods (Access = private)
+ function setOmpMCoptions(obj)
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ %display options
+ obj.ompMCoptions.verbose = matRad_cfg.logLevel - 1;
+
+ % start MC control
+ obj.ompMCoptions.nHistories = obj.numHistoriesPerBeamlet;
+ obj.ompMCoptions.nSplit = 20;
+ obj.ompMCoptions.nBatches = 10;
+ obj.ompMCoptions.randomSeeds = [97 33];
+
+ %start source definition
+ obj.ompMCoptions.spectrumFile = [obj.omcFolder filesep 'spectra' filesep 'mohan6.spectrum'];
+ obj.ompMCoptions.monoEnergy = 0.1;
+ obj.ompMCoptions.charge = 0;
+ obj.ompMCoptions.sourceGeometry = 'gaussian';
+ obj.ompMCoptions.sourceGaussianWidth = obj.getSourceWidthFromPenumbra./obj.scale;
+
+ % start MC transport
+ obj.ompMCoptions.dataFolder = [obj.omcFolder filesep 'data' filesep];
+ obj.ompMCoptions.pegsFile = [obj.omcFolder filesep 'pegs4' filesep '700icru.pegs4dat'];
+ obj.ompMCoptions.pgs4formFile = [obj.omcFolder filesep 'pegs4' filesep 'pgs4form.dat'];
+
+ obj.ompMCoptions.global_ecut = 0.7;
+ obj.ompMCoptions.global_pcut = 0.010;
+
+ % Relative Threshold for dose
+ obj.ompMCoptions.relDoseThreshold = 1 - obj.relativeDosimetricCutOff;
+
+ % Output folders
+ obj.ompMCoptions.outputFolder = [obj.omcFolder filesep 'output' filesep];
+ end
+
+ function sigmaGauss = getSourceWidthFromPenumbra(obj)
+ % gaussian filter to model penumbra from (measured) machine output / see diploma thesis siggel 4.1.2
+ matRad_cfg = MatRad_Config.instance();
+
+ if isfield(obj.machine.data,'penumbraFWHMatIso')
+ penumbraFWHM = obj.machine.data.penumbraFWHMatIso;
+ else
+ penumbraFWHM = 5;
+ matRad_cfg.dispWarning('photon machine file does not contain measured penumbra width in machine.data.penumbraFWHMatIso. Assuming 5 mm.');
+ end
+
+ sourceFWHM = penumbraFWHM * obj.machine.meta.SCD/(obj.machine.meta.SAD - obj.machine.meta.SCD);
+ sigmaGauss = sourceFWHM / sqrt(8*log(2)); % [mm]
+ end
+
+ function obj = materialConversion(obj,ctGrid,doseGrid,ct)
+ % conversion from HU to densities & materials
+ obj.cubeHU = cell(1,ct.numOfCtScen);
+ obj.cubeMatIx = cell(1,ct.numOfCtScen);
+ obj.cubeRho = cell(1,ct.numOfCtScen);
+
+ % Create Material Density Cube
+ material = obj.setupMaterials();
+
+ for s = 1:ct.numOfCtScen
+
+ obj.cubeHU{s} = matRad_interp3(ctGrid.x,ctGrid.y',ctGrid.z,ct.cubeHU{s}, ...
+ doseGrid.x,doseGrid.y',doseGrid.z,'nearest');
+
+ % projecting out of bounds HU values where necessary
+ if max(obj.cubeHU{s}(:)) > material{end,3}
+ matRad_cfg.dispWarning('Projecting out of range HU values');
+ obj.cubeHU{s}(obj.cubeHU{s}(:) > material{end,3}) = material{end,3};
+ end
+ if min(obj.cubeHU{s}(:)) < material{1,2}
+ matRad_cfg.dispWarning('Projecting out of range HU values');
+ obj.cubeHU{s}(obj.cubeHU{s}(:) < material{1,2}) = material{1,2};
+ end
+
+ % find material index
+ obj.cubeMatIx{s} = NaN*ones(doseGrid.dimensions,'int32');
+ for i = size(material,1):-1:1
+ obj.cubeMatIx{s}(obj.cubeHU{s} <= material{i,3}) = i;
+ end
+
+ % create an artificial HU lookup table
+ hlut = [];
+ for i = 1:size(material,1)
+ hlut = [hlut;material{i,2} material{i,4};material{i,3}-1e-10 material{i,5}]; % add eps for interpolation
+ end
+
+ obj.cubeRho{s} = interp1(hlut(:,1),hlut(:,2),obj.cubeHU{s});
+
+ end
+ end
+
+ function getOmpMCgeometry(obj,doseGrid)
+ obj.ompMCgeo.xBounds = (doseGrid.resolution.y * (0.5 + [0:doseGrid.dimensions(1)])) ./ obj.scale;
+ obj.ompMCgeo.yBounds = (doseGrid.resolution.x * (0.5 + [0:doseGrid.dimensions(2)])) ./ obj.scale;
+ obj.ompMCgeo.zBounds = (doseGrid.resolution.z * (0.5 + [0:doseGrid.dimensions(3)])) ./ obj.scale;
+
+ % Create Material Density Cube
+ obj.ompMCgeo.material = obj.setupMaterials();
+
+ %% debug visualization
+ if obj.visBool
+
+ figure;
+ hold on;
+
+ axis equal;
+
+ % ct box
+ ctCorner1 = [obj.ompMCgeo.xBounds(1) obj.ompMCgeo.yBounds(1) obj.ompMCgeo.zBounds(1)];
+ ctCorner2 = [obj.ompMCgeo.xBounds(end) obj.ompMCgeo.yBounds(end) obj.ompMCgeo.zBounds(end)];
+ plot3([ctCorner1(1) ctCorner2(1)],[ctCorner1(2) ctCorner1(2)],[ctCorner1(3) ctCorner1(3)],'k' );
+ plot3([ctCorner1(1) ctCorner2(1)],[ctCorner2(2) ctCorner2(2)],[ctCorner1(3) ctCorner1(3)],'k' );
+ plot3([ctCorner1(1) ctCorner1(1)],[ctCorner1(2) ctCorner2(2)],[ctCorner1(3) ctCorner1(3)],'k' );
+ plot3([ctCorner2(1) ctCorner2(1)],[ctCorner1(2) ctCorner2(2)],[ctCorner1(3) ctCorner1(3)],'k' );
+ plot3([ctCorner1(1) ctCorner2(1)],[ctCorner1(2) ctCorner1(2)],[ctCorner2(3) ctCorner2(3)],'k' );
+ plot3([ctCorner1(1) ctCorner2(1)],[ctCorner2(2) ctCorner2(2)],[ctCorner2(3) ctCorner2(3)],'k' );
+ plot3([ctCorner1(1) ctCorner1(1)],[ctCorner1(2) ctCorner2(2)],[ctCorner2(3) ctCorner2(3)],'k' );
+ plot3([ctCorner2(1) ctCorner2(1)],[ctCorner1(2) ctCorner2(2)],[ctCorner2(3) ctCorner2(3)],'k' );
+ plot3([ctCorner1(1) ctCorner1(1)],[ctCorner1(2) ctCorner1(2)],[ctCorner1(3) ctCorner2(3)],'k' );
+ plot3([ctCorner2(1) ctCorner2(1)],[ctCorner1(2) ctCorner1(2)],[ctCorner1(3) ctCorner2(3)],'k' );
+ plot3([ctCorner1(1) ctCorner1(1)],[ctCorner2(2) ctCorner2(2)],[ctCorner1(3) ctCorner2(3)],'k' );
+ plot3([ctCorner2(1) ctCorner2(1)],[ctCorner2(2) ctCorner2(2)],[ctCorner1(3) ctCorner2(3)],'k' );
+
+ xlabel('x [cm]');
+ ylabel('y [cm]');
+ zlabel('z [cm]');
+
+ rotate3d on;
+
+ end
+ end
+
+ function material = setupMaterials(~)
+ material = cell(4,5);
+ material{1,1} = 'AIR700ICRU';
+ material{1,2} = -1024;
+ material{1,3} = -974;
+ material{1,4} = 0.001;
+ material{1,5} = 0.044;
+ material{2,1} = 'LUNG700ICRU';
+ material{2,2} = -974;
+ material{2,3} = -724;
+ material{2,4} = 0.044;
+ material{2,5} = 0.302;
+ material{3,1} = 'ICRUTISSUE700ICRU';
+ material{3,2} = -724;
+ material{3,3} = 101;
+ material{3,4} = 0.302;
+ material{3,5} = 1.101;
+ material{4,1} = 'ICRPBONE700ICRU';
+ material{4,2} = 101;
+ material{4,3} = 1976;
+ material{4,4} = 1.101;
+ material{4,5} = 2.088;
+
+ end
+
+ function getOmpMCsource(obj,stf)
+ numOfBeams = numel(stf);
+
+ numOfBixelsPerBeam = [stf(:).numOfRays];
+ totalNumOfBixels = sum(numOfBixelsPerBeam);
+ beamSource = zeros(numOfBeams, 3);
+
+ bixelCorner = zeros(totalNumOfBixels,3);
+ bixelSide1 = zeros(totalNumOfBixels,3);
+ bixelSide2 = zeros(totalNumOfBixels,3);
+
+ beamNum = zeros(1,prod(totalNumOfBixels,numOfBeams));
+ counter = 0;
+
+ for i = 1:numOfBeams % loop over all beams
+
+ % define beam source in physical coordinate system in cm
+ beamSource(i,:) = (stf(i).sourcePoint + stf(i).isoCenter)/10;
+
+ for j = 1:stf(i).numOfRays % loop over all rays / for photons we only have one bixel per ray!
+
+ counter = counter + 1;
+
+ beamNum(counter) = i;
+
+ if obj.useCornersSCD
+ beamletCorners = stf(i).ray(j).rayCorners_SCD;
+ else
+ beamletCorners = stf(i).ray(j).beamletCornersAtIso;
+ end
+
+ % get bixel corner and delimiting vectors.
+ % a) change coordinate system (Isocenter cs-> physical cs) and units mm -> cm
+ currCorner = (beamletCorners(1,:) + stf(i).isoCenter) ./ obj.scale;
+ bixelCorner(counter,:) = currCorner;
+ bixelSide1(counter,:) = (beamletCorners(2,:) + stf(i).isoCenter) ./ obj.scale - currCorner;
+ bixelSide2(counter,:) = (beamletCorners(4,:) + stf(i).isoCenter) ./ obj.scale - currCorner;
+
+ if obj.visBool
+ for k = 1:4
+ currCornerVis = (beamletCorners(k,:) + stf(i).isoCenter)/10;
+ % rays connecting source and ray corner
+ plot3([beamSource(i,1) currCornerVis(1)],[beamSource(i,2) currCornerVis(2)],[beamSource(i,3) currCornerVis(3)],'b')
+ % connection between corners
+ lRayCorner = (beamletCorners(mod(k,4) + 1,:) + stf(i).isoCenter)/10;
+ plot3([lRayCorner(1) currCornerVis(1)],[lRayCorner(2) currCornerVis(2)],[lRayCorner(3) currCornerVis(3)],'r')
+ end
+ end
+
+ end
+
+ end
+
+ obj.ompMCsource.nBeams = numOfBeams;
+ obj.ompMCsource.iBeam = beamNum(:);
+
+ % Switch x and y directions to match ompMC cs.
+ obj.ompMCsource.xSource = beamSource(:,2);
+ obj.ompMCsource.ySource = beamSource(:,1);
+ obj.ompMCsource.zSource = beamSource(:,3);
+
+ obj.ompMCsource.nBixels = sum(numOfBixelsPerBeam(:));
+ obj.ompMCsource.xCorner = bixelCorner(:,2);
+ obj.ompMCsource.yCorner = bixelCorner(:,1);
+ obj.ompMCsource.zCorner = bixelCorner(:,3);
+
+ obj.ompMCsource.xSide1 = bixelSide1(:,2);
+ obj.ompMCsource.ySide1 = bixelSide1(:,1);
+ obj.ompMCsource.zSide1 = bixelSide1(:,3);
+
+ obj.ompMCsource.xSide2 = bixelSide2(:,2);
+ obj.ompMCsource.ySide2 = bixelSide2(:,1);
+ obj.ompMCsource.zSide2 = bixelSide2(:,3);
+
+ if obj.visBool
+ plot3(obj.ompMCsource.ySource,obj.ompMCsource.xSource,obj.ompMCsource.zSource,'rx')
+ end
+
+ end
+
+ end
+
+ methods (Static)
+
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ msg = [];
+ available = false;
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(DoseEngines.matRad_PhotonOmpMCEngine.possibleRadiationModes, machine.meta.radiationMode));
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ checkMeta = all(isfield(machine.meta,{'SAD','SCD'}));
+
+ if checkMeta
+ available = true;
+ msg = 'The ompMC machine is not representing the machine exactly and approximates it with a virtual Gaussian source and generic primary fluence & 6 MV energy spectrum!';
+ end
+ end
+
+ function compileOmpMCInterface(dest,omcFolder)
+ % Compiles the ompMC interface (integrated as submodule)
+ %
+ % call
+ % matRad_OmpConfig.compileOmpMCInterface()
+ % matRad_OmpConfig.compileOmpMCInterface(dest)
+ % matRad_OmpConfig.compileOmpMCInterface(dest,sourceFolder)
+ % if an object is instantiated, matRad_OmpConfig can be replaced by the
+ % object handle
+ %
+ % input:
+ % dest: (optional) destination for mex file. Default: location
+ % of this file
+ % sourceFolder: (optional) path to ompMC . Default assumes its checked
+ % out in the submodules folder of matRad
+ %
+ % References
+ %
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ matRad_cfg = MatRad_Config.instance();
+
+ env = matRad_getEnvironment();
+
+ %Our destination usually lies in the ompMC thirdPartyFolder
+ if nargin < 1
+ dest = [matRad_cfg.matRadRoot filesep 'thirdParty' filesep 'ompMC'];
+ end
+
+ %We can recompile form the submodules
+ if nargin < 2
+ omcFolder = [matRad_cfg.matRadRoot filesep 'submodules' filesep 'ompMC'];
+ end
+
+ sourceFolder = [omcFolder filesep 'src'];
+ interfaceFolder = [omcFolder filesep 'ucodes' filesep 'omc_matrad'];
+
+ mainFile = [interfaceFolder filesep 'omc_matrad.c'];
+
+ addFiles = {'ompmc.c','omc_utilities.c','omc_random.c'};
+ addFiles = cellfun(@(f) fullfile(sourceFolder,f),addFiles,'UniformOutput',false);
+
+ addFiles = strjoin(addFiles,' ');
+
+ if exist ('OCTAVE_VERSION','builtin')
+ ccName = evalc('mkoctfile -p CC');
+ else
+ myCCompiler = mex.getCompilerConfigurations('C','Selected');
+ ccName = myCCompiler.ShortName;
+ end
+
+ %These settings have only been tested for MSVC and g++. You may need to adapt for other compilers
+ if ~isempty(strfind(ccName,'MSVC')) %Not use contains(...) because of octave
+ flags{1,1} = 'COMPFLAGS';
+ flags{1,2} = '/openmp';
+ flags{2,1} = 'OPTIMFLAGS';
+ flags{2,2} = '/O2';
+ else
+ flags{1,1} = 'CFLAGS';
+ flags{1,2} = '-std=gnu99 -fopenmp -O3';
+ flags{2,1} = 'LDFLAGS';
+ flags{2,2} = '-fopenmp';
+
+ end
+
+ includestring = ['-I' sourceFolder];
+
+ flagstring = '';
+
+ %For Octave, the flags will be set in the environment, while they
+ %will be parsed as string arguments in MATLAB
+ for flag = 1:size(flags,1)
+ if strcmp(env,'OCTAVE')
+ preFlagContent = evalc(['mkoctfile -p ' flags{flag,1}]);
+ if ~isempty(preFlagContent)
+ preFlagContent = preFlagContent(1:end-1); %Strip newline
+ end
+ newContent = [preFlagContent ' ' flags{flag,2}];
+ setenv(flags{flag,1},newContent);
+ matRad_cfg.dispDebug('Set compiler flag %s to %s\n',flags{flag,1},newContent);
+ else
+ flagstring = [flagstring flags{flag,1} '="$' flags{flag,1} ' ' flags{flag,2} '" '];
+ end
+ end
+
+ mexCall = ['mex -largeArrayDims ' flagstring ' ' includestring ' ' mainFile ' ' addFiles];
+ matRad_cfg.dispDebug('Compiler call: %s\n',mexCall);
+
+ currDir = pwd;
+ cd(dest);
+ eval(mexCall);
+ cd(currDir);
+ end
+ end
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_PhotonPencilBeamSVDEngine.m b/matRad/doseCalc/+DoseEngines/matRad_PhotonPencilBeamSVDEngine.m
new file mode 100644
index 000000000..5dcea9066
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_PhotonPencilBeamSVDEngine.m
@@ -0,0 +1,607 @@
+classdef matRad_PhotonPencilBeamSVDEngine < DoseEngines.matRad_PencilBeamEngineAbstract
+ % matRad_PhotonPencilBeamSVDEngine: Pencil-beam dose calculation with
+ % singular value decomposed kernels
+ %
+ %
+ % References
+ % [1] http://www.ncbi.nlm.nih.gov/pubmed/8497215
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2022 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ possibleRadiationModes = {'photons'}; %constant which represent available radiation modes
+ name = 'SVD Pencil Beam';
+ shortName = 'SVDPB';
+
+ %supportedQuantities = {'physicalDose'};
+
+ % Define function_Di for beamlet calculation. Constant for use in
+ % static computations
+ %func_Di = @(x,m,beta) beta/(beta-m) * (exp(-m*x) - exp(-beta*x));
+ %func_DiVec = @(x,m,betas) betas./(betas-m) .* (exp(-m*x) - exp(-betas.*x));
+ end
+
+ properties (SetAccess = public, GetAccess = public)
+ useCustomPrimaryPhotonFluence; %boolean to control usage of the primary fluence during dose (influence matrix) computation
+ kernelCutOff; %cut off in [mm] of kernel values
+ randomSeed = 0; %for bixel sampling
+ intConvResolution = 0.5; %resolution for kernel convolution [mm]
+
+ enableDijSampling = true;
+ dijSampling; %struct with lateral dij sampling parameters
+ ignoreInvalidValues = false; %ignore negative, infinite or NaN values in bixel calculation
+ end
+
+ %Calculation variables
+ properties (SetAccess = protected,GetAccess = public)
+ isFieldBasedDoseCalc; %Will be set
+ penumbraFWHM; %will be obtained from machine
+ fieldWidth; %Will be obtained during calculation
+
+ %Kernel Grid for convolution
+ kernelConvSize; %size of the convolution kernel
+ kernelX; %meshgrid in X
+ kernelZ; %meshgrid in Z
+ kernelMxs; %cell array of kernel matrices
+
+ gaussFilter; %two-dimensional gaussian filter to model penumbra
+ gaussConvSize; %size of the gaussian convolution kernel
+
+ convMx_X; %convolution meshgrid in X
+ convMx_Z; %convolution meshgrid in Z
+
+ F_X; %fluence meshgrid in X
+ F_Z; %fluence meshgrid in Z
+
+ Fpre; %precomputed fluence if uniform fluence used for calculation
+ interpKernelCache; %Kernel interpolators (cached if precomputation per beam possible)
+
+ collimation; %collimation structure from dicom import
+ end
+
+
+ methods
+
+ function this = matRad_PhotonPencilBeamSVDEngine(pln)
+ % Constructor
+ %
+ % call
+ % engine = DoseEngines.matRad_PhotonPencilBeamSVDEngine(pln)
+ %
+ % input
+ % ct: matRad ct struct
+ % stf: matRad steering information struct
+ % pln: matRad plan meta information struct
+ % cst: matRad cst struct
+
+ if nargin < 1
+ pln = [];
+ end
+
+ % create this from superclass
+ this = this@DoseEngines.matRad_PencilBeamEngineAbstract(pln);
+
+ %TODO: engines should not rely on reading properties from "propStf", we need to find another way to handle those two fields in the future.
+ if nargin > 0 && isfield(pln,'propStf')
+ if isfield(pln.propStf,'bixelWidth')
+ % 0 if field calc is bixel based, 1 if dose calc is field based
+ % num2str is only used to prevent failure of strcmp when bixelWidth
+ % contains a number and not a string
+ this.isFieldBasedDoseCalc = strcmp(num2str(pln.propStf.bixelWidth),'field');
+ end
+
+ %Potentially stored collimation information
+ if isfield(pln.propStf,'collimation')
+ this.collimation = pln.propStf.collimation;
+ end
+ end
+ end
+
+ function setDefaults(this)
+ setDefaults@DoseEngines.matRad_PencilBeamEngineAbstract(this);
+
+ %Assign defaults from Config
+ matRad_cfg = MatRad_Config.instance();
+ this.useCustomPrimaryPhotonFluence = matRad_cfg.defaults.propDoseCalc.useCustomPrimaryPhotonFluence;
+ this.kernelCutOff = matRad_cfg.defaults.propDoseCalc.kernelCutOff;
+
+ %dij sampling defaults
+ this.dijSampling.relDoseThreshold = 0.01;
+ this.dijSampling.latCutOff = 20;
+ this.dijSampling.type = 'radius';
+ this.dijSampling.deltaRadDepth = 5;
+ end
+ end
+
+ methods (Access = protected)
+
+ function dij = initDoseCalc(this,ct,cst,stf)
+ %% Assign parameters
+ matRad_cfg = MatRad_Config.instance();
+
+ % 0 if field calc is bixel based, 1 if dose calc is field based
+ % num2str is only used to prevent failure of strcmp when bixelWidth
+ % contains a number and not a string
+ this.isFieldBasedDoseCalc = any(arrayfun(@(s) strcmp(num2str(s.bixelWidth),'field'),stf));
+
+ %% Call Superclass init
+ dij = initDoseCalc@DoseEngines.matRad_PencilBeamEngineAbstract(this,ct,cst,stf);
+
+ %% Validate some properties
+ % gaussian filter to model penumbra from (measured) machine output / see
+ % diploma thesis siggel 4.1.2 -> https://github.com/e0404/matRad/wiki/Dose-influence-matrix-calculation
+ if isfield(this.machine.data,'penumbraFWHMatIso')
+ this.penumbraFWHM = this.machine.data.penumbraFWHMatIso;
+ else
+ this.penumbraFWHM = 5;
+ matRad_cfg.dispWarning('photon machine file does not contain measured penumbra width in machine.data.penumbraFWHMatIso. Assuming %f mm.',this.penumbraFWHM);
+ end
+
+ %Correct kernel cut off to base data limits if needed
+ if this.kernelCutOff > this.machine.data.kernelPos(end)
+ matRad_cfg.dispWarning('Kernel Cut-Off ''%f mm'' larger than machine data range of ''%f mm''. Using ''%f mm''!',this.kernelCutOff,this.machine.data.kernelPos(end),this.machine.data.kernelPos(end));
+ this.kernelCutOff = this.machine.data.kernelPos(end);
+ end
+
+ if this.kernelCutOff < this.geometricLateralCutOff
+ matRad_cfg.dispWarning('Kernel Cut-Off ''%f mm'' cannot be smaller than geometric lateral cutoff ''%f mm''. Using ''%f mm''!',this.kernelCutOff,this.geometricLateralCutOff,this.geometricLateralCutOff);
+ this.kernelCutOff = this.geometricLateralCutOff;
+ end
+
+ %% kernel convolution
+ % set up convolution grid
+ if this.isFieldBasedDoseCalc
+ % get data from DICOM import
+ this.intConvResolution = this.collimation.convResolution; %overwrite default value from dicom
+ this.fieldWidth = this.collimation.fieldWidth;
+ else
+ if numel(unique([stf.bixelWidth])) > 1
+ matRad_cfg.dispError('Different bixelWidths pear beam are not supported!');
+ end
+
+ this.fieldWidth = unique([stf.bixelWidth]);
+ end
+
+ % calculate field size and distances
+ fieldLimit = ceil(this.fieldWidth/(2*this.intConvResolution));
+ [this.F_X,this.F_Z] = meshgrid(-fieldLimit*this.intConvResolution: ...
+ this.intConvResolution: ...
+ (fieldLimit-1)*this.intConvResolution);
+
+
+
+ sigmaGauss = this.penumbraFWHM / sqrt(8*log(2)); % [mm]
+ % use 5 times sigma as the limits for the gaussian convolution
+ gaussLimit = ceil(5*sigmaGauss/this.intConvResolution);
+ [gaussFilterX,gaussFilterZ] = meshgrid(-gaussLimit*this.intConvResolution: ...
+ this.intConvResolution: ...
+ (gaussLimit-1)*this.intConvResolution);
+ this.gaussFilter = 1/(2*pi*sigmaGauss^2/this.intConvResolution^2) * exp(-(gaussFilterX.^2+gaussFilterZ.^2)/(2*sigmaGauss^2) );
+ this.gaussConvSize = 2*(fieldLimit + gaussLimit);
+
+ % get kernel size and distances
+
+ kernelLimit = ceil(this.kernelCutOff/this.intConvResolution);
+ [this.kernelX, this.kernelZ] = meshgrid(-kernelLimit*this.intConvResolution: ...
+ this.intConvResolution: ...
+ (kernelLimit-1)*this.intConvResolution);
+
+ % precalculate convolved kernel size and distances
+ kernelConvLimit = fieldLimit + gaussLimit + kernelLimit;
+ [this.convMx_X, this.convMx_Z] = meshgrid(-kernelConvLimit*this.intConvResolution: ...
+ this.intConvResolution: ...
+ (kernelConvLimit-1)*this.intConvResolution);
+ % calculate also the total size and distance as we need this during convolution extensively
+ this.kernelConvSize = 2*kernelConvLimit;
+
+ % define an effective lateral cutoff where dose will be calculated. note
+ % that storage within the influence matrix may be subject to sampling
+ this.effectiveLateralCutOff = this.geometricLateralCutOff + this.fieldWidth/sqrt(2);
+
+
+ % Check if we can precompute fluence and precompute kernel
+ % convolution if we use a uniform fluence
+ if ~this.isFieldBasedDoseCalc
+ % Create fluence matrix
+ this.Fpre = ones(floor(this.fieldWidth/this.intConvResolution));
+
+ if ~this.useCustomPrimaryPhotonFluence
+ % gaussian convolution of field to model penumbra
+ this.Fpre = real(ifft2(fft2(this.Fpre,this.gaussConvSize,this.gaussConvSize).*fft2(this.gaussFilter,this.gaussConvSize,this.gaussConvSize)));
+ end
+ end
+
+ %% Initialize randomization
+ [env, ~] = matRad_getEnvironment();
+
+ switch env
+ case 'MATLAB'
+ rng(this.randomSeed); %Initializes Mersenne Twister with seed 0
+ case 'OCTAVE'
+ rand('state',this.randomSeed); %Initializes Mersenne Twister with state 0 (does not give similar random numbers as in Matlab)
+ otherwise
+ rand('seed',this.randomSeed); %Fallback
+ matRad_cfg.dispWarning('Environment %s not recognized!',env);
+ end
+
+ end
+
+ function currBeam = initBeam(this,currBeam,ct,cst,stf,i)
+ % Method for initializing the beams for analytical pencil beam
+ % dose calculation
+ %
+ % call
+ % this.initBeam(ct,stf,dij,i)
+ %
+ % input
+ % ct: matRad ct struct
+ % stf: matRad steering information struct
+ % dij: matRad dij struct
+ % i: index of beam
+ %
+ % output
+ % dij: updated dij struct
+
+ currBeam = initBeam@DoseEngines.matRad_PencilBeamEngineAbstract(this,currBeam,ct,cst,stf,i);
+
+ matRad_cfg = MatRad_Config.instance();
+
+ % get index of central ray or closest to the central ray
+ [~,center] = min(sum(reshape([currBeam.ray.rayPos_bev],3,[]).^2));
+
+ % get correct kernel for given SSD at central ray (nearest neighbor approximation)
+ [~,currSSDix] = min(abs([this.machine.data.kernel.SSD]-currBeam.ray(center).SSD));
+ % Display console message.
+ matRad_cfg.dispInfo('\tSSD = %g mm ...\n',this.machine.data.kernel(currSSDix).SSD);
+
+ %Hardcoded for now
+ useKernels = {'kernel1','kernel2','kernel3'};
+
+ kernelPos = this.machine.data.kernelPos;
+
+ for k = 1:length(useKernels)
+ kernel = this.machine.data.kernel(currSSDix).(useKernels{k});
+ this.kernelMxs{k} = interp1(kernelPos,kernel,sqrt(this.kernelX.^2+this.kernelZ.^2),'linear',0);
+ end
+
+ % convolution here if no custom primary fluence and no field based dose calc
+ if ~isempty(this.Fpre) && ~this.useCustomPrimaryPhotonFluence && ~this.isFieldBasedDoseCalc
+
+ % Display console message.
+ matRad_cfg.dispInfo('\tUniform primary photon fluence -> pre-compute kernel convolution...\n');
+
+ % Get kernel interpolators
+ this.interpKernelCache = this.getKernelInterpolators(this.Fpre);
+ end
+ end
+
+ function [bixel] = computeBixel(this,currRay,k)
+ % matRad photon dose calculation for an individual bixel
+ %
+ % call
+ % bixel = this.computeBixel(currRay,k)
+
+ bixel = struct();
+
+ if isfield(this.tmpMatrixContainers,'physicalDose')
+ bixel.physicalDose = this.calcSingleBixel(currRay.SAD,...
+ this.machine.data.m,...
+ this.machine.data.betas,...
+ currRay.interpKernels,...
+ currRay.radDepths,...
+ currRay.geoDepths,...
+ currRay.isoLatDists(:,1),...
+ currRay.isoLatDists(:,2),...
+ this.ignoreInvalidValues);
+
+ % sample dose only for bixel based dose calculation
+ if this.enableDijSampling && ~this.isFieldBasedDoseCalc
+ [bixel.ix,bixel.physicalDose] = this.sampleDij(currRay.ix,bixel.physicalDose,currRay.radDepths,currRay.radialDist_sq,currRay.bixelWidth);
+ else
+ bixel.ix = currRay.ix;
+ end
+ else
+ bixel.ix = [];
+ end
+ end
+
+ function interpKernels = getKernelInterpolators(this,Fx)
+
+ matRad_cfg = MatRad_Config.instance();
+
+ nKernels = length(this.kernelMxs);
+ interpKernels = cell(1,nKernels);
+
+ for ik = 1:nKernels
+ % 2D convolution of Fluence and Kernels in fourier domain
+ convMx = real( ifft2(fft2(Fx,this.kernelConvSize,this.kernelConvSize).* fft2(this.kernelMxs{ik},this.kernelConvSize,this.kernelConvSize)));
+
+ % Creates an interpolant for kernes from vectors position X and Z
+ if matRad_cfg.isMatlab
+ interpKernels{ik} = griddedInterpolant(this.convMx_X',this.convMx_Z',convMx','linear','none');
+ elseif matRad_cfg.isOctave
+ %For some reason the use of interpn here is much faster
+ %than using interp2 in Octave
+ interpKernels{ik} = @(x,y) interpn(this.convMx_X(1,:),this.convMx_Z(:,1),convMx',x,y,'linear',NaN);
+ end
+ end
+ end
+
+ function [ixNew,bixelDoseNew] = sampleDij(this,ix,bixelDose,radDepthV,rad_distancesSq,bixelWidth)
+ % matRad dij sampling function
+ % This function samples.
+ %
+ % call
+ % [ixNew,bixelDoseNew] =
+ % this.sampleDij(ix,bixelDose,radDepthV,rad_distancesSq,sType,Param)
+ %
+ % input
+ % ix: indices of voxels where we want to compute dose influence data
+ % bixelDose: dose at specified locations as linear vector
+ % radDepthV: radiological depth vector
+ % rad_distancesSq: squared radial distance to the central ray
+ % bixelWidth: bixelWidth as set in pln (optional)
+ %
+ % output
+ % ixNew: reduced indices of voxels where we want to compute dose influence data
+ % bixelDoseNew reduced dose at specified locations as linear vector
+ %
+ % References
+ % [1] http://dx.doi.org/10.1118/1.1469633
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2016 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ relDoseThreshold = this.dijSampling.relDoseThreshold;
+ LatCutOff = this.dijSampling.latCutOff;
+ Type = this.dijSampling.type;
+ deltaRadDepth = this.dijSampling.deltaRadDepth;
+
+ % if the input index vector is of type logical convert it to linear indices
+ if islogical(ix)
+ ix = find(ix);
+ end
+
+ %Increase sample cut-off by bixel width if given
+ if nargin == 6 && ~isempty(bixelWidth)
+ LatCutOff = LatCutOff + bixelWidth/sqrt(2); %use half of the bixel width diagonal as max. field size radius for sampling
+ end
+
+ %% remember dose values inside the inner core
+ switch Type
+ case 'radius'
+ ixCore = rad_distancesSq < LatCutOff^2; % get voxels indices having a smaller radial distance than r0
+ case 'dose'
+ ixCore = bixelDose > relDoseThreshold * max(bixelDose); % get voxels indices having a greater dose than the thresholdDose
+ otherwise
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Dij Sampling mode ''%s'' not known!',Type);
+ end
+
+ bixelDoseCore = bixelDose(ixCore); % save dose values that are not affected by sampling
+
+ if all(ixCore)
+ %% all bixels are in the core
+ %exit function with core dose only
+ ixNew = ix;
+ bixelDoseNew = bixelDoseCore;
+ else
+ logIxTail = ~ixCore; % get voxels indices beyond r0
+ linIxTail = find(logIxTail); % convert logical index to linear index
+ numTail = numel(linIxTail);
+ bixelDoseTail = bixelDose(linIxTail); % dose values that are going to be reduced by sampling
+ ixTail = ix(linIxTail); % indices that are going to be reduced by sampling
+
+ %% sample for each radiological depth the lateral halo dose
+ radDepthTail = (radDepthV(linIxTail)); % get radiological depth in the tail
+
+ % cluster radiological dephts to reduce computations
+ B_r = int32(ceil(radDepthTail)); % cluster radiological depths;
+ maxRadDepth = double(max(B_r));
+ C = int32(linspace(0,maxRadDepth,round(maxRadDepth)/deltaRadDepth)); % coarse clustering of rad depths
+
+ ixNew = zeros(numTail,1); % inizialize new index vector
+ bixelDoseNew = zeros(numTail,1); % inizialize new dose vector
+ linIx = int32(1:1:numTail)';
+ IxCnt = 1;
+
+ %% loop over clustered radiological depths
+ for i = 1:numel(C)-1
+ ixTmp = linIx(B_r >= C(i) & B_r < C(i+1)); % extracting sub indices
+ if isempty(ixTmp)
+ continue
+ end
+ subDose = bixelDoseTail(ixTmp); % get tail dose in current cluster
+ subIx = ixTail(ixTmp); % get indices in current cluster
+ thresholdDose = max(subDose);
+ r = rand(numel(subDose),1); % get random samples
+ ixSamp = r<=(subDose/thresholdDose);
+ NumSamples = sum(ixSamp);
+
+ ixNew(IxCnt:IxCnt+NumSamples-1,1) = subIx(ixSamp); % save new indices
+ bixelDoseNew(IxCnt:IxCnt+NumSamples-1,1) = thresholdDose; % set the dose
+ IxCnt = IxCnt + NumSamples;
+ end
+
+
+ % cut new vectors and add inner core values
+ ixNew = [ix(ixCore); ixNew(1:IxCnt-1)];
+ bixelDoseNew = [bixelDoseCore; bixelDoseNew(1:IxCnt-1)];
+ end
+
+ end
+
+ function [ray] = initRay(this,currBeam,j)
+
+ ray = initRay@DoseEngines.matRad_PencilBeamEngineAbstract(this,currBeam,j);
+
+ % convolution here if custom primary fluence OR field based dose calc
+ if this.useCustomPrimaryPhotonFluence || this.isFieldBasedDoseCalc
+
+ % overwrite field opening if necessary
+ if this.isFieldBasedDoseCalc
+ F = ray.shape;
+ else
+ F = this.Fpre;
+ end
+
+ % prepare primary fluence array
+ primaryFluence = this.machine.data.primaryFluence;
+ r = sqrt( (this.F_X-ray.rayPos(1)).^2 + (this.F_Z-ray.rayPos(3)).^2 );
+ Psi = interp1(primaryFluence(:,1)',primaryFluence(:,2)',r,'linear',0);
+
+ % apply the primary fluence to the field
+ Fx = F .* Psi;
+
+ % convolve with the gaussian
+ Fx = real( ifft2(fft2(Fx,this.gaussConvSize,this.gaussConvSize).* fft2(this.gaussFilter,this.gaussConvSize,this.gaussConvSize)) );
+
+ % Get kernel interpolators
+ ray.interpKernels = this.getKernelInterpolators(Fx);
+
+ else
+ ray.interpKernels = this.interpKernelCache;
+ end
+
+
+ end
+ end
+
+ methods (Static)
+
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ msg = [];
+ available = false;
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(DoseEngines.matRad_PhotonPencilBeamSVDEngine.possibleRadiationModes, machine.meta.radiationMode));
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+
+ %Basic check for information (does not check data integrity & subfields etc.)
+ checkData = all(isfield(machine.data,{'betas','energy','m','primaryFluence','kernel','kernelPos'}));
+ checkMeta = all(isfield(machine.meta,{'SAD','SCD'}));
+
+ if checkData && checkMeta
+ available = true;
+ else
+ available = false;
+ return;
+ end
+
+ %Now check for optional fields that would be guessed otherwise
+ checkOptional = isfield(machine.data,'penumbraFWHMatIso');
+ if checkOptional
+ msg = 'No penumbra given, generic value will be used!';
+ end
+ end
+
+ function bixelDose = calcSingleBixel(SAD,m,betas,interpKernels,...
+ radDepths,geoDists,isoLatDistsX,isoLatDistsZ,ignoreInvalidValues)
+ % matRad photon dose calculation for an individual bixel
+ % This is defined as a static function so it can also be
+ % called individually for certain applications without having
+ % a fully defined dose engine
+ %
+ % call
+ % dose = this.calcPhotonDoseBixel(SAD,m,betas,Interp_kernel1,...
+ % Interp_kernel2,Interp_kernel3,radDepths,geoDists,...
+ % isoLatDistsX,isoLatDistsZ)
+ %
+ % input
+ % SAD: source to axis distance
+ % m: absorption in water (part of the dose calc base
+ % data)
+ % betas: beta parameters for the parameterization of the
+ % three depth dose components
+ % interpKernels: kernel interpolators for dose calculation
+ % radDepths: radiological depths
+ % geoDists: geometrical distance from virtual photon source
+ % isoLatDistsX: lateral distance in X direction in BEV from central
+ % ray at iso center plane
+ % isoLatDistsZ: lateral distance in Z direction in BEV from central
+ % ray at iso center plane
+ %
+ % output
+ % dose: photon dose at specified locations as linear vector
+ %
+ % References
+ % [1] http://www.ncbi.nlm.nih.gov/pubmed/8497215
+ %
+
+ if nargin < 9
+ ignoreInvalidValues = false;
+ end
+
+ % Compute depth dose components according to [1, eq. 17]
+ doseComponent = betas./(betas-m) .* (exp(-m*radDepths) - exp(-betas.*radDepths));
+
+ % Multiply with lateral 2D-convolved kernels using
+ % grid interpolation at lateral distances (summands in [1, eq.
+ % 19] w/o inv sq corr)
+ for ik = 1:length(interpKernels)
+ doseComponent(:,ik) = doseComponent(:,ik) .* interpKernels{ik}(isoLatDistsX,isoLatDistsZ);
+ end
+
+ % now add everything together (eq 19 w/o inv sq corr -> see below)
+ bixelDose = sum(doseComponent,2);
+
+ % inverse square correction
+ bixelDose = bixelDose .* ((SAD)./geoDists(:)).^2;
+
+ % check if we have valid dose values and adjust numerical instabilities
+ % from fft convolution
+ bixelDose(bixelDose < 0 & bixelDose > -1e-14) = 0;
+ if ~ignoreInvalidValues && any(isnan(bixelDose) | bixelDose<0)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError([ ...
+ 'Invalid numerical values in photon dose calculation.\n' ...
+ 'Check your kernel or set ignoreInvalidValues to true.']);
+ end
+ end
+
+ end
+
+end
+
diff --git a/matRad/doseCalc/+DoseEngines/matRad_TG43BrachyEngine.m b/matRad/doseCalc/+DoseEngines/matRad_TG43BrachyEngine.m
new file mode 100644
index 000000000..11c8e0cbd
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_TG43BrachyEngine.m
@@ -0,0 +1,569 @@
+classdef matRad_TG43BrachyEngine < DoseEngines.matRad_DoseEngineBase
+% Engine for dose calculation based on the revised AAPM protocol for
+% brachytherapy Dose Calculations
+%
+%
+% for more informations see superclass
+% DoseEngines.matRad_DoseEngineBase
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ possibleRadiationModes = {'brachy'};
+ name = 'TG43';
+ shortName = 'TG43';
+ end
+
+ properties
+ DistanceCutoff;
+ TG43approximation;
+ end
+
+ methods
+
+ function this = matRad_TG43BrachyEngine(pln)
+ if nargin < 1
+ pln = [];
+ end
+
+ % call superclass constructor
+ this = this@DoseEngines.matRad_DoseEngineBase(pln);
+ end
+
+ function setDefaults(this)
+ setDefaults@DoseEngines.matRad_DoseEngineBase(this);
+
+ this.DistanceCutoff = 130;
+
+ this.doseGrid.resolution.x = 5;
+ this.doseGrid.resolution.y = 5;
+ this.doseGrid.resolution.z = 5;
+ end
+ end
+
+ methods (Access = protected)
+
+ function dij = initDoseCalc(this,ct,cst,stf)
+
+ for i = 1:numel(stf)
+ stf(i).numOfRays = stf(i).numOfNeedles;
+ end
+
+ dij = initDoseCalc@DoseEngines.matRad_DoseEngineBase(this,ct,cst,stf);
+
+ % dij meta information for brachy plan
+ dij.numOfScenarios = 1;
+ dij.numOfBeams = 1;
+ dij.beamNum = 1;
+ dij.numOfNeedles = stf.numOfNeedles;
+ dij.numOfSeedsPerNeedle = stf.numOfSeedsPerNeedle;
+ dij.totalNumOfSeeds = dij.numOfNeedles*dij.numOfSeedsPerNeedle;
+ dij.totalNumOfBixels = dij.totalNumOfSeeds;
+ end
+
+ function dij = calcDose(this,ct,cst,stf)
+ % Initialize dij
+ dij = this.initDoseCalc(ct, cst, stf);
+
+ matRad_cfg = MatRad_Config.instance();
+
+ seedPoints.x = single(stf.seedPoints.x);
+ seedPoints.y = single(stf.seedPoints.y);
+ seedPoints.z = single(stf.seedPoints.z);
+
+ [XGrid,YGrid,ZGrid] = meshgrid(dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z);
+ dosePoints.x = single(reshape(XGrid,1,[]));
+ dosePoints.y = single(reshape(YGrid,1,[]));
+ dosePoints.z = single(reshape(ZGrid,1,[]));
+
+ matRad_cfg.dispInfo('\t computing distance transform... ');
+ startTime = tic;
+
+ DistanceMatrix = matRad_getDistanceMatrix(seedPoints,dosePoints);
+
+ % ignore all distances > Cutoff for the following calculations to save time
+ Ignore = DistanceMatrix.dist > this.DistanceCutoff;
+ calcDistanceMatrix.x = DistanceMatrix.x(~Ignore);
+ calcDistanceMatrix.y = DistanceMatrix.y(~Ignore);
+ calcDistanceMatrix.z = DistanceMatrix.z(~Ignore);
+ calcDistanceMatrix.dist = DistanceMatrix.dist(~Ignore);
+
+
+ % now all fields of calcDistanceMatrix are n x 1 arrays!
+
+ % update
+ matRad_cfg.dispInfo('done in %f s!\n',toc(startTime));
+ this.progressUpdate(0.125,1);
+
+ if ~isfield(this,'propDoseCalc') || ~isfield(this,'TG43approximation')
+ this.TG43approximation = '2D';
+ end
+
+ if strcmp(this.TG43approximation,'2D')
+ matRad_cfg.dispInfo('\t computing angle for TG43-2D... ');
+ tmpTimer = tic;
+ [ThetaMatrix,~] = this.getThetaMatrix(stf.templateNormal,calcDistanceMatrix);
+ matRad_cfg.dispInfo('done in %f s!\n',toc(tmpTimer));
+ end
+
+ % update
+ this.progressUpdate(0.25,1);
+
+ matRad_cfg.dispInfo('\t computing dose-rate for TG43-%s... ',this.TG43approximation);
+ tmpTimer = tic;
+ DoseRate = zeros(length(dosePoints.x),length(seedPoints.x));
+ switch this.TG43approximation
+ case '1D'
+ DoseRate(~Ignore) = ...
+ this.getDoseRate1D_poly(this.machine,calcDistanceMatrix.dist);
+ case '2D'
+ DoseRate(~Ignore) = ...
+ this.getDoseRate2D_poly(this.machine,calcDistanceMatrix.dist,ThetaMatrix);
+ otherwise
+ matRad_cfg.dispError('TG43 Approximation ''%s'' not known!',this.TG43approximation);
+ end
+ matRad_cfg.dispInfo('done in %f s!\n',toc(tmpTimer));
+
+ dij.physicalDose = {DoseRate};
+
+ % update waitbar, delete waitbar
+ matRad_cfg.dispInfo('Brachytherapy dose calculation finished in %f s!\n',toc(startTime));
+ this.progressUpdate(1,1);
+
+ %Finalize dose calculation
+ dij = this.finalizeDose(dij);
+ end
+ end
+
+ methods ( Access = public )
+
+ function dij = setCalcDose(this, ct, cst, stf)
+ dij = this.calcDose(ct, cst, stf);
+ end
+
+ function DoseRate = getDoseRate1D_poly(this,machine,r_mm)
+ % Calculation of radial dose Rate, interpolating using polynomes
+ % 1D dose rate formalism from Rivard et al. (2004): AAPM TG-43 update,
+ % page 639, Eq. 11:
+ %
+ % call
+ % DoseRate = matRad_TG43BrachyEngine.getDoseRate1D_poly(machine,r_mm)
+ %
+ % input
+ % machine: TG43 information about the used seeds
+ % r: radial distance array, given in mm!
+ %
+ % output
+ % DoseRate: size(r) array of dose Rate in cGy/h
+ %
+ % comment on dimensions / units
+ % TG43 consensus data cm, cGy, s
+ % matRad mm, Gy, s
+ % output dimensions depend on the dimensions of air kerma strength
+ % Sk, normallyi in cGy*cm^2/h)
+
+ % validate/ complete input arguments
+ if ~isfield(machine.data,'AnisotropyFactorRadialDistance')
+ matRad_cfg.dispError('machine is missing field "AnisotropyFactorRadialDistance"...you might be trying to apply the TG43 2D formalism for basedata measured for 1D formalism')
+ end
+ if ~isfield(machine.data,'AnisotropyFactorValue')
+ matRad_cfg.dispError('machine is missing field "AnisotropyFactorValue"')
+ end
+ if ~isfield(machine.data,'lambda')
+ matRad_cfg.dispError...
+ ('machine is missing field "lambda" (dose constant in water)')
+ end
+ if machine.data.lambda < 0
+ matRad_cfg.dispError('negative doseRate')
+ end
+ if min(r_mm(:)) < 0
+ matRad_cfg.dispError('r contatins negative distances')
+ end
+ if ~isfield(machine.data,'SourceStrengthImplanted')
+ this.machine.data.SourceStrengthImplanted = 1;
+ end
+
+ % arguments used during function
+ % r: radius (within this function all radii are given in cm)
+ r = 0.1*r_mm;
+
+ % Sk: Air-kerma strength in U...[1 U = 1 muGy*m^2/h) = 1 cGy*cm^2/h]
+ Sk = machine.data.SourceStrengthImplanted;
+
+ % lambda: Dose-rate constant in water (Lambda) in cGy/(h*U)
+ lambda = machine.data.lambda;
+
+ % L: length of line source in cm
+ L = machine.data.ActiveSourceLength;
+
+ % r0: reference radius in cm
+ r0 = 1;
+
+ % thet0: standard angle in degree
+ theta0 = 90;
+
+ % gLTab: Tabulated radial dose function \\ cell entry 1: radii; entry 2: values
+ % radii in cm, values in units of g(r0)
+ gLTab{1} = machine.data.RadialDoseDistance;
+ gLTab{2} = machine.data.RadialDoseValue;
+
+ % PhiAn: Tabulated anisotropy factor \\ cell entry 1: radii; entry 2: values
+ % radii in cm, values unitless
+ PhiAnTab{1} = machine.data.AnisotropyFactorRadialDistance;
+ PhiAnTab{2} = machine.data.AnisotropyFactorValue;
+
+ % 1D formalism
+ % according to Rivard et al.: AAPM TG-43 update Eq. (11)
+ gL = this.radialDoseFunction(r,gLTab);
+ GL = this.geometryFunction(r,theta0,L);
+ GL0 = this.geometryFunction(r0,theta0,L);
+ PhiAn = this.anisotropyFactor1D(r,PhiAnTab, L);
+
+ DoseRate = Sk * lambda * GL./GL0 .* gL .* PhiAn;
+
+ end
+
+ function DoseRate = getDoseRate2D_poly(this,machine,r_mm,theta)
+ % Calculation of radial dose Rate, interpolating using polynomes
+ % 2D dose rate formalism from Rivard et al. (2004): AAPM TG-43 update,
+ % page 637, eq. 1
+ %
+ % call
+ % DoseRate = matRad_TG43BrachyEngine.getDoseRate2D_poly(machine,r_mm)
+ %
+ % input
+ % machine: TG43 information about the used seeds
+ % r: radial distance array, given in mm!
+ % theta: polar angle in degree
+ %
+ % output
+ % DoseRate: size(r) array of dose Rate in cGy/h
+ %
+ % comment on dimensions / units
+ % TG43 consensus data cm, cGy, s
+ % matRad mm, Gy, s
+ % output dimensions depend on the dimensions of air kerma strength
+ % Sk, normallyi in cGy*cm^2/h)
+
+ matRad_cfg = MatRad_Config.instance();
+
+ % validate/ complete input arguments
+ if ~isfield(machine.data,'AnisotropyRadialDistances')
+ matRad_cfg.dispError('machine is missing field "AnisotropyRadialDistances"...you might be trying to apply the TG43 1D formalism for basedata measured for 2D formalism')
+ end
+ if ~isfield(machine.data,'AnisotropyPolarAngles')
+ matRad_cfg.dispError('machine is missing field "AnisotropyPolarAngles"')
+ end
+ if ~isfield(machine.data,'AnisotropyFunctionValue')
+ matRad_cfg.dispError('machine is missing field "AnisotropyFunctionValue"')
+ end
+ if ~isfield(machine.data,'lambda')
+ matRad_cfg.dispError('machine is missing field "lambda" (dose constant in water)')
+ end
+ if machine.data.lambda < 0
+ matRad_cfg.dispError('negative doseRate')
+ end
+ if ~isfield(machine.data,'AnisotropyRadialDistances')
+ matRad_cfg.dispError('machine is missing field "AnisotropyRadialDistances"')
+ end
+ if ~isfield(machine.data,'AnisotropyPolarAngles')
+ matRad_cfg.dispError('machine is missing field "AnisotropyPolarAngles"')
+ end
+ if ~isfield(machine.data,'AnisotropyFunctionValue')
+ matRad_cfg.dispError('machine is missing field "AnisotropyFunctionValue"')
+ end
+ if min(r_mm(:)) < 0
+ matRad_cfg.dispError('r contatins negative distances')
+ end
+ if ~isfield(machine.data,'ActiveSourceLength')
+ matRad_cfg.dispError('machine is missing field "ActiveSourceLength", defining the source length')
+ end
+ if ~isfield(machine.data,'SourceStrengthImplanted')
+ machine.data.SourceStrengthImplanted = 1;
+ end
+
+ % arguments used during function
+ % r: radius (within this function all radii are given in cm)
+ r = 0.1*r_mm;
+
+ % Sk: Air-kerma strength in U...[1 U = 1 muGy*m^2/h) = 1 cGy*cm^2/h]
+ Sk = machine.data.SourceStrengthImplanted;
+
+ % lambda: Dose-rate constant in water (Lambda) in cGy/(h*U)
+ lambda = machine.data.lambda;
+
+ % L: length of line source in cm
+ L = machine.data.ActiveSourceLength;
+
+ % r0: standard radius in cm
+ r0 = 1;
+
+ % thet0: standard angle in degree
+ theta0 = 90;
+
+ % gLTab: Tabulated radial dose function \\ cell entry 1: radii; entry 2: values
+ % radii in cm, values in units of g(r0)
+ gLTab{1} = machine.data.RadialDoseDistance;
+ gLTab{2} = machine.data.RadialDoseValue;
+
+ % FTab: Tabulated 2D anisotropy function
+ % \\ cell entry 1: radii; entry 2: angles; entry 3: values
+ % radii in cm, angles in degree, values unitless
+ FTab{1} = machine.data.AnisotropyRadialDistances;
+ FTab{2} =machine.data.AnisotropyPolarAngles;
+ FTab{3} = machine.data.AnisotropyFunctionValue;
+
+ % 2D formalism
+ % according to Rivard et al.: AAPM TG-43 update p. 637 eq. (1)
+ % call interpolate functions and calculate formalism
+ gL = this.radialDoseFunction(r,gLTab);
+ GL = this.geometryFunction(r,theta,L);
+ GL0 = this.geometryFunction(r0,theta0,L);
+ if isfield(machine.data,'AnisotropyPolynomial')
+ F = machine.data.AnisotropyPolynomial(r,theta);
+ else
+ F = DoseEngines.matRad_TG43BrachyEngine.anisotropyFunction2DInterp(r,theta,FTab); % uses the Interp2 function for estimation of Anisotropy function ( Gamma(1mm,1%) pass rate 99.5%)
+ % F = matRad_TG43BrachyEngine.anisotropyFunction2D(r,theta,FTab); % uses the 5th order polynomial for estimation of Anisotropy function
+ end
+ DoseRate = Sk * lambda * GL./GL0.*gL.*F;
+ end
+
+ function [ThetaMatrix,ThetaVector] = getThetaMatrix(this,templateNormal,DistanceMatrix)
+ % getThetaMatrix gets (seed x dosepoint) matrix of relative polar angles
+ %
+ % call
+ % [ThetaMatrix,ThetaVector] = matRad_TG43BrachyEngine.getThetaMatrix(templateNormal,...
+ % DistanceMatrix)
+ % normally called within matRad_TG43BrachyEngine.getBrachyDose
+ % !!getDistanceMatrix needs to be called first!!
+ %
+ % input
+ % DistanceMatrix: [dosePoint x seedPoint] struct with fields 'x','y',
+ % 'z' and total distance 'dist'
+ % templateNormal: normal vector of template (its assumed that this is
+ % the dir all seeds point to)
+ %
+ % output
+ % angle matrix: rows: index of dosepoint
+ % columns: index of deedpoint
+ % entry: polar angles betreen seedpoints and
+ % dosepoint in degrees
+ % angle vector: column vector of angle matrix entries
+ %
+ % comment:
+ % The shape of the Theta matrix will be consistent with the shape of
+ % input fields.
+
+ DistanceMatrix.dist(DistanceMatrix.dist == 0) = 1; %Avoid deviding by zero
+
+ ThetaMatrix = acosd((templateNormal(1)*DistanceMatrix.x + templateNormal(2)*DistanceMatrix.y + templateNormal(3)*DistanceMatrix.z)./DistanceMatrix.dist);
+ if nargout == 2
+ ThetaVector = reshape(ThetaMatrix,[],1);
+ end
+ end
+ end
+
+ methods (Static)
+ function PhiAn = anisotropyFactor1D(r,PhiAnTab, L)
+ % anisotropy function interpolates tabulated data
+ % using fifth order polynomial and approximates small and large distances
+ % according to Rivard et al.2007: Supplement to the 2004 update of the
+ % AAPM Task Group No. 43 Report Eq. (2).
+ % Normally called within matRad_TG43BrachyEngine.getDoseRate(...)
+ %
+ % call
+ % PhiAn = matRad_TG43BrachyEngine.anisotropyFactor1D(r,PhiAnTab, L)
+ %
+ % input
+ % r: array of radial distances in cm!
+ % PhiAnTab: tabulated consensus data of gL according to the following
+ % cell structure:
+ % PhiAnTab{1} = AnisotropyFactorRadialDistance
+ % PhiAnTab{2} = AnisotropyFactorValue
+ %
+ % output
+ % PhiAn: array of the same shape as r and thet containing the
+ % interpolated and extrapolated values
+ rmin = PhiAnTab{1}(1);
+ rmax = PhiAnTab{1}(end);
+ p = polyfit(PhiAnTab{1},PhiAnTab{2},5);
+ PhiAn = zeros(size(r));
+ PhiAn(r>=rmin & r<=rmax) = polyval(p,r(r>=rmin & r<=rmax));
+ PhiAn(r>rmax) = PhiAnTab{2}(end);
+ PhiAn(r rmax;
+ IndSmall = r < rmin;
+ F(IndLarge) = p.PolynomialExpression(rmax,thet(IndLarge));
+ F(IndSmall) = p.PolynomialExpression(rmin,thet(IndSmall));
+ end
+
+
+ function GL = geometryFunction(r,thet,L)
+ % calculates 2D geometry function
+ % according to Rivard et al.: AAPM TG-43, p 638 update Eq. (4)
+ % Normally called within matRad_TG43BrachyEngine.getDoseRate(...)
+ %
+ % call
+ % GL = matRad_TG43BrachyEngine.geometryFunction(r,thet,L)
+ %
+ % inputs
+ % r: array of radial distances in cm!
+ % thet: array of azimual angles in °
+ % Length: length of radiation source in cm
+ %
+ % outputs
+ % GL(r,theta): geometry function output
+
+ % calculate solution
+ if thet == 90
+ beta = 2*atan(L./2./r);
+ GL = beta./(L.*r);
+ else
+ GL = DoseEngines.matRad_TG43BrachyEngine.calcBeta(r,thet,L)./(L.*r.*sind(thet));
+ GL(thet==0) = 1./(r(thet==0).^2-L^2/4);
+ GL(thet==180) = 1./(r(thet==180).^2-L^2/4);
+ GL(GL<0) = 0;
+ end
+ end
+
+ function beta = calcBeta(r, theta,L)
+ % calculate beta (see Rivard et al.: AAPM TG-43, p 637, Fig 1)
+ % calculates beta from r[cm], theta [deg] and L[cm]
+ % array inputs are allowed for theta
+
+ r1 = sqrt(r.^2 + (L/2)^2 - r.*L.*cosd(180 - theta)); % cos theorem
+ r2 = sqrt(r.^2 + (L/2)^2 - r.*L.*cosd(theta)); % cos theorem
+
+ beta1 = asin(sind(180-theta).*L/2./r1); % sine theorem
+ beta2 = asin(sind(theta).*L/2./r2); % sine theorem
+
+ beta = beta1 + beta2;
+ end
+
+ function gL = radialDoseFunction(r,gLTab)
+ % interpolates tabulated data using
+ % fifth order polynomial and approximates small and large distances
+ % according to Rivard et al.: AAPM TG-43 update, p.669, Eq. (C1).
+ % Normally called within matRad_TG43BrachyEngine.TG43BrachyEngine.getDoseRate(...)
+ %
+ % call
+ % matRad_TG43BrachyEngine.TG43BrachyEngine.radialDoseFuncrion(r,gLTab)
+ %
+ % input
+ % r: array of radial distances in cm!
+ % gLTab: tabulated consensus data of gL according to the
+ % following cell structure:
+ % gLTab{1} = RadialDoseDistance
+ % gLTab{2} = RadialDoseValue
+ %
+ % output
+ % gL: array of the same shape as r containing the interpolated
+ % and extrapolated values
+ rmin = gLTab{1}(1);
+ rmax = gLTab{1}(end);
+ polyCoefficients = polyfit(gLTab{1},gLTab{2},5);
+ gL = zeros(size(r));
+ gL(r>=rmin & r<=rmax) = polyval(polyCoefficients,r(r>=rmin & r<=rmax));
+ gL(rrmax) = gLTab{2}(end) + ...
+ (gLTab{2}(end)-gLTab{2}(end-1)) / (gLTab{1}(end)-...
+ gLTab{1}(end-1)).*(r(r>rmax)-gLTab{1}(end));
+ end
+
+ function F = anisotropyFunction2DInterp(r,thet,FTab)
+ % anisotropy function interpolates tabulated
+ % data using interp2 ( interp technique TBD)
+ % Normally called within matRad_TG43BrachyEngine.TG43BrachyEngine.getDoseRate(...)
+ %
+ % call
+ % F = matRad_TG43BrachyEngine.TG43BrachyEngine.anisotropyFunction2D(r,thet,FTab)
+ %
+ % input
+ % r: array of radial distances in cm
+ % thet: array of azimuthal angles in ??
+ % FTab: tabulated consensus data of F according to the
+ % following cell structure:
+ % FTab{1} = AnisotropyRadialDistances
+ % FTab{2} = AnisotropyPolarAngles
+ % FTab{3} = AnisotropyFunctionValue
+ %
+ % output
+ % F: array of the same shape as r and thet containing the
+ % interpolated and extrapolated values
+ [DataRGrid,DataThetGrid] = meshgrid(FTab{1},FTab{2});
+ Data(:,1) = reshape(DataRGrid,[],1);
+ Data(:,2) = reshape(DataThetGrid,[],1);
+ Value = reshape(FTab{3},[],1);
+
+ F = interp2(DataRGrid,DataThetGrid,FTab{3}, r, thet, 'linear');
+ % extrapolate for large and small values of r by taking the
+ % interpolation of the maximal tabulated value at this angle
+ % theta should be tabulated from 0?? to 180??
+ rmin = FTab{1}(1);
+ rmax = FTab{1}(end);
+
+ IndLarge = r > rmax;
+ IndSmall = r < rmin;
+ rmaxGrid = rmax*ones(sum(IndLarge(:)),1);
+ rminGrid = rmin*ones(sum(IndSmall(:)),1);
+ F(IndLarge) = interp2(DataRGrid,DataThetGrid,FTab{3},rmaxGrid,double(thet(IndLarge)));
+ F(IndSmall) = interp2(DataRGrid,DataThetGrid,FTab{3},rminGrid,double(thet(IndSmall)));
+ end
+
+ end
+
+end
diff --git a/matRad/doseCalc/+DoseEngines/matRad_TopasMCEngine.m b/matRad/doseCalc/+DoseEngines/matRad_TopasMCEngine.m
new file mode 100644
index 000000000..70e616949
--- /dev/null
+++ b/matRad/doseCalc/+DoseEngines/matRad_TopasMCEngine.m
@@ -0,0 +1,2447 @@
+classdef matRad_TopasMCEngine < DoseEngines.matRad_MonteCarloEngineAbstract
+ % matRad_TopasMCEngine
+ % Implementation of the TOPAS interface for Monte Carlo dose
+ % calculation
+ %
+ % References
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2023 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ possibleRadiationModes = {'photons','protons','helium','carbon'};
+ name = 'TOPAS';
+ shortName = 'TOPAS';
+
+ defaultPhotonBeamProfile = 'uniform';
+ defaultParticleBeamProfile = 'biGaussian';
+ end
+
+ properties
+ calcLET = false;
+ calcBioDose = false;
+ prescribedDose = [];
+
+
+ topasExecCommand; %Defaults will be set during construction according to TOPAS installation instructions and used system
+
+ parallelRuns = false; %Starts runs in parallel
+
+ externalCalculation = 'off'; %Generates folder for external TOPAS calculation (e.g. on a server)
+
+ workingDir; %working directory for the simulation
+
+ engine = 'TOPAS'; %parameter for continuity
+
+ label = 'matRad_plan';
+
+ %Simulation parameters
+ numThreads = 0; %number of used threads, 0 = max number of threads (= num cores)
+ numOfRuns = 1; %Default number of runs / batches
+ modeHistories = 'num'; %'frac';
+ fracHistories = 1e-4; %Fraction of histories to compute
+
+ numParticlesPerHistory = 1e6;
+ verbosity = struct( 'timefeatures',0,...
+ 'cputime',true,...
+ 'run',0,...
+ 'event',0,...
+ 'tracking',0,...
+ 'material',0,...
+ 'maxinterruptedhistories',1000,...
+ 'maxDetailedErrorReports',0);
+
+ minRelWeight = .00001; %Threshold for discarding beamlets. 0 means all weights are being considered, can otherwise be assigned to min(w)
+
+ useOrigBaseData = false; % base data of the original matRad plan will be used?
+ beamProfile = 'biGaussian'; %'biGaussian' (emittance); 'simple'
+
+ useEnergySpectrum = false;
+
+ %Not yet implemented
+ %beamletMode = false; %In beamlet mode simulation will be performed for a dose influence matrix (i.e., each beamlet simulates numHistories beamlets)
+
+ pencilBeamScanning = true; %This should be always true except when using photons (enables deflection)
+
+ %Image
+ materialConverter = struct('mode','HUToWaterSchneider',... %'RSP','HUToWaterSchneider';
+ 'densityCorrection','Schneider_TOPAS',... %'rspHLUT','Schneider_TOPAS','Schneider_matRad'
+ 'addSection','none',... %'none','lung'
+ 'addTitanium',false,... %'false','true' (can only be used with advanced HUsections)
+ 'HUSection','advanced',... %'default','advanced'
+ 'HUToMaterial','default',... %'default',','advanced','MCsquare'
+ 'loadConverterFromFile',false); % set true if you want to use your own SchneiderConverter written in "TOPAS_SchneiderConverter"
+
+ arrayOrdering = 'F'; %'C';
+ rsp_basematerial = 'Water';
+
+ %Scoring
+ scorer = struct('volume',false,...
+ 'doseToMedium',true,...
+ 'doseToWater',false,...
+ 'surfaceTrackCount',false,...
+ 'calcDij',false,...
+ 'RBE',false,...
+ 'RBE_model',{{'default'}},... % default is MCN for protons and LEM1 for ions
+ 'defaultModelProtons',{{'MCN'}},...
+ 'defaultModelCarbon',{{'LEM'}},...
+ 'LET',false,...
+ 'sharedSubscorers',true,...
+ 'outputType','binary',... %'csv'; 'binary';%
+ ... % This variable is only used for physicalDose, since for now it adds unnecessary computation time
+ 'reportQuantity',{{'Sum','Standard_Deviation'}}); % 'reportQuantity',{{'Sum'}});
+ scorerRBEmodelOrderForEvaluation = {'MCN','WED','LEM','libamtrack'};
+ bioParameters = struct( 'PrescribedDose',2,...
+ 'AlphaX',0.1,...
+ 'BetaX',0.05,...
+ 'SimultaneousExposure','"True"');
+
+ %Physics
+ electronProductionCut = 0.5; %in mm
+ radiationMode;
+ modules_protons = {'g4em-standard_opt4','g4h-phy_QGSP_BIC_HP','g4decay','g4h-elastic_HP','g4stopping','g4ion-QMD','g4radioactivedecay'};
+ modules_GenericIon = {'g4em-standard_opt4','g4h-phy_QGSP_BIC_HP','g4decay','g4h-elastic_HP','g4stopping','g4ion-QMD','g4radioactivedecay'};
+ modules_photons = {'g4em-standard_opt4','g4h-phy_QGSP_BIC_HP','g4decay'};
+
+ %Geometry / World
+ worldMaterial = 'G4_AIR';
+
+ %filenames
+ converterFolder = 'materialConverter';
+ scorerFolder = 'scorer';
+ outfilenames = struct( 'patientParam','matRad_cube.txt',...
+ 'patientCube','matRad_cube.dat');
+
+ infilenames = struct( 'geometry','world/TOPAS_matRad_geometry.txt.in',...
+ ... % BeamSetup files
+ 'beam_virtualGaussian','beamSetup/TOPAS_beamSetup_virtualGaussian.txt.in',...
+ 'beam_phasespace','beamSetup/TOPAS_beamSetup_phasespace.txt.in',...
+ 'beam_uniform','beamSetup/TOPAS_beamSetup_uniform.txt.in',...
+ 'beam_mlc','beamSetup/TOPAS_beamSetup_mlc.txt.in',...
+ 'beam_biGaussian','beamSetup/TOPAS_beamSetup_biGaussian.txt.in',...
+ 'beam_generic','beamSetup/TOPAS_beamSetup_generic.txt.in',...
+ ... % Schneier Converter
+ ... % Defined Materials
+ 'matConv_Schneider_definedMaterials',struct('default','definedMaterials/default.txt.in',...
+ 'MCsquare','definedMaterials/MCsquare.txt.in',...
+ 'advanced','definedMaterials/advanced.txt.in'),...
+ ... % Density Correction
+ 'matConv_Schneider_densityCorr_Schneider_matRad','densityCorrection/Schneider_matRad.dat',...
+ 'matConv_Schneider_densityCorr_Schneider_TOPAS','densityCorrection/Schneider_TOPAS.dat',...
+ ... % load from file
+ 'matConv_Schneider_loadFromFile','TOPAS_SchneiderConverter.txt.in',...
+ ... % Scorer
+ 'Scorer_surfaceTrackCount','TOPAS_scorer_surfaceIC.txt.in',...
+ 'Scorer_doseToMedium','TOPAS_scorer_doseToMedium.txt.in',...
+ 'Scorer_LET','TOPAS_subscorer_LET.txt.in',...
+ 'Scorer_doseToWater','TOPAS_scorer_doseToWater.txt.in',...
+ 'Scorer_RBE_libamtrack','TOPAS_scorer_doseRBE_libamtrack.txt.in',...
+ 'Scorer_RBE_LEM1','TOPAS_scorer_doseRBE_LEM1.txt.in',...
+ 'Scorer_RBE_WED','TOPAS_scorer_doseRBE_Wedenberg.txt.in',...
+ 'Scorer_RBE_MCN','TOPAS_scorer_doseRBE_McNamara.txt.in', ...
+ ... %PhaseSpace Source
+ 'phaseSpaceSourcePhotons' ,'VarianClinaciX_6MV_20x20_aboveMLC_w2' );
+
+
+ end
+
+ properties (SetAccess = protected, GetAccess = private)
+ topasFolder;
+ MCparam; %Struct with parameters of last simulation to be saved to file
+
+ ctR; %resmpaled CT
+ end
+
+ methods
+ function obj = matRad_TopasMCEngine(pln)
+
+ if nargin < 1
+ pln = [];
+ end
+
+ % call superclass constructor
+ obj = obj@DoseEngines.matRad_MonteCarloEngineAbstract(pln);
+ end
+
+ function setDefaults(this)
+ this.setDefaults@DoseEngines.matRad_MonteCarloEngineAbstract();
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ % Default execution paths are set here
+ this.topasFolder = [matRad_cfg.matRadSrcRoot filesep 'doseCalc' filesep 'topas' filesep];
+ this.workingDir = [matRad_cfg.primaryUserFolder filesep 'TOPAS' filesep];
+
+ if ~exist(this.workingDir,'dir')
+ mkdir(this.workingDir);
+ matRad_cfg.dispInfo('Created TOPAS working directory in userfolder %s\n',this.workingDir);
+ end
+
+
+ %Let's set some default commands taken from topas installation
+ %instructions for mac & debain/ubuntu
+ if ispc %We assume topas is installed in wsl (since no windows version)
+ this.topasExecCommand = 'wsl export TOPAS_G4_DATA_DIR=~/G4Data; ~/topas/bin/topas';
+ elseif ismac
+ this.topasExecCommand = 'export TOPAS_G4_DATA_DIR=/Applications/G4Data; export QT_QPA_PLATFORM_PLUGIN_PATH=/Applications/topas/Frameworks; /Applications/topas/bin/topas';
+ elseif isunix
+ this.topasExecCommand = 'export TOPAS_G4_DATA_DIR=~/G4Data; ~/topas/bin/topas';
+ else
+ this.topasExecCommand = '';
+ end
+ end
+
+ function writeAllFiles(obj,ct,cst,stf,machine,w)
+ % constructor to write all TOPAS fils for local or external simulation
+ %
+ % call
+ % topasConfig.writeAllFiles(ct,pln,stf,machine,w)
+ %
+ % input
+ % ct: Path to folder where TOPAS files are in (as string)
+ % pln: matRad plan struct
+ % stf: matRad steering struct
+ % machine: machine to be used for calculation
+ % w: (optional) weights in case of calcDoseDirect
+
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ % prepare biological parameters
+ if ~isempty(obj.prescribedDose)
+ obj.bioParameters.PrescribedDose = obj.prescribedDose;
+ end
+ if isempty(obj.radiationMode)
+ obj.radiationMode = machine.meta.radiationMode;
+ end
+
+ % Set correct RBE scorer parameters
+ if obj.scorer.RBE
+ obj.scorer.doseToMedium = true;
+ if any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'default')), obj.scorer.RBE_model))
+ switch obj.radiationMode
+ case 'protons'
+ obj.scorer.RBE_model = obj.scorer.defaultModelProtons;
+ case {'carbon','helium'}
+ obj.scorer.RBE_model = obj.scorer.defaultModelCarbon;
+ otherwise
+ matRad_cfg.dispError(['No RBE model implemented for ',obj.radiationMode]);
+ end
+ end
+
+ % Get alpha beta parameters from bioParam struct
+ for i = 1:length(obj.bioParameters.AvailableAlphaXBetaX)
+ if ~isempty(strfind(lower(obj.bioParameters.AvailableAlphaXBetaX{i,2}),'default'))
+ break
+ end
+ end
+ obj.bioParameters.AlphaX = obj.bioParameters.AvailableAlphaXBetaX{5,1}(1);
+ obj.bioParameters.BetaX = obj.bioParameters.AvailableAlphaXBetaX{5,1}(2);
+
+ end
+ if obj.scorer.LET
+ obj.scorer.doseToMedium = true;
+ end
+
+ % create TOPAS working directory if not set
+ if ~exist(obj.workingDir,'dir')
+ mkdir(obj.workingDir);
+ matRad_cfg.dispInfo('Created TOPAS working directory %s\n',obj.workingDir);
+ end
+
+ % Write CT, patient parameters and Schneider converter
+ matRad_cfg.dispInfo('Writing parameter files to %s\n',obj.workingDir);
+ obj.writePatient(ct);
+
+ % Generate uniform weights in case of dij calculation (for later optimization)
+ if ~exist('w','var')
+ numBixels = sum([stf(:).totalNumOfBixels]);
+ w = ones(numBixels,1);
+ end
+
+ % Set MCparam structure with important simulation parameters that is needed for later readOut and
+ % postprocessing
+ obj.MCparam = struct();
+ obj.MCparam.tallies = {};
+ obj.MCparam.nbRuns = obj.numOfRuns;
+ obj.MCparam.simLabel = obj.label;
+ obj.MCparam.scoreReportQuantity = obj.scorer.reportQuantity;
+ obj.MCparam.workingDir = obj.workingDir;
+ obj.MCparam.weights = w;
+ obj.MCparam.ctGrid = ct.ctGrid;
+ if isfield(ct,'originalGrid')
+ obj.MCparam.originalGrid = ct.originalGrid;
+ end
+ obj.MCparam.cubeDim = ct.cubeDim;
+ obj.MCparam.ctResolution = ct.resolution;
+ obj.MCparam.numOfCtScen = ct.numOfCtScen;
+ % Save used RBE models
+ if obj.scorer.RBE
+ obj.MCparam.RBE_models = obj.scorer.RBE_model;
+ [obj.MCparam.ax,obj.MCparam.bx] = matRad_getPhotonLQMParameters(cst,prod(ct.cubeDim),obj.MCparam.numOfCtScen);
+ obj.MCparam.abx(obj.MCparam.bx>0) = obj.MCparam.ax(obj.MCparam.bx>0)./obj.MCparam.bx(obj.MCparam.bx>0);
+ end
+
+ % fill in bixels, rays and beams in case of dij calculation or external calculation
+ if obj.scorer.calcDij
+ counter = 1;
+ for f = 1:length(stf)
+ for r = 1:stf(f).numOfRays
+ for b = 1:stf(f).numOfBixelsPerRay(r)
+ obj.MCparam.bixelNum(counter) = b;
+ obj.MCparam.rayNum(counter) = r;
+ obj.MCparam.beamNum(counter) = f;
+ counter = counter + 1;
+ end
+ end
+ end
+ else
+ % In case of calcDoseDirect, you only need beamNum
+ obj.MCparam.bixelNum = 1;
+ obj.MCparam.rayNum = 1;
+ obj.MCparam.beamNum = 1:length(stf);
+ end
+ obj.MCparam.numOfRaysPerBeam = [stf(:).numOfRays];
+
+ % Generate baseData using the MCemittanceBaseData constructor
+ % Write TOPAS beam properties
+ if ~strcmp(machine.meta.radiationMode,'photons')
+ topasBaseData = matRad_MCemittanceBaseData(machine,stf);
+ else
+ topasBaseData = [];
+ end
+ obj.writeStfFields(ct,stf,w,topasBaseData);
+
+ % Save simulation parameters to folder
+ obj.writeMCparam();
+
+ % Console message
+ matRad_cfg.dispInfo('Successfully written TOPAS setup files!\n')
+ end
+
+ function dij = readFiles(obj,folder)
+ % function to read out TOPAS data
+ %
+ % call
+ % topasCube = topasConfig.readFiles(folder,dij)
+ % topasCube = obj.readFiles(folder,dij)
+ %
+ % input
+ % folder: Path to folder where TOPAS files are in (as string)
+ % dij: dij struct (this part needs update)
+ %
+ % output
+ % topasCube: struct with all read out subfields
+
+
+ % Load in saved MC parameters
+ if isfile([folder filesep 'MCparam.mat'])
+ obj.MCparam = load([folder filesep 'MCparam.mat'],'MCparam');
+ obj.MCparam = obj.MCparam.MCparam;
+ end
+
+ % Read out all TOPAS fields
+ topasCubes = obj.readTopasCubes(folder);
+
+ % Set 0 for empty or NaN fields
+ topasCubes = obj.markFieldsAsEmpty(topasCubes);
+
+ %% Fill Dij analogously to matRad
+ % Prepare empty Dij with empty sparse matrices for fields in topasCubes
+ dij = obj.prepareDij(topasCubes);
+
+ % Fill empty Dij with fields from topasCubes
+ dij = obj.fillDij(topasCubes,dij);
+
+ end
+
+ function resultGUI = getResultGUI(obj,dij)
+ if obj.scorer.calcDij
+ resultGUI = matRad_calcCubes(ones(dij.totalNumOfBixels,1),dij,1);
+ else
+ resultGUI = matRad_calcCubes(ones(dij.numOfBeams,1),dij,1);
+ end
+
+ % Export RBE model if filled
+ if isfield(resultGUI,'RBE_model') && ~isempty(resultGUI.RBE_model)
+ resultGUI.RBE_model = dij.RBE_model;
+ end
+
+ % Export histories to resultGUI
+ if isfield(dij,'nbHistoriesTotal')
+ resultGUI.nbHistoriesTotal = dij.nbHistoriesTotal;
+ resultGUI.nbParticlesTotal = dij.nbParticlesTotal;
+ end
+
+ % stuff for 4D
+ % if this.multScen.totNumScen ~= 1
+ % resultGUI.accPhysicalDose = zeros(size(resultGUI.phaseDose{1}));
+ % for i = 1:this.multScen.totNumScen
+ % resultGUI.accPhysicalDose = resultGUI.accPhysicalDose + resultGUI.phaseDose{i};
+ % end
+ % end
+ end
+
+ function dij = readExternal(obj,folder)
+ % function to read out complete TOPAS simulation from single folder
+ %
+ % call
+ % topasCube = topasConfig.readExternal(folder)
+ % topasCube = obj.readExternal(folder)
+ %
+ % input
+ % folder: Path to folder where TOPAS files are in (as string)
+ %
+ % output
+ % topasCube: struct with all read out subfields
+ %
+ % EXAMPLE calls:
+ % topasCube = topasConfig.readExternal('pathToFolder')
+
+ % read in TOPAS files in dij
+ dij = obj.readFiles(folder);
+
+ % Postprocessing
+ %resultGUI = obj.getResultGUI(dij);
+
+ end
+ end
+
+ methods (Access = protected)
+ function dij = calcDose(this,ct,cst,stf)
+ % Instance of MatRad_Config class
+ matRad_cfg = MatRad_Config.instance();
+
+ % Set parameters for full Dij calculation
+ if ~this.calcDoseDirect
+ this.scorer.calcDij = true;
+ this.numOfRuns = 1;
+ end
+ % set nested folder structure if external calculation is turned on (this will put new simulations in subfolders)
+ if strcmp(this.externalCalculation,'write')
+ this.workingDir = [matRad_cfg.primaryUserFolder filesep 'TOPAS' filesep];
+ this.workingDir = [this.workingDir stf(1).radiationMode,'_',stf(1).machine,'_',datestr(now, 'dd-mm-yy')];
+ elseif isfolder(this.externalCalculation)
+ dij = this.readExternal(this.externalCalculation);
+ return;
+ else
+ end
+
+
+ %% Initialize dose grid and dij
+
+ % load calcDoseInit as usual
+ dij = this.initDoseCalc(ct,cst,stf);
+
+ %% sending data to topas
+ if isfield(this.machine.meta,'SCD')
+ for i = 1:size(stf,2)
+ stf(i).SCD = this.machine.meta.SCD;
+ end
+ end
+
+ % Collect given weights
+ if this.calcDoseDirect
+ % w = zeros(sum([stf(:).totalNumOfBixels]),ctR.numOfCtScen);
+ w = zeros(sum([stf(:).totalNumOfBixels]),1);
+ counter = 1;
+ for i = 1:length(stf)
+ for j = 1:stf(i).numOfRays
+ rayBix = stf(i).numOfBixelsPerRay(j);
+ if isfield(stf(1).ray, 'shapes')
+ w(counter:counter+rayBix-1) = [stf(i).ray.shapes.weight];
+ else
+ w(counter:counter+rayBix-1,:) = stf(i).ray(j).weight;
+ end
+ counter = counter + rayBix;
+ end
+ end
+ end
+
+ for i = 1:numel(stf)
+ if strcmp(stf(i).radiationMode,'photons')
+ stf(i).ray.energy = stf(i).ray.energy.*ones(size(w));
+ end
+ end
+
+ % Get photon parameters for RBExD calculation
+ if this.calcBioDose
+ this.scorer.RBE = true;
+ [dij.ax,dij.bx] = matRad_getPhotonLQMParameters(cst,dij.doseGrid.numOfVoxels,1,VdoseGrid);
+ dij.abx(dij.bx>0) = dij.ax(dij.bx>0)./dij.bx(dij.bx>0);
+ end
+
+ % save current directory to revert back to later
+ currDir = cd;
+
+ if this.multScen.totNumRangeScen > 1
+ matRad_cfg.dispWarning('Range shift scenarios are not yet implemented for Monte Carlo simulations.');
+ end
+
+ for shiftScen = 1:this.multScen.totNumShiftScen
+
+ %Find first instance of the shift to select the shift values
+ ixShiftScen = find(this.multScen.linearMask(:,2) == shiftScen,1);
+
+ % manipulate isocenter
+ for k = 1:numel(stf)
+ stf(k).isoCenter = matRad_world2cubeCoords(stf(k).isoCenter,this.doseGrid) + this.multScen.isoShift(ixShiftScen,:);
+ end
+
+ % Delete previous topas files so there is no mix-up
+ files = dir([this.workingDir,'*']);
+ files = {files(~[files.isdir]).name};
+ fclose('all');
+ for i = 1:length(files)
+ delete([this.workingDir,files{i}])
+ end
+
+ % Run simulations for each scenario
+ for ctScen = 1:this.multScen.numOfCtScen
+ for rangeShiftScen = 1:this.multScen.totNumRangeScen
+ if this.multScen.scenMask(ctScen,shiftScen,rangeShiftScen)
+
+ % Save ctScen and rangeShiftScen for file constructor
+ if ct.numOfCtScen > 1
+ this.ctR.currCtScen = ctScen;
+ this.ctR.currRangeShiftScen = rangeShiftScen;
+ end
+
+ % actually write TOPAS files
+ if this.calcDoseDirect
+ this.writeAllFiles(this.ctR,cst,stf,this.machine,w);
+ else
+ this.writeAllFiles(this.ctR,cst,stf,this.machine);
+ end
+ end
+ end
+ end
+
+ % change director back to original directory
+ cd(this.workingDir);
+
+ % Skip local calculation and data readout with this parameter. All necessary parameters to read the data back in
+ % later are stored in the MCparam file that is stored in the folder. The folder is generated in the working
+ % directory and the matRad_plan*.txt file can be manually called with TOPAS.
+ if strcmp(this.externalCalculation,'write')
+ matRad_cfg.dispInfo(['TOPAS simulation skipped for external calculation\nFiles have been written to: "',strrep(this.workingDir,'\','\\'),'"']);
+ else
+ for ctScen = 1:ct.numOfCtScen
+ for beamIx = 1:numel(stf)
+ for runIx = 1:this.numOfRuns
+ if ct.numOfCtScen > 1
+ fname = sprintf('%s_field%d_ct%d_run%d',this.label,beamIx,ctScen,runIx);
+ else
+ fname = sprintf('%s_field%d_run%d',this.label,beamIx,runIx);
+ end
+
+ if strcmp(this.verbosity,'full')
+ topasCall = sprintf('%s %s.txt',this.topasExecCommand,fname);
+ else
+ topasCall = sprintf('%s %s.txt > %s.out > %s.log',this.topasExecCommand,fname,fname,fname);
+ end
+
+ % initiate parallel runs and delete previous files
+ if this.parallelRuns
+ finishedFiles{runIx} = sprintf('%s.finished',fname);
+ topasCall = [topasCall '; touch ' finishedFiles{runIx} ' &'];
+ end
+
+ % Actual simulation happening here
+ matRad_cfg.dispInfo('Calling TOPAS: %s\n',topasCall);
+ [status,cmdout] = system(topasCall,'-echo');
+
+ % Process TOPAS output and potential errors
+ % cout = splitlines(string(cmdout));
+ cout = cmdout;
+ if status == 0
+ matRad_cfg.dispInfo('TOPAS simulation completed succesfully\n');
+ else
+ if status == 139
+ matRad_cfg.dispError('TOPAS segmentation fault: might be caused from an outdated TOPAS version or Linux distribution');
+ else
+ matRad_cfg.dispError('TOPAS simulation exited with error code %d\n "%s"',status,cout(2:end-1));
+ end
+ end
+ end
+
+ % wait for parallel runs to finish and process
+ if this.parallelRuns
+ runsFinished = false;
+ pause('on');
+ while ~runsFinished
+ pause(1);
+ fin = cellfun(@(f) exist(f,'file'),finishedFiles);
+ runsFinished = all(fin);
+ end
+ % Delete marker files
+ delete(finishedFiles{:});
+ end
+ end
+ end
+ end
+
+ % revert back to original directory
+ cd(currDir);
+
+ % manipulate isocenter back
+ for k = 1:length(stf)
+ stf(k).isoCenter = stf(k).isoCenter - this.multScen.isoShift(ixShiftScen,:);
+ end
+
+ end
+
+ %% Simulation(s) finished - read out volume scorers from topas simulation
+ % Skip readout if external files were generated
+ if strcmp(this.externalCalculation,'off')
+ dij = this.readFiles(this.workingDir);
+
+ % Order fields for easier comparison between different dijs
+ dij = orderfields(dij);
+ else
+ dij.beamNum = 1;
+ dij.bixelNum = 1;
+ dij.ctGrid = this.ctR.ctGrid;
+ dij.doseGrid = this.doseGrid;
+ dij.numOfBeams = 1;
+ dij.numOfRaysPerBeam = 1;
+ dij.numOfScenarios = this.multScen.totNumScen;
+ for i = 1:this.multScen.numOfCtScen
+ for j = 1:this.multScen.totNumShiftScen
+ for k = 1:this.multScen.totNumRangeScen
+ if this.multScen.scenMask(i,j,k)
+ %TODO: loop over all expected output quantities
+ dij.physicalDose{i,j,k} = zeros(dij.ctGrid.numOfVoxels,1);
+ dij.physicalDose_std{i,j,k} = zeros(dij.ctGrid.numOfVoxels,1);
+ end
+
+ end
+ end
+ end
+ dij.rayNum = 1;
+ dij.totalNumOfBixels = 1;
+ dij.totalNumOfRays = 1;
+ dij.meta.TOPASworkingDir = this.workingDir;
+ end
+
+ this.finalizeDose();
+
+ end
+
+
+ function dij = initDoseCalc(this,ct,cst,stf)
+ dij = this.initDoseCalc@DoseEngines.matRad_MonteCarloEngineAbstract(ct,cst,stf);
+ matRad_cfg = MatRad_Config.instance();
+
+ % % for TOPAS we explicitly downsample the ct to the dose grid (might not be necessary in future versions with separated grids)
+ % Check if CT has already been resampled
+ matRad_cfg.dispInfo('Resampling cst... ');
+ if ~isfield(ct,'resampled')
+ % Allpcate resampled cubes
+ cubeHUresampled = cell(1,ct.numOfCtScen);
+ cubeResampled = cell(1,ct.numOfCtScen);
+
+ % Perform resampling to dose grid
+ for s = 1:ct.numOfCtScen
+ cubeHUresampled{s} = matRad_interp3(dij.ctGrid.x, dij.ctGrid.y', dij.ctGrid.z,ct.cubeHU{s}, ...
+ dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'linear');
+ cubeResampled{s} = matRad_interp3(dij.ctGrid.x, dij.ctGrid.y', dij.ctGrid.z,ct.cube{s}, ...
+ dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'linear');
+ end
+
+ % Allocate temporary resampled CT
+ this.ctR = ct;
+ this.ctR.cube = cell(1);
+ this.ctR.cubeHU = cell(1);
+
+ % Set CT resolution to doseGrid resolution
+ this.ctR.resolution = dij.doseGrid.resolution;
+ this.ctR.cubeDim = dij.doseGrid.dimensions;
+ this.ctR.x = dij.doseGrid.x;
+ this.ctR.y = dij.doseGrid.y;
+ this.ctR.z = dij.doseGrid.z;
+
+ % Write resampled cubes
+ this.ctR.cubeHU = cubeHUresampled;
+ this.ctR.cube = cubeResampled;
+
+ % Set flag for complete resampling
+ this.ctR.resampled = 1;
+ this.ctR.ctGrid = dij.doseGrid;
+
+ % Save original grid
+ this.ctR.originalGrid = dij.ctGrid;
+ matRad_cfg.dispInfo('done!\n');
+ else
+ this.ctR = ct;
+ matRad_cfg.dispInfo('already resampled. Skipping! \n');
+ end
+
+ % overwrite CT grid in dij in case of modulation.
+ if isfield(this.ctR,'ctGrid')
+ dij.ctGrid = this.ctR.ctGrid;
+ end
+ end
+ end
+ methods (Access = private)
+ function topasCubes = markFieldsAsEmpty(obj,topasCubes)
+
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ % Check if all fields in topasCubes are filled or overwrite with 0 if not.
+ fields = fieldnames(topasCubes);
+ for field = 1:length(fields)
+ if all(isnan(topasCubes.(fields{field}){1}(:)) | topasCubes.(fields{field}){1}(:)==0)
+ matRad_cfg.dispWarning(['Field ' fields{field} ' in topasCubes resulted in all zeros and NaN.'])
+ topasCubes.(fields{field}) = 0;
+ end
+ end
+
+ end
+
+ function topasCube = readTopasCubes(obj,folder)
+ % function to read out TOPAS data
+ %
+ % call
+ % topasCube = topasConfig.readTopasCubes(folder,dij)
+ % topasCube = obj.readTopasCubes(folder,dij)
+ %
+ % input
+ % folder: Path to folder where TOPAS files are in (as string)
+ % dij: dij struct (this part needs update)
+ %
+ % output
+ % topasCube: struct with all read out subfields
+
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ % obj.MCparam.scoreReportQuantity = 'Sum';
+ % Process reportQuantities (for example 'Sum' or 'Standard_Deviation'read
+ if iscell(obj.MCparam.scoreReportQuantity)
+ obj.MCparam.numOfReportQuantities = length(obj.MCparam.scoreReportQuantity);
+ else
+ obj.MCparam.numOfReportQuantities = 1;
+ obj.MCparam.scoreReportQuantity = {obj.MCparam.scoreReportQuantity};
+ end
+
+ % Normalize with histories and particles/weight
+ correctionFactor = obj.numParticlesPerHistory / double(obj.MCparam.nbHistoriesTotal);
+
+ % Get all saved quantities
+ % Make sure that the filename always ends on 'run1_tally'
+ switch obj.MCparam.outputType
+ case 'csv'
+ searchstr = 'score_matRad_plan_field1_run1_*.csv';
+ files = dir([folder filesep searchstr]);
+ %obj.MCparam.tallies = cellfun(@(x) extractBetween(x,'run1_','.csv') ,{files(:).name}); %Not Octave compatible
+ nameBegin = strfind(searchstr,'*');
+ obj.MCparam.tallies = cellfun(@(s) s(nameBegin:end-4),{files(:).name},'UniformOutput',false);
+ case 'binary'
+ searchstr = 'score_matRad_plan_field1_run1_*.bin';
+ files = dir([folder filesep searchstr]);
+ %obj.MCparam.tallies = cellfun(@(x) extractBetween(x,'run1_','.bin') ,{files(:).name}); %Not Octave compatible
+ nameBegin = strfind(searchstr,'*');
+ obj.MCparam.tallies = cellfun(@(s) s(nameBegin:end-4),{files(:).name},'UniformOutput',false);
+ end
+
+ obj.MCparam.tallies = unique(obj.MCparam.tallies);
+ talliesCut = strrep(obj.MCparam.tallies,'-','_');
+
+ % Load data for each tally individually
+ for t = 1:length(obj.MCparam.tallies)
+ tnameFile = obj.MCparam.tallies{t};
+ tname = talliesCut{t};
+ % Loop over all beams/fields and ctScenarios
+ for f = 1:obj.MCparam.nbFields
+ for ctScen = 1:obj.MCparam.numOfCtScen
+
+ % Loop over all batches/runs
+ for k = 1:obj.MCparam.nbRuns
+ % Get file name of current field, run and tally (and ct, if applicable)
+ if obj.MCparam.numOfCtScen > 1
+ genFileName = sprintf('score_%s_field%d_ct%d_run%d_%s',obj.MCparam.simLabel,f,ctScen,k,tnameFile);
+ else
+ genFileName = sprintf('score_%s_field%d_run%d_%s',obj.MCparam.simLabel,f,k,tnameFile);
+ end
+
+
+ switch obj.MCparam.outputType
+ case 'csv'
+ % Generate csv file path to load
+ genFullFile = fullfile(folder,[genFileName '.csv']);
+ case 'binary'
+ % Generate bin file path to load
+ genFullFile = fullfile(folder,[genFileName '.bin']);
+ otherwise
+ matRad_cfg.dispError('Not implemented!');
+ end
+
+ % Read data from scored TOPAS files
+ dataRead = obj.readBinCsvData(genFullFile);
+
+ for i = 1:numel(dataRead)
+ data.(obj.MCparam.scoreReportQuantity{i}){k} = dataRead{i};
+ end
+
+ % for example the standard deviation is not calculated for alpha/beta so a loop through all
+ % reportQuantities does not work here
+ currNumOfQuantities = numel(dataRead);
+ end
+
+ % Set dimensions of output cube
+ cubeDim = size(dataRead{1});
+
+ % add STD quadratically
+ for i = 1:currNumOfQuantities
+ if ~isempty(strfind(lower(obj.MCparam.scoreReportQuantity{i}),'standard_deviation'))
+ topasSum.(obj.MCparam.scoreReportQuantity{i}) = sqrt(double(obj.MCparam.nbHistoriesTotal)) * sqrt(sum(cat(4,data.(obj.MCparam.scoreReportQuantity{i}){:}).^2,4));
+ else
+ topasSum.(obj.MCparam.scoreReportQuantity{i}) = sum(cat(4,data.(obj.MCparam.scoreReportQuantity{i}){:}),4);
+ end
+ end
+
+ if ~isempty(strfind(lower(tnameFile),'dose'))
+ if obj.MCparam.nbRuns > 1
+ % Calculate Standard Deviation from batches
+ topasMeanDiff = zeros(cubeDim(1),cubeDim(2),cubeDim(3));
+ for k = 1:obj.MCparam.nbRuns
+ topasMeanDiff = topasMeanDiff + (data.Sum{k} - topasSum.Sum / obj.MCparam.nbRuns).^2;
+ end
+ % variance of the mean
+ topasVarMean = topasMeanDiff./(obj.MCparam.nbRuns - 1)./obj.MCparam.nbRuns;
+ % std of the MEAN!
+ topasStdMean = sqrt(topasVarMean);
+ % std of the SUM
+ topasStdSum = topasStdMean * correctionFactor * obj.MCparam.nbRuns;
+
+ % Save std to topasCube
+ topasCube.([tname '_batchStd_beam' num2str(f)]){ctScen} = topasStdSum;
+ end
+
+ for i = 1:currNumOfQuantities
+ topasSum.(obj.MCparam.scoreReportQuantity{i}) = correctionFactor .* topasSum.(obj.MCparam.scoreReportQuantity{i});
+ end
+
+ elseif any(cellfun(@(teststr) ~isempty(strfind(tname,teststr)), {'alpha','beta','RBE','LET'}))
+ for i = 1:currNumOfQuantities
+ topasSum.(obj.MCparam.scoreReportQuantity{i}) = topasSum.(obj.MCparam.scoreReportQuantity{i}) ./ obj.MCparam.nbRuns;
+ end
+ end
+
+ % Tally per field
+ if isfield(topasSum,'Sum')
+ topasCube.([tname '_beam' num2str(f)]){ctScen} = topasSum.Sum;
+ end
+ if isfield(topasSum,'Standard_Deviation')
+ topasCube.([tname '_std_beam' num2str(f)]){ctScen} = topasSum.Standard_Deviation;
+ end
+ end
+ end
+ end
+ end
+
+ function dataOut = readBinCsvData(~,genFullFile)
+
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ if ~isempty(strfind(lower(genFullFile),'.csv'))
+ % Read csv header file to get cubeDim and number of scorers automatically
+ fid = fopen(genFullFile);
+ header = textscan(fid,'%[^,],%[^,],%[^,]',1);
+ fclose(fid);
+
+ % Split header in rows
+ header = strsplit(strrep(header{1}{1},' ',''),'#');
+ elseif ~isempty(strfind(lower(genFullFile),'.bin'))
+ % Isolate filename without ending
+ [folder, filename] = fileparts(genFullFile);
+ strippedFileName = [folder filesep filename];
+
+ % Read binheader file to get cubeDim and number of scorers automatically
+ fID = fopen([strippedFileName '.binheader']);
+ header = textscan(fID,'%c');
+ fclose(fID);
+
+ % Split header in rows
+ header = strsplit(header{1}','#')';
+ else
+ % Error if neither csv nor bin
+ matRad_cfg.dispError('Not implemented!');
+ end
+
+ % Find rows where number of bins are stored
+ xLine = find(cellfun(@(x) ~isempty(x), strfind(header,'Xin')));
+ cubeDim(2) = str2double(header{xLine}(4:strfind(header{xLine},'binsof')-1));
+ cubeDim(1) = str2double(header{xLine+1}(4:strfind(header{xLine+1},'binsof')-1));
+ cubeDim(3) = str2double(header{xLine+2}(4:strfind(header{xLine+2},'binsof')-1));
+
+ if ~isempty(strfind(lower(genFullFile),'.csv'))
+ % Read out bin data
+ dataOut = matRad_readCsvData(genFullFile,cubeDim);
+ elseif ~isempty(strfind(lower(genFullFile),'.bin'))
+ % Read out bin data
+ dataOut = matRad_readBinData(genFullFile,cubeDim);
+ end
+
+ end
+
+ function dij = prepareDij(obj,topasCubes)
+
+ % Load ctScen variable
+ numOfScenarios = obj.MCparam.numOfCtScen;
+
+ % Set flag for RBE and LET
+ if any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'alpha')), fieldnames(topasCubes)))
+ obj.scorer.RBE = true;
+ end
+ if any(cellfun(@(teststr) ~isempty(strfind(teststr,'LET')), fieldnames(topasCubes)))
+ obj.scorer.LET = true;
+ end
+
+ % Create empty dij
+ dij.numOfScenarios = numOfScenarios;
+ dij.numOfBeams = max(obj.MCparam.beamNum);
+ dij.numOfRaysPerBeam = obj.MCparam.numOfRaysPerBeam;
+ dij.totalNumOfRays = sum(dij.numOfRaysPerBeam);
+ dij.totalNumOfBixels = length(obj.MCparam.bixelNum);
+ dij.bixelNum = obj.MCparam.bixelNum';
+ dij.rayNum = obj.MCparam.rayNum';
+ dij.beamNum = obj.MCparam.beamNum';
+
+ % Write dij grids
+ dij.doseGrid = obj.MCparam.ctGrid;
+ dij.ctGrid = obj.MCparam.originalGrid;
+
+ % Save RBE models in dij for postprocessing in calcCubes
+ if obj.scorer.RBE
+ dij.RBE_models = obj.MCparam.RBE_models;
+ dij.ax = obj.MCparam.ax;
+ dij.bx = obj.MCparam.bx;
+ dij.abx = obj.MCparam.abx;
+ end
+
+ % Get basic tallies from topasCubes for sparse matrix allocation
+ beamNames = strsplit(sprintf('_beam%i,',1:dij.numOfBeams),',');
+ if obj.scorer.calcDij
+ rayNames = strsplit(sprintf('_ray%i,',unique(dij.rayNum)),',');
+ bixelNames = strsplit(sprintf('_bixel%i,',unique(dij.bixelNum)),',');
+ topasCubesTallies = unique(erase(fieldnames(topasCubes),rayNames));
+ topasCubesTallies = unique(erase(topasCubesTallies,bixelNames));
+ topasCubesTallies = unique(erase(topasCubesTallies,beamNames));
+ else
+ topasCubesTallies = unique(erase(fieldnames(topasCubes),beamNames));
+ end
+
+ % Get default tallies from dose
+ dijTallies = topasCubesTallies(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'dose')), topasCubesTallies));
+
+ % Handle LET tally
+ if obj.scorer.LET
+ dijTallies{end+1} = 'mLETDose';
+ % dijTallies{end+1} = 'LET';
+ end
+
+ % Get unique tallies for RBE models
+ if obj.scorer.RBE
+ for r = 1:length(obj.MCparam.RBE_models)
+ dijTallies{end+1} = ['mAlphaDose_' obj.MCparam.RBE_models{r}];
+ dijTallies{end+1} = ['mSqrtBetaDose_' obj.MCparam.RBE_models{r}];
+ % dijTallies{end+1} = 'alpha';
+ % dijTallies{end+1} = 'beta';
+ end
+ end
+
+ % Create empty sparse matrices
+ % Note that for MonteCarlo, there are no individual bixels, but only 2 beams
+ for t = 1:length(dijTallies)
+ for ctScen = 1:dij.numOfScenarios
+ if obj.scorer.calcDij
+ dij.(dijTallies{t}){ctScen,1} = spalloc(obj.MCparam.ctGrid.numOfVoxels,dij.totalNumOfBixels,1);
+ else
+ dij.(dijTallies{t}){ctScen,1} = spalloc(obj.MCparam.ctGrid.numOfVoxels,dij.numOfBeams,1);
+ end
+ end
+ end
+
+ end
+
+ function dij = fillDij(obj,topasCubes,dij)
+ %TODO: Insert documentation
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ % Load weights from parameter variable
+ w = obj.MCparam.weights;
+
+ % Get basic tallies from topasCubes for sparse matrix allocation
+ beamNames = strsplit(sprintf('_beam%i,',1:dij.numOfBeams),',');
+ if obj.scorer.calcDij
+ rayNames = strsplit(sprintf('_ray%i,',unique(dij.rayNum)),',');
+ bixelNames = strsplit(sprintf('_bixel%i,',unique(dij.bixelNum)),',');
+ topasCubesTallies = unique(erase(fieldnames(topasCubes),rayNames));
+ topasCubesTallies = unique(erase(topasCubesTallies,bixelNames));
+ topasCubesTallies = unique(erase(topasCubesTallies,beamNames));
+ else
+ topasCubesTallies = unique(erase(fieldnames(topasCubes),beamNames));
+ end
+
+ % Allocate possible scored quantities
+ processedQuantities = {'','_std','_batchStd'};
+ topasCubesTallies = unique(erase(topasCubesTallies,processedQuantities(2:end)));
+
+
+ % Loop through 4D scenarios
+ for ctScen = 1:dij.numOfScenarios
+
+ % Process physicalDose
+ % this is done separately since it's needed for processing the other dose fields
+ if obj.scorer.calcDij
+ for d = 1:dij.totalNumOfBixels
+ physDoseFields = strfind(lower(topasCubesTallies),'physicaldose');
+ physDoseFields = not(cellfun('isempty',physDoseFields));
+ for j = find(physDoseFields)'
+ % loop through possible quantities
+ for p = 1:length(processedQuantities)
+ % Check if current quantity is available and write to dij
+ if isfield(topasCubes,[topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) processedQuantities{p} '_beam' num2str(dij.beamNum(d))]) ...
+ && iscell(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) processedQuantities{p} '_beam' num2str(dij.beamNum(d))]))
+ dij.([topasCubesTallies{j} processedQuantities{p}]){ctScen,1}(:,d) = sum(w)*reshape(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) processedQuantities{p} '_beam' num2str(dij.beamNum(d))]){ctScen},[],1);
+ end
+ end
+ end
+ end
+ else
+ for d = 1:dij.numOfBeams
+ physDoseFields = strfind(lower(topasCubesTallies),'physicaldose');
+ physDoseFields = not(cellfun('isempty',physDoseFields));
+ for j = find(physDoseFields)'
+ for p = 1:length(processedQuantities)
+ % Check if current quantity is available and write to dij
+ if isfield(topasCubes,[topasCubesTallies{j} processedQuantities{p} '_beam' num2str(d)]) && iscell(topasCubes.([topasCubesTallies{j} processedQuantities{p} '_beam' num2str(d)]))
+ dij.([topasCubesTallies{j} processedQuantities{p}]){ctScen}(:,d) = sum(w)*reshape(topasCubes.([topasCubesTallies{j} processedQuantities{p} '_beam',num2str(d)]){ctScen},[],1);
+ end
+ end
+ end
+ end
+ end
+
+ % Remove processed physDoseFields from total tallies
+ topasCubesTallies = topasCubesTallies(~physDoseFields);
+
+ % Process other fields
+ if obj.scorer.calcDij
+ for d = 1:dij.totalNumOfBixels
+ for j = 1:numel(topasCubesTallies)
+ % Handle dose to water
+ if ~isempty(strfind(lower(topasCubesTallies{j}),'dose'))
+ % loop through possible quantities
+ for p = 1:length(processedQuantities)
+ % Check if current quantity is available and write to dij
+ if isfield(topasCubes,[topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) processedQuantities{p} '_beam' num2str(dij.beamNum(d))]) ...
+ && iscell(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) processedQuantities{p} '_beam' num2str(dij.beamNum(d))]))
+ dij.([topasCubesTallies{j} processedQuantities{p}]){ctScen,1}(:,d) = sum(w)*reshape(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) processedQuantities{p} '_beam' num2str(dij.beamNum(d))]){ctScen},[],1);
+ end
+ end
+ % Handle RBE-related quantities (not multiplied by sum(w)!)
+ elseif ~isempty(strfind(lower(topasCubesTallies{j}),'alpha'))
+ modelName = strsplit(topasCubesTallies{j},'_');
+ modelName = modelName{end};
+ if isfield(topasCubes,[topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]) ...
+ && iscell(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]))
+ dij.(['mAlphaDose_' modelName]){ctScen,1}(:,d) = reshape(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]){ctScen},[],1) .* dij.physicalDose{ctScen,1}(:,d);
+ end
+ elseif ~isempty(strfind(lower(topasCubesTallies{j}),'beta'))
+ modelName = strsplit(topasCubesTallies{j},'_');
+ modelName = modelName{end};
+ if isfield(topasCubes,[topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]) ...
+ && iscell(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]))
+ dij.(['mSqrtBetaDose_' modelName]){ctScen,1}(:,d) = sqrt(reshape(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]){ctScen},[],1)) .* dij.physicalDose{ctScen,1}(:,d);
+ end
+ elseif ~isempty(strfind(topasCubesTallies{j},'LET'))
+ if isfield(topasCubes,[topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]) ...
+ && iscell(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]))
+ dij.mLETDose{ctScen,1}(:,d) = reshape(topasCubes.([topasCubesTallies{j} '_ray' num2str(dij.rayNum(d)) '_bixel' num2str(dij.bixelNum(d)) '_beam' num2str(dij.beamNum(d))]){ctScen},[],1) .* dij.physicalDose{ctScen,1}(:,d);
+ end
+ else
+ matRad_cfg.dispError('Postprocessing error: Tallies handles incorrectly')
+ end
+ end
+ end
+ else
+ for d = 1:dij.numOfBeams
+ for j = 1:numel(topasCubesTallies)
+ % Handle dose to medium and dose to water
+ if ~isempty(strfind(lower(topasCubesTallies{j}),'dose'))
+ % loop through possible quantities
+ for p = 1:length(processedQuantities)
+ % Check if current quantity is available and write to dij
+ if isfield(topasCubes,[topasCubesTallies{j} processedQuantities{p} '_beam' num2str(d)]) && iscell(topasCubes.([topasCubesTallies{j} processedQuantities{p} '_beam' num2str(d)]))
+ dij.([topasCubesTallies{j} processedQuantities{p}]){ctScen}(:,d) = sum(w)*reshape(topasCubes.([topasCubesTallies{j} processedQuantities{p} '_beam',num2str(d)]){ctScen},[],1);
+ end
+ end
+ % Handle RBE-related quantities (not multiplied by sum(w)!)
+ elseif ~isempty(strfind(lower(topasCubesTallies{j}),'alpha'))
+ modelName = strsplit(topasCubesTallies{j},'_');
+ modelName = modelName{end};
+ if isfield(topasCubes,[topasCubesTallies{j} '_beam' num2str(d)]) && iscell(topasCubes.([topasCubesTallies{j} '_beam' num2str(d)]))
+ dij.(['mAlphaDose_' modelName]){ctScen}(:,d) = reshape(topasCubes.([topasCubesTallies{j} '_beam',num2str(d)]){ctScen},[],1) .* dij.physicalDose{ctScen}(:,d);
+ end
+ elseif ~isempty(strfind(lower(topasCubesTallies{j}),'beta'))
+ modelName = strsplit(topasCubesTallies{j},'_');
+ modelName = modelName{end};
+ if isfield(topasCubes,[topasCubesTallies{j} '_beam' num2str(d)]) && iscell(topasCubes.([topasCubesTallies{j} '_beam' num2str(d)]))
+ dij.(['mSqrtBetaDose_' modelName]){ctScen}(:,d) = sqrt(reshape(topasCubes.([topasCubesTallies{j} '_beam',num2str(d)]){ctScen},[],1)) .* dij.physicalDose{ctScen}(:,d);
+ end
+ elseif ~isempty(strfind(topasCubesTallies{j},'LET'))
+ if isfield(topasCubes,[topasCubesTallies{j} '_beam' num2str(d)]) && iscell(topasCubes.([topasCubesTallies{j} '_beam' num2str(d)]))
+ dij.mLETDose{ctScen}(:,d) = reshape(topasCubes.([topasCubesTallies{j} '_beam',num2str(d)]){ctScen},[],1) .* dij.physicalDose{ctScen}(:,d);
+ end
+ else
+ matRad_cfg.dispError('Postprocessing error: Tallies handles incorrectly')
+ end
+ end
+ end
+ end
+ end
+
+ % Save number of histories and particles to Dij
+ dij.nbHistoriesTotal = obj.MCparam.nbHistoriesTotal;
+ dij.nbParticlesTotal = obj.MCparam.nbParticlesTotal;
+
+ end
+
+ function writeRunHeader(obj,fID,fieldIx,runIx,ctScen)
+ %TODO: Insert documentation
+ fprintf(fID,'s:Sim/PlanLabel = "%s"\n',obj.label);
+ if exist('ctScen','var')
+ fprintf(fID,'s:Sim/ScoreLabel = "score_%s_field%d_ct%d_run%d"\n',obj.label,fieldIx,ctScen,runIx);
+ else
+ fprintf(fID,'s:Sim/ScoreLabel = "score_%s_field%d_run%d"\n',obj.label,fieldIx,runIx);
+ end
+ fprintf(fID,'\n');
+
+ logicalString = {'"False"', '"True"'};
+
+ fprintf(fID,'i:Ma/Verbosity = %d\n',obj.verbosity.material);
+ fprintf(fID,'i:Ts/TrackingVerbosity = %d\n',obj.verbosity.tracking);
+ fprintf(fID,'i:Ts/EventVerbosity = %d\n',obj.verbosity.event);
+ fprintf(fID,'i:Ts/RunVerbosity = %d\n',obj.verbosity.run);
+ fprintf(fID,'b:Ts/ShowCPUTime = %s\n',logicalString{obj.verbosity.cputime + 1});
+ fprintf(fID,'i:Tf/Verbosity = %d\n',obj.verbosity.timefeatures);
+ fprintf(fID,'i:Ts/MaxInterruptedHistories = %d\n',obj.verbosity.maxinterruptedhistories);
+ fprintf(fID,'i:Ts/NumberOfThreads = %d\n',obj.numThreads);
+ fprintf(fID,'i:Ts/MaximumNumberOfDetailedErrorReports = %d\n',obj.verbosity.maxDetailedErrorReports);
+ fprintf(fID,'i:Ts/ShowHistoryCountAtInterval = %d\n',10^(floor(log10(1/obj.numOfRuns * obj.numHistoriesDirect))-1));
+ fprintf(fID,'\n');
+
+
+ fprintf(fID,'s:Sim/DoseScorerOutputType = "%s"\n',obj.scorer.outputType);
+ if iscell(obj.scorer.reportQuantity)
+ fprintf(fID,'sv:Sim/DoseScorerReport = %i ',length(obj.scorer.reportQuantity));
+ fprintf(fID,'"%s" ',obj.scorer.reportQuantity{:});
+ fprintf(fID,'\n');
+ else
+ fprintf(fID,'sv:Sim/DoseScorerReport = 1 "%s"\n',obj.scorer.reportQuantity);
+ end
+ fprintf(fID,'\n');
+ fprintf(fID,['i:Ts/Seed = ',num2str(runIx),'\n']);
+
+ %TODO: remove or document
+ %fprintf(fID,'includeFile = %s/TOPAS_Simulation_Setup.txt\n',obj.thisFolder);
+ %fprintf(fID,'includeFile = %s/TOPAS_matRad_geometry.txt\n',obj.thisFolder);
+ %fprintf(fID,'includeFile = %s/TOPAS_scorer_surfaceIC.txt\n',obj.thisFolder);
+ end
+
+ function writeFieldHeader(obj,fID,ctScen)
+ %TODO: Insert documentation
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ if ~strcmp(obj.beamProfile,'phasespace')
+ fprintf(fID,'u:Sim/HalfValue = %d\n',0.5);
+ fprintf(fID,'u:Sim/SIGMA2FWHM = %d\n',2.354818);
+ fprintf(fID,'u:Sim/FWHM2SIGMA = %d\n',0.424661);
+ fprintf(fID,'\n');
+ else
+ fprintf(fID,'u:Sim/HalfValue = %d\n',0.5);
+ fprintf(fID,'u:Sim/SIGMA2FWHM = %d\n',1.1905);
+ fprintf(fID,'u:Sim/FWHM2SIGMA = %d\n',0.84);
+ fprintf(fID,'\n');
+ end
+
+ fprintf(fID,'d:Sim/ElectronProductionCut = %f mm\n',obj.electronProductionCut);
+ fprintf(fID,'s:Sim/WorldMaterial = "%s"\n',obj.worldMaterial);
+ fprintf(fID,'\n');
+
+ % Add ctScen number to filenames
+ if exist('ctScen','var')
+ paramFile = strsplit(obj.outfilenames.patientParam,'.');
+ paramFile = strjoin(paramFile,[num2str(ctScen) '.']);
+ else
+ paramFile = obj.outfilenames.patientParam;
+ end
+
+ fprintf(fID,'includeFile = %s\n',paramFile);
+ fprintf(fID,'\n');
+
+ fname = fullfile(obj.topasFolder,obj.infilenames.geometry);
+ matRad_cfg.dispInfo('Reading Geometry from %s\n',fname);
+ world = fileread(fname);
+ fprintf(fID,'%s\n',world);
+
+ end
+
+ function writeScorers(obj,fID)
+ %TODO: Insert documentation
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ obj.MCparam.outputType = obj.scorer.outputType;
+
+ % write dose to medium scorer
+ if obj.scorer.doseToMedium
+ fname = fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_doseToMedium);
+ matRad_cfg.dispDebug('Reading doseToMedium scorer from %s\n',fname);
+ scorerName = fileread(fname);
+ fprintf(fID,'\n%s\n\n',scorerName);
+
+ % Update MCparam.tallies with processed scorer
+ obj.MCparam.tallies = [obj.MCparam.tallies,{'physicalDose'}];
+ end
+
+ % write RBE scorer
+ if obj.scorer.RBE
+ for i = 1:length(obj.scorer.RBE_model)
+ switch obj.radiationMode
+ case 'protons'
+ % Process available varRBE models for protons
+
+ if ~isempty(strfind(lower(obj.scorer.RBE_model{i}),'mcn'))
+ fname = fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_RBE_MCN);
+ elseif ~isempty(strfind(lower(obj.scorer.RBE_model{i}),'wed'))
+ fname = fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_RBE_WED);
+ else
+ matRad_cfg.dispError(['Model ',obj.scorer.RBE_model{i},' not implemented for ',obj.radiationMode]);
+ end
+ case {'carbon','helium'}
+ % Process available varRBE models for carbon and helium
+ if ~isempty(strfind(lower(obj.scorer.RBE_model{i}),'libamtrack'))
+ fname = fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_RBE_libamtrack);
+ elseif ~isempty(strfind(lower(obj.scorer.RBE_model{i}),'lem'))
+ fname = fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_RBE_LEM1);
+ else
+ matRad_cfg.dispError(['Model ',obj.scorer.RBE_model{i},' not implemented for ',obj.radiationMode]);
+ end
+ otherwise
+ % Throw error in case an invalid radiationMode has been selected
+ matRad_cfg.dispError(['Model ',obj.scorer.RBE_model{i},' not implemented for ',obj.radiationMode]);
+ end
+
+ % Read appropriate scorer from file and write to config file
+ matRad_cfg.dispDebug('Reading RBE Scorer from %s\n',fname);
+ scorerName = fileread(fname);
+ fprintf(fID,'\n%s\n\n',scorerName);
+ end
+
+ % Begin writing biological scorer components: cell lines
+ switch obj.radiationMode
+ case 'protons'
+ fprintf(fID,'\n### Biological Parameters ###\n');
+ fprintf(fID,'sv:Sc/CellLines = 1 "CellLineGeneric"\n');
+ fprintf(fID,'d:Sc/CellLineGeneric/Alphax = Sc/AlphaX /Gy\n');
+ fprintf(fID,'d:Sc/CellLineGeneric/Betax = Sc/BetaX /Gy2\n');
+ fprintf(fID,'d:Sc/CellLineGeneric/AlphaBetaRatiox = Sc/AlphaBetaX Gy\n\n');
+ case {'carbon','helium'}
+ fprintf(fID,'\n### Biological Parameters ###\n');
+ fprintf(fID,'sv:Sc/CellLines = 1 "CellGeneric_abR2"\n');
+ fprintf(fID,'d:Sc/CellGeneric_abR2/Alphax = Sc/AlphaX /Gy\n');
+ fprintf(fID,'d:Sc/CellGeneric_abR2/Betax = Sc/BetaX /Gy2\n\n');
+ % fprintf(fID,'d:Sc/CellGeneric_abR2/AlphaBetaRatiox = Sc/AlphaBetaX Gy\n');
+ otherwise
+ matRad_cfg.dispError([obj.radiationMode ' not implemented']);
+ end
+
+ % write biological scorer components: dose parameters
+ matRad_cfg.dispDebug('Writing Biologial Scorer components.\n');
+ fprintf(fID,'d:Sc/PrescribedDose = %.4f Gy\n',obj.bioParameters.PrescribedDose);
+ fprintf(fID,'b:Sc/SimultaneousExposure = %s\n',obj.bioParameters.SimultaneousExposure);
+ fprintf(fID,'d:Sc/AlphaX = %.4f /Gy\n',obj.bioParameters.AlphaX);
+ fprintf(fID,'d:Sc/BetaX = %.4f /Gy2\n',obj.bioParameters.BetaX);
+ fprintf(fID,'d:Sc/AlphaBetaX = %.4f Gy\n',obj.bioParameters.AlphaX/obj.bioParameters.BetaX);
+
+ % Update MCparam.tallies with processed scorer
+ for i = 1:length(obj.scorer.RBE_model)
+ obj.MCparam.tallies = [obj.MCparam.tallies,{['alpha_' obj.scorer.RBE_model{i}],['beta_' obj.scorer.RBE_model{i}]}];
+ end
+ end
+
+ % Write share sub-scorer
+ if obj.scorer.sharedSubscorers && obj.scorer.RBE
+ % Select appropriate scorer from selected flags
+ scorerNames = {'Alpha','Beta'};
+ if any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'mcn')), obj.scorer.RBE_model))
+ obj.scorer.LET = true;
+ obj.scorer.doseToWater = true;
+ scorerPrefix = 'McNamara';
+ elseif any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'wed')), obj.scorer.RBE_model))
+ obj.scorer.LET = true;
+ obj.scorer.doseToWater = true;
+ scorerPrefix = 'Wedenberg';
+ elseif any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'lem')), obj.scorer.RBE_model)) || any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'libamtrack')), obj.scorer.RBE_model))
+ obj.scorer.doseToWater = true;
+ scorerPrefix = 'tabulated';
+ end
+
+ % Write subscorer to config files
+ for s = 1:length(scorerNames)
+ if strcmp(obj.radiationMode,'protons')
+ fprintf(fID,'s:Sc/%s%s/ReferencedSubScorer_LET = "ProtonLET"\n',scorerPrefix,scorerNames{s});
+ end
+ fprintf(fID,'s:Sc/%s%s/ReferencedSubScorer_Dose = "Tally_DoseToWater"\n',scorerPrefix,scorerNames{s});
+ end
+ end
+
+ % write dose to water scorer from file
+ if obj.scorer.doseToWater
+ fname = fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_doseToWater);
+ matRad_cfg.dispDebug('Reading doseToWater scorer from %s\n',fname);
+ scorerName = fileread(fname);
+ fprintf(fID,'\n%s\n\n',scorerName);
+
+ % Update MCparam.tallies with processed scorer
+ obj.MCparam.tallies = [obj.MCparam.tallies,{'doseToWater'}];
+ end
+
+ % write LET scorer from file
+ if obj.scorer.LET
+ if strcmp(obj.radiationMode,'protons')
+ fname = fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_LET);
+ matRad_cfg.dispDebug('Reading LET Scorer from %s\n',fname);
+ scorerName = fileread(fname);
+ fprintf(fID,'\n%s\n\n',scorerName);
+
+ % Update MCparam.tallies with processed scorer
+ obj.MCparam.tallies = [obj.MCparam.tallies,{'LET'}];
+ else
+ matRad_cfg.dispError('LET in TOPAS only for protons!\n');
+ end
+ end
+
+ % write volume scorer from file
+ if obj.scorer.volume
+ fileList = dir(fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,'TOPAS_scorer_volume_*.in'));
+ for fileIx=1:length(fileList)
+ fname = fullfile(obj.topasFolder,fileList(fileIx).name);
+ matRad_cfg.dispDebug('Reading Volume Scorer from %s\n',fname);
+ scorerName = fileread(fname);
+ fprintf(fID,'\n%s\n\n',scorerName);
+
+ tallyLabel = regexprep(fileList(fileIx).name,'TOPAS_scorer_volume_','');
+ tallyLabel = regexprep(tallyLabel,'.txt.in','');
+
+ % Update MCparam.tallies with processed scorer
+ obj.MCparam.tallies = [obj.MCparam.tallies,{tallyLabel}];
+ end
+ end
+
+ % write surface track count from file
+ if obj.scorer.surfaceTrackCount
+ fname = fullfile(obj.topasFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_surfaceTrackCount);
+ matRad_cfg.dispDebug('Reading surface scorer from %s\n',fname);
+ scorerName = fileread(fname);
+ fprintf(fID,'\n%s\n\n',scorerName);
+
+ % Update MCparam.tallies with processed scorer
+ obj.MCparam.tallies = [obj.MCparam.tallies,{'IC'}];
+ end
+
+
+ % Write timefeature-splitting in case of dij calculation
+ if obj.scorer.calcDij
+ tallyName = cell(1,0);
+ if obj.scorer.RBE
+ if any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'mcn')), obj.MCparam.RBE_models))
+ tallyName{end+1} = 'McNamaraAlpha';
+ tallyName{end+1} = 'McNamaraBeta';
+ end
+ if any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'wed')), obj.MCparam.RBE_models))
+ tallyName{end+1} = 'WedenbergAlpha';
+ tallyName{end+1} = 'WedenbergBeta';
+ end
+ if any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'libamtrack')), obj.MCparam.RBE_models))
+ tallyName{end+1} = 'tabulatedAlpha';
+ tallyName{end+1} = 'tabulatedBeta';
+ end
+ if any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'lem')), obj.MCparam.RBE_models))
+ tallyName{end+1} = 'tabulatedAlpha';
+ tallyName{end+1} = 'tabulatedBeta';
+ end
+ end
+ if obj.scorer.LET
+ tallyName{end+1} = 'ProtonLET';
+ end
+ if obj.scorer.surfaceTrackCount
+ tallyName{end+1} = 'IC';
+ end
+ if obj.scorer.doseToMedium
+ tallyName{end+1} = 'Patient/Tally_DoseToMedium';
+ end
+ if obj.scorer.doseToMedium
+ tallyName{end+1} = 'Tally_DoseToWater';
+ end
+
+ % We should discuss here if that's something that has to be available for photons as well, turned off for now
+ if ~strcmp(obj.radiationMode,'photons')
+ fprintf(fID,'#-- Time feature splitting for dij calculation\n');
+
+ for i = 1:length(tallyName)
+ fprintf(fID,['s:Sc/' tallyName{i} '/SplitByTimeFeature = "ImageName"\n']);
+ end
+ end
+ end
+ end
+
+ function writeStfFields(obj,ct,stf,w,baseData)
+ %TODO: Insert documentation
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+ isPhoton = false;
+
+ switch obj.radiationMode
+ case 'photons'
+ % if photons
+ isPhoton = true;
+ if any(ismember(obj.beamProfile,{'biGaussian','simple'}))
+ matRad_cfg.dispWarning('beamProfile "%s" not available for photons, switching to "%s" as default.',obj.beamProfile,obj.defaultPhotonBeamProfile);
+ obj.beamProfile = obj.defaultPhotonBeamProfile;
+ end
+
+ otherwise
+ % if particles
+ if ~any(ismember(obj.beamProfile,{'biGaussian','simple'}))
+ matRad_cfg.dispWarning('beamProfile "%s" not available for particles, switching to "%s" as default.',obj.beamProfile,obj.defaultParticleBeamProfile);
+ obj.beamProfile = obj.defaultParticleBeamProfile;
+ end
+ end
+
+ %Bookkeeping
+ obj.MCparam.nbFields = length(stf);
+
+ %Sanity check
+ if numel(w) ~= sum([stf(:).totalNumOfBixels])
+ matRad_cfg.dispError('Given number of weights (#%d) doesn''t match bixel count in stf (#%d)',numel(w), sum([stf(:).totalNumOfBixels]));
+ end
+
+ nParticlesTotalBixel = round(obj.numParticlesPerHistory * w);
+ nParticlesTotal = sum(nParticlesTotalBixel);
+ maxParticlesBixel = obj.numParticlesPerHistory * max(w(:));
+ minParticlesBixel = round(max([obj.minRelWeight*maxParticlesBixel,1]));
+
+ switch obj.modeHistories
+ case 'num'
+ obj.fracHistories = obj.numHistoriesDirect ./ sum(nParticlesTotalBixel);
+ case 'frac'
+ obj.numHistoriesDirect = sum(nParticlesTotalBixel);
+ otherwise
+ matRad_cfg.dispError('Invalid history setting!');
+ end
+
+ nParticlesTotal = 0;
+
+ %Preread beam setup
+ switch obj.beamProfile
+ case 'biGaussian'
+ fname = fullfile(obj.topasFolder,obj.infilenames.beam_biGaussian);
+ TOPAS_beamSetup = fileread(fname);
+ matRad_cfg.dispInfo('Reading ''%s'' Beam Characteristics from ''%s''\n',obj.beamProfile,fname);
+
+ case 'simple'
+ fname = fullfile(obj.topasFolder,obj.infilenames.beam_generic);
+ TOPAS_beamSetup = fileread(fname);
+ matRad_cfg.dispInfo('Reading ''%s'' Beam Characteristics from ''%s''\n',obj.beamProfile,fname);
+
+ case 'phasespace'
+ fname = fullfile(obj.topasFolder,obj.infilenames.beam_phasespace);
+ TOPAS_beamSetup = fileread(fname);
+ obj.pencilBeamScanning = 0 ;
+ matRad_cfg.dispInfo('Reading ''%s'' Beam Characteristics from ''%s''\n',obj.beamProfile,fname);
+
+ case 'virtualGaussian'
+ fname = fullfile(obj.topasFolder,obj.infilenames.beam_virtualGaussian);
+ TOPAS_beamSetup = fileread(fname);
+ matRad_cfg.dispInfo('Reading ''%s'' Beam Characteristics from ''%s''\n',obj.beamProfile,fname);
+
+ case 'uniform'
+ fname = fullfile(obj.topasFolder,obj.infilenames.beam_uniform);
+ TOPAS_beamSetup = fileread(fname);
+ matRad_cfg.dispInfo('Reading ''%s'' Beam Characteristics from ''%s''\n',obj.beamProfile,fname);
+
+ otherwise
+ matRad_cfg.dispError('Beam Type ''%s'' not supported for photons',obj.beamProfile);
+
+ end
+
+ % Set variables for loop over beams
+ nBeamParticlesTotal = zeros(1,length(stf));
+ currentBixel = 1;
+ bixelNotMeetingParticleQuota = 0;
+ historyCount = zeros(1,length(stf));
+
+ for beamIx = 1:length(stf)
+
+ SAD = stf(beamIx).SAD;
+
+ if isPhoton
+ nozzleToAxisDistance = SAD;
+ sourceToNozzleDistance = 0;
+ else
+ nozzleToAxisDistance = baseData.nozzleToIso;
+ sourceToNozzleDistance = SAD - nozzleToAxisDistance;
+
+ %Selection of base data given the energies and focusIndex
+ if obj.useOrigBaseData
+ [~,ixTmp,~] = intersect([ baseData.machine.data.energy], [stf.ray.energy]);
+ for i = 1:length(ixTmp)
+ selectedData(i) = baseData.machine.data(ixTmp(i));
+ end
+ energies = [selectedData.energy];
+ else
+ selectedData = [];
+ focusIndex = baseData.selectedFocus(baseData.energyIndex);
+
+ scalarFields = {'NominalEnergy','EnergySpread','MeanEnergy'};
+
+ for i = 1:numel(focusIndex)
+ for field = scalarFields
+ baseData.monteCarloData(i).(field{1}) = ones(1,max(focusIndex))*baseData.monteCarloData(i).(field{1});
+ end
+ selectedData = [selectedData, structfun(@(x) x(focusIndex(i)),baseData.monteCarloData(i),'UniformOutput',false)];
+ end
+ energies = [selectedData.NominalEnergy];
+ end
+
+ %Get Range Shifters in field if present
+ allRays = [stf(beamIx).ray];
+ raShis = [allRays.rangeShifter];
+ [~,ix] = unique(cell2mat(squeeze(struct2cell(raShis))'),'rows');
+
+ raShis = raShis(ix);
+ ix = [raShis.ID] == 0;
+ raShis = raShis(~ix);
+
+ %Convert ID into readable string
+ for r = 1:numel(raShis)
+ if isnumeric(raShis(r).ID)
+ raShis(r).topasID = ['RangeShifter' num2str(raShis(r).ID)];
+ else
+ raShis(r).topasID = ['RangeShifter' raShis(r).ID];
+ end
+ end
+ end
+
+ %get beamlet properties for each bixel in the stf and write it into dataTOPAS
+ cutNumOfBixel = 0;
+
+ % Clear dataTOPAS from the previous beam
+ dataTOPAS = [];
+
+ %Loop over rays and then over spots on ray
+ for rayIx = 1:stf(beamIx).numOfRays
+ for bixelIx = 1:stf(beamIx).numOfBixelsPerRay(rayIx)
+
+ nCurrentParticles = nParticlesTotalBixel(currentBixel);
+
+ % check whether there are (enough) particles for beam delivery
+ if (nCurrentParticles>minParticlesBixel)
+
+ % collectBixelIdx(end+1) = bixelIx;
+ cutNumOfBixel = cutNumOfBixel + 1;
+ bixelEnergy = stf(beamIx).ray(rayIx).energy(bixelIx);
+
+ dataTOPAS(cutNumOfBixel).posX = stf(beamIx).ray(rayIx).rayPos_bev(3);
+ dataTOPAS(cutNumOfBixel).posY = stf(beamIx).ray(rayIx).rayPos_bev(1);
+
+ dataTOPAS(cutNumOfBixel).current = uint32(obj.fracHistories * nCurrentParticles / obj.numOfRuns);
+
+ if obj.pencilBeamScanning
+ % angleX corresponds to the rotation around the X axis necessary to move the spot in the Y direction
+ % angleY corresponds to the rotation around the Y' axis necessary to move the spot in the X direction
+ % note that Y' corresponds to the Y axis after the rotation of angleX around X axis
+ % note that Y translates to -Y for TOPAS
+ dataTOPAS(cutNumOfBixel).angleX = atan(dataTOPAS(cutNumOfBixel).posY / SAD);
+ dataTOPAS(cutNumOfBixel).angleY = atan(-dataTOPAS(cutNumOfBixel).posX ./ (SAD ./ cos(dataTOPAS(cutNumOfBixel).angleX)));
+ % Translate posX and posY to patient coordinates
+ dataTOPAS(cutNumOfBixel).posX = (dataTOPAS(cutNumOfBixel).posX / SAD)*(SAD-nozzleToAxisDistance);
+ dataTOPAS(cutNumOfBixel).posY = (dataTOPAS(cutNumOfBixel).posY / SAD)*(SAD-nozzleToAxisDistance);
+ end
+
+ switch obj.radiationMode
+ case {'protons','carbon','helium'}
+ [~,ixTmp,~] = intersect(energies, bixelEnergy);
+ if obj.useOrigBaseData
+ dataTOPAS(cutNumOfBixel).energy = selectedData(ixTmp).energy;
+ dataTOPAS(cutNumOfBixel).focusFWHM = selectedData(ixTmp).initFocus.SisFWHMAtIso(stf(beamIx).ray(rayIx).focusIx(bixelIx));
+
+ else
+ dataTOPAS(cutNumOfBixel).energy = selectedData(ixTmp).MeanEnergy;
+ dataTOPAS(cutNumOfBixel).nominalEnergy = selectedData(ixTmp).NominalEnergy;
+ dataTOPAS(cutNumOfBixel).energySpread = selectedData(ixTmp).EnergySpread;
+ dataTOPAS(cutNumOfBixel).spotSizeX = selectedData(ixTmp).SpotSize1x;
+ dataTOPAS(cutNumOfBixel).divergenceX = selectedData(ixTmp).Divergence1x;
+ dataTOPAS(cutNumOfBixel).correlationX = selectedData(ixTmp).Correlation1x;
+ dataTOPAS(cutNumOfBixel).spotSizeY = selectedData(ixTmp).SpotSize1y;
+ dataTOPAS(cutNumOfBixel).divergenceY = selectedData(ixTmp).Divergence1y;
+ dataTOPAS(cutNumOfBixel).correlationY = selectedData(ixTmp).Correlation1y;
+ dataTOPAS(cutNumOfBixel).focusFWHM = selectedData(ixTmp).FWHMatIso;
+ end
+ case 'photons'
+ dataTOPAS(cutNumOfBixel).energy = bixelEnergy;
+ dataTOPAS(cutNumOfBixel).energySpread = 0;
+ end
+
+ if obj.scorer.calcDij
+ % remember beam and bixel number
+ dataTOPAS(cutNumOfBixel).beam = beamIx;
+ dataTOPAS(cutNumOfBixel).ray = rayIx;
+ dataTOPAS(cutNumOfBixel).bixel = bixelIx;
+ dataTOPAS(cutNumOfBixel).totalBixel = currentBixel;
+ end
+
+ %Add RangeShifterState
+ if exist('raShis','var') && ~isempty(raShis)
+ raShiOut = zeros(1,length(raShis));
+ for r = 1:length(raShis)
+ if stf(beamIx).ray(rayIx).rangeShifter(bixelIx).ID == raShis(r).ID
+ raShiOut(r) = 0; %Range shifter is in beam path
+ else
+ raShiOut(r) = 1; %Range shifter is out of beam path / not used
+ end
+ end
+ dataTOPAS(cutNumOfBixel).raShiOut = raShiOut;
+ end
+
+ nBeamParticlesTotal(beamIx) = nBeamParticlesTotal(beamIx) + nCurrentParticles;
+
+
+ end
+
+ currentBixel = currentBixel + 1;
+
+ end
+ end
+
+ bixelNotMeetingParticleQuota = bixelNotMeetingParticleQuota + (stf(beamIx).totalNumOfBixels-cutNumOfBixel);
+
+ % discard data if the current has unphysical values
+ idx = find([dataTOPAS.current] < 1);
+ dataTOPAS(idx) = [];
+
+ % Safety check for empty beam (not allowed)
+ if isempty(dataTOPAS)
+ matRad_cfg.dispError('dataTOPAS of beam %i is empty.',beamIx);
+ else
+ cutNumOfBixel = length(dataTOPAS(:));
+ end
+
+ % Sort dataTOPAS according to energy
+ if length(dataTOPAS)>1 && ~issorted([dataTOPAS(:).energy])
+ [~,ixSorted] = sort([dataTOPAS(:).energy]);
+ dataTOPAS = dataTOPAS(ixSorted);
+ end
+
+ % Save adjusted beam histories
+ historyCount(beamIx) = uint32(obj.fracHistories * nBeamParticlesTotal(beamIx) / obj.numOfRuns);
+
+ if historyCount(beamIx) < cutNumOfBixel || cutNumOfBixel == 0
+ matRad_cfg.dispError('Insufficient number of histories!')
+ end
+
+ % Check if current has the set amount of histories
+ % If needed, adjust current to actual histories (by adding/subtracting from random rays)
+ while sum([dataTOPAS(:).current]) ~= historyCount(beamIx)
+ diff = sum([dataTOPAS.current]) - sum(historyCount(beamIx));
+ if matRad_cfg.isMatlab
+ [~,~,R] = histcounts(rand(abs(diff),1),cumsum([0;double(transpose([dataTOPAS(:).current]))./double(sum([dataTOPAS(:).current]))]));
+ else
+ [~,R] = histc(rand(abs(diff),1),cumsum([0;double(transpose([dataTOPAS(:).current]))./double(sum([dataTOPAS(:).current]))]));
+ end
+ idx = 1:length(dataTOPAS);
+ randIx = idx(R);
+
+ newCurr = num2cell(arrayfun(@plus,double([dataTOPAS(randIx).current]),-1*sign(diff)*ones(1,abs(diff))),1);
+ [dataTOPAS(randIx).current] = newCurr{:};
+ end
+
+ % Previous histories were set per run
+ historyCount(beamIx) = historyCount(beamIx) * obj.numOfRuns;
+
+ % Write TOPAS data base file
+ if isfield(ct,'currCtScen')
+ % 4D case
+ fieldSetupFileName = sprintf('beamSetup_%s_field%d_ct%d.txt',obj.label,beamIx,ct.currCtScen);
+ fileID = fopen(fullfile(obj.workingDir,fieldSetupFileName),'w');
+ obj.writeFieldHeader(fileID,ct.currCtScen);
+ else
+ fieldSetupFileName = sprintf('beamSetup_%s_field%d.txt',obj.label,beamIx);
+ fileID = fopen(fullfile(obj.workingDir,fieldSetupFileName),'w');
+ obj.writeFieldHeader(fileID);
+ end
+
+ % NozzleAxialDistance
+ if isPhoton
+ fprintf(fileID,'d:Ge/Nozzle/TransZ = -%f mm\n', stf(beamIx).SCD+40); %Phasespace hardcorded infront of MLC at SSD 46 cm
+ else
+ fprintf(fileID,'d:Ge/Nozzle/TransZ = -%f mm\n', nozzleToAxisDistance);
+ end
+
+ if obj.pencilBeamScanning
+ fprintf(fileID,'d:Ge/Nozzle/RotX = Tf/Beam/AngleX/Value rad\n');
+ fprintf(fileID,'d:Ge/Nozzle/RotY = Tf/Beam/AngleY/Value rad\n');
+ fprintf(fileID,'d:Ge/Nozzle/RotZ = 0.0 rad\n\n');
+ end
+
+ %Write modality specific info
+ switch stf(beamIx).radiationMode
+ case 'protons'
+ fprintf(fileID,'s:Sim/ParticleName = "proton"\n');
+ fprintf(fileID,'u:Sim/ParticleMass = 1.0\n');
+
+ particleA = 1;
+ % particleZ = 1;
+
+ modules = obj.modules_protons;
+
+ case 'carbon'
+ fprintf(fileID,'s:Sim/ParticleName = "GenericIon(6,12)"\n');
+ fprintf(fileID,'u:Sim/ParticleMass = 12.0\n');
+
+ particleA = 12;
+ % particleZ = 6;
+
+ modules = obj.modules_GenericIon;
+
+ case 'helium'
+ fprintf(fileID,'s:Sim/ParticleName = "GenericIon(2,4)"\n');
+ fprintf(fileID,'u:Sim/ParticleMass = 4.0\n');
+
+ particleA = 4;
+ % particleZ = 2;
+
+ modules = obj.modules_GenericIon;
+
+ case 'photons'
+ fprintf(fileID,'s:Sim/ParticleName = "gamma"\n');
+ fprintf(fileID,'u:Sim/ParticleMass = 0\n');
+
+ particleA = 0;
+ % particleZ = 0;
+
+ modules = obj.modules_photons;
+
+ otherwise
+ matRad_cfg.dispError('Invalid radiation mode %s!',stf.radiationMode)
+ end
+
+ if obj.pencilBeamScanning
+ % Write couch and gantry angles
+ fprintf(fileID,'d:Sim/GantryAngle = %f deg\n',stf(beamIx).gantryAngle);
+ fprintf(fileID,'d:Sim/CouchAngle = %f deg\n',stf(beamIx).couchAngle);
+
+ % Write time feature (TOPAS uses time features to loop through bixels)
+ fprintf(fileID,'d:Tf/TimelineStart = 0. ms\n');
+ fprintf(fileID,'d:Tf/TimelineEnd = %i ms\n', 10 * cutNumOfBixel);
+ fprintf(fileID,'i:Tf/NumberOfSequentialTimes = %i\n', cutNumOfBixel);
+ fprintf(fileID,'dv:Tf/Beam/Spot/Times = %i ', cutNumOfBixel);
+ fprintf(fileID,'%i ',linspace(10,cutNumOfBixel*10,cutNumOfBixel));
+ fprintf(fileID,' ms\n');
+ %fprintf(fileID,'uv:Tf/Beam/Spot/Values = %i %s\n',cutNumOfBixel,num2str(collectBixelIdx));
+
+ % Write energySpectrum if available and flag is set
+ if ~isPhoton && isfield(baseData.machine.data,'energySpectrum') && obj.useEnergySpectrum
+ matRad_cfg.dispInfo('Beam energy spectrum available\n');
+ energySpectrum = [baseData.machine.data(:).energySpectrum];
+ nbSpectrumPoints = length(energySpectrum(1).energy_MeVpN);
+
+ % Get energy indices of the current energies in the baseData
+ [~,energyIx] = ismember([dataTOPAS.nominalEnergy],[baseData.machine.data.energy]);
+
+ fprintf(fileID,'s:So/PencilBeam/BeamEnergySpectrumType = "Continuous"\n');
+ fprintf(fileID,'dv:So/PencilBeam/BeamEnergySpectrumValues = %d %s MeV\n',nbSpectrumPoints,strtrim(sprintf('Tf/Beam/EnergySpectrum/Energy/Point%03d/Value ',1:nbSpectrumPoints)));
+ fprintf(fileID,'uv:So/PencilBeam/BeamEnergySpectrumWeights = %d %s\n',nbSpectrumPoints,strtrim(sprintf('Tf/Beam/EnergySpectrum/Weight/Point%03d/Value ',1:nbSpectrumPoints)));
+ points_energy = reshape([energySpectrum(energyIx).energy_MeVpN],[],length(energyIx));
+ points_weight = reshape([energySpectrum(energyIx).weight],[],length(energyIx));
+ for spectrumPoint=1:nbSpectrumPoints
+ fprintf(fileID,'s:Tf/Beam/EnergySpectrum/Energy/Point%03d/Function = "Step"\n',spectrumPoint);
+ fprintf(fileID,'dv:Tf/Beam/EnergySpectrum/Energy/Point%03d/Times = Tf/Beam/Spot/Times ms\n',spectrumPoint);
+ fprintf(fileID,'dv:Tf/Beam/EnergySpectrum/Energy/Point%03d/Values = %d %s MeV\n',spectrumPoint,cutNumOfBixel,strtrim(sprintf('%f ',particleA*points_energy(spectrumPoint,:))));
+ fprintf(fileID,'s:Tf/Beam/EnergySpectrum/Weight/Point%03d/Function = "Step"\n',spectrumPoint);
+ fprintf(fileID,'dv:Tf/Beam/EnergySpectrum/Weight/Point%03d/Times = Tf/Beam/Spot/Times ms\n',spectrumPoint);
+ fprintf(fileID,'uv:Tf/Beam/EnergySpectrum/Weight/Point%03d/Values = %d %s\n',spectrumPoint,cutNumOfBixel,strtrim(sprintf('%f ',points_weight(spectrumPoint,:))));
+ end
+ end
+
+ % Write amount of energies in plan
+ fprintf(fileID,'s:Tf/Beam/Energy/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/Energy/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'dv:Tf/Beam/Energy/Values = %i ', cutNumOfBixel);
+
+ % Write actual energies
+ % WARNING: Transform total energy with atomic number
+ fprintf(fileID,'%f ',particleA*[dataTOPAS.energy]);
+ fprintf(fileID,' MeV\n');
+ end
+
+ % Write beam profile
+ switch obj.beamProfile
+ case 'biGaussian'
+ % Write energy spread
+ fprintf(fileID,'s:Tf/Beam/EnergySpread/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/EnergySpread/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'uv:Tf/Beam/EnergySpread/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.energySpread]);
+ fprintf(fileID,'\n');
+
+ % Write parameters for first dimension
+ fprintf(fileID,'s:Tf/Beam/SigmaX/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/SigmaX/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'dv:Tf/Beam/SigmaX/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.spotSizeX]);
+ fprintf(fileID,' mm\n');
+ fprintf(fileID,'s:Tf/Beam/SigmaXPrime/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/SigmaXPrime/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'uv:Tf/Beam/SigmaXPrime/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.divergenceX]);
+ fprintf(fileID,'\n');
+ fprintf(fileID,'s:Tf/Beam/CorrelationX/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/CorrelationX/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'uv:Tf/Beam/CorrelationX/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.correlationX]);
+ fprintf(fileID,'\n');
+
+ % Write parameters for second dimension (profile is uniform)
+ fprintf(fileID,'s:Tf/Beam/SigmaY/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/SigmaY/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'dv:Tf/Beam/SigmaY/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.spotSizeY]);
+ fprintf(fileID,' mm\n');
+ fprintf(fileID,'s:Tf/Beam/SigmaYPrime/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/SigmaYPrime/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'uv:Tf/Beam/SigmaYPrime/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.divergenceY]);
+ fprintf(fileID,'\n');
+ fprintf(fileID,'s:Tf/Beam/CorrelationY/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/CorrelationY/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'uv:Tf/Beam/CorrelationY/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.correlationY]);
+ fprintf(fileID,'\n');
+
+ case 'simple'
+ fprintf(fileID,'s:Tf/Beam/FocusFWHM/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/FocusFWHM/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'dv:Tf/Beam/FocusFWHM/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.focusFWHM]);
+ fprintf(fileID,' mm\n');
+
+ case 'virtualGaussian'
+ fprintf(fileID,'s:Tf/Beam/EnergySpread/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/EnergySpread/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'uv:Tf/Beam/EnergySpread/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,num2str([dataTOPAS.energySpread]));
+ fprintf(fileID,'\n');
+
+ if isfield([stf.ray], 'collimation')
+ % Use field width for now
+ fprintf(fileID,'d:So/PencilBeam/BeamPositionSpreadX = %d mm\n', stf(1).ray.collimation.fieldWidth);
+ fprintf(fileID,'d:So/PencilBeam/BeamPositionSpreadY = %d mm\n', stf(1).ray.collimation.fieldWidth);
+ else
+ % Set some default value
+ fprintf(fileID,'d:So/PencilBeam/BeamPositionSpreadX = %d mm\n', 30);
+ fprintf(fileID,'d:So/PencilBeam/BeamPositionSpreadY = %d mm\n', 30);
+ end
+
+ case 'uniform'
+ fprintf(fileID,'s:Tf/Beam/EnergySpread/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/EnergySpread/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'uv:Tf/Beam/EnergySpread/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,num2str([dataTOPAS.energySpread]));
+ fprintf(fileID,'\n');
+
+ if isfield([stf.ray],'collimation')
+ % Use field width for now
+ fprintf(fileID,'d:So/PencilBeam/BeamPositionCutoffX = %d mm\n', stf(1).ray.collimation.fieldWidth/2);
+ fprintf(fileID,'d:So/PencilBeam/BeamPositionCutoffY = %d mm\n', stf(1).ray.collimation.fieldWidth/2);
+ else
+ % Set some default value
+ fprintf(fileID,'d:So/PencilBeam/BeamPositionCutoffX = %d mm\n', 15);
+ fprintf(fileID,'d:So/PencilBeam/BeamPositionCutoffY = %d mm\n', 15);
+ end
+
+ case 'phasespace'
+
+ fprintf(fileID,'d:Sim/GantryAngle = %f deg\n',stf(beamIx).gantryAngle); %just one beam angle for now
+ fprintf(fileID,'d:Sim/CouchAngle = %f deg\n',stf(beamIx).couchAngle);
+ % Here the phasespace file is loaded and referenced in the beamSetup file
+ if strcmp(obj.externalCalculation,'write')
+ matRad_cfg.dispWarning(['External calculation and phaseSpace selected, manually place ' obj.infilenames.phaseSpaceSourcePhotons '.header and ' obj.infilenames.phaseSpaceSourcePhotons '.phsp into your simulation directory.']);
+ else
+ if length(dir([obj.topasFolder filesep 'beamSetup' filesep 'phasespace' filesep obj.infilenames.phaseSpaceSourcePhotons '*'])) < 2
+ matRad_cfg.dispError([phaseSpaceFileName ' header or phsp file could not be found in beamSetup/phasespace folder.']);
+ end
+ end
+ %phasespaceStr = ['..' filesep 'beamSetup' filesep 'phasespace' filesep phaseSpaceFileName];
+ %&phasespaceStr = replace(phasespaceStr, '\', '/');
+ fprintf(fileID,'s:So/Phasespace/PhaseSpaceFileName = "%s"\n', obj.infilenames.phaseSpaceSourcePhotons );
+
+ end
+
+ % Write spot angles
+ if obj.pencilBeamScanning
+ fprintf(fileID,'s:Tf/Beam/AngleX/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/AngleX/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'dv:Tf/Beam/AngleX/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.angleX]);
+ fprintf(fileID,' rad\n');
+ fprintf(fileID,'s:Tf/Beam/AngleY/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/AngleY/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'dv:Tf/Beam/AngleY/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.angleY]);
+ fprintf(fileID,' rad\n');
+
+
+ % Write spot positions
+ fprintf(fileID,'s:Tf/Beam/PosX/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/PosX/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'dv:Tf/Beam/PosX/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.posX]);
+ fprintf(fileID,' mm\n');
+ fprintf(fileID,'s:Tf/Beam/PosY/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/PosY/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'dv:Tf/Beam/PosY/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.posY]);
+ fprintf(fileID,' mm\n');
+
+ % Write spot current (translates to the amount of particles in a spot)
+ fprintf(fileID,'s:Tf/Beam/Current/Function = "Step"\n');
+ fprintf(fileID,'dv:Tf/Beam/Current/Times = Tf/Beam/Spot/Times ms\n');
+ fprintf(fileID,'iv:Tf/Beam/Current/Values = %i ', cutNumOfBixel);
+ fprintf(fileID,'%i ',[dataTOPAS.current]);
+ fprintf(fileID,'\n\n');
+
+ % Range shifter in/out
+ if ~isPhoton && ~isempty(raShis)
+ fprintf(fileID,'#Range Shifter States:\n');
+ for r = 1:numel(raShis)
+ fprintf(fileID,'s:Tf/Beam/%sOut/Function = "Step"\n',raShis(r).topasID);
+ fprintf(fileID,'dv:Tf/Beam/%sOut/Times = Tf/Beam/Spot/Times ms\n',raShis(r).topasID);
+ fprintf(fileID,'uv:Tf/Beam/%sOut/Values = %i ', raShis(r).topasID, cutNumOfBixel);
+ fprintf(fileID,'%f ',[dataTOPAS.raShiOut]);
+ fprintf(fileID,'\n\n');
+ end
+
+ % Range Shifter Definition
+ for r = 1:numel(raShis)
+ obj.writeRangeShifter(fileID,raShis(r),sourceToNozzleDistance);
+ end
+ end
+
+
+ end
+
+ % Write previously beam profile
+ fprintf(fileID,'%s\n',TOPAS_beamSetup);
+
+ % Write MLC if available
+ if isfield(stf(beamIx).ray, 'shapes')
+ SCD = stf(beamIx).SCD;
+ fname = fullfile(obj.topasFolder,obj.infilenames.beam_mlc);
+ TOPAS_mlcSetup = fileread(fname);
+ fprintf(fileID,'%s\n',TOPAS_mlcSetup);
+ [numOfLeaves,leafTimes]=size([stf(beamIx).ray.shapes(:).leftLeafPos]); %there are #numOfLeaves leaves and #leafTimes times/shapes
+ leftLeafPos = [stf(beamIx).ray.shapes(:).leftLeafPos]*SCD./SAD;
+ rightLeafPos = [stf(beamIx).ray.shapes(:).rightLeafPos]*SCD./SAD;
+ % Set MLC paramters as in TOPAS example file https://topas.readthedocs.io/en/latest/parameters/geometry/specialized.html#multi-leaf-collimator
+ fprintf(fileID,'d:Sim/Ge/MultiLeafCollimatorA/TransZ = %f cm\n', 4);
+ fprintf(fileID,'d:Ge/MultiLeafCollimatorA/MaximumLeafOpen = %f cm\n',15);
+ fprintf(fileID,'d:Ge/MultiLeafCollimatorA/Thickness = %f cm\n',8);
+ fprintf(fileID,'d:Ge/MultiLeafCollimatorA/Length = %f cm\n',15);
+ fprintf(fileID,'dv:Ge/MultiLeafCollimatorA/Widths = %i ', numOfLeaves+2);
+ fprintf(fileID, '%f ', [200, stf(1).ray.collimation.leafWidth*ones(1,numOfLeaves)*SCD./SAD , 200]);
+ fprintf(fileID,' mm \n');
+ fprintf(fileID,'dv:Ge/MultiLeafCollimatorA/XPlusLeavesOpen = %i ',numOfLeaves+2);
+ for i = 0:numOfLeaves+1
+ fprintf( fileID,'Tf/LeafXPlus%i/Value ',i);
+ end
+ fprintf(fileID,'mm \n');
+ fprintf(fileID,'dv:Ge/MultiLeafCollimatorA/XMinusLeavesOpen = %i ',numOfLeaves+2);
+ for i = 0:numOfLeaves+1
+ fprintf( fileID,'Tf/LeafXMinus%i/Value ',i);
+ end
+ fprintf(fileID,'mm \n');
+
+ %initilization of time features
+ fprintf(fileID,'d:Tf/TimelineStart = 0 ms\n');
+ fprintf(fileID,'d:Tf/TimelineEnd = %f ms\n',leafTimes*10);
+ fprintf(fileID,'i:Tf/NumberOfSequentialTimes = %i \n',leafTimes);
+
+ for i = 1:numOfLeaves
+ fprintf(fileID,'s:Tf/LeafXMinus%i/Function = "Step"\n',i);
+ fprintf(fileID,'dv:Tf/LeafXMinus%i/Times = %i ', i,leafTimes);
+ fprintf(fileID,'%i ', [1:leafTimes]*10);
+ fprintf(fileID,' ms\n');
+ fprintf(fileID,'dv:Tf/LeafXMinus%i/Values = %i ', i,leafTimes);
+ fprintf(fileID,'%f ', leftLeafPos(i,:));
+ fprintf(fileID,' mm\n\n');
+
+ fprintf(fileID,'s:Tf/LeafXPlus%i/Function = "Step"\n',i);
+ fprintf(fileID,'dv:Tf/LeafXPlus%i/Times = %i ',i,leafTimes);
+ fprintf(fileID,'%i ',[1:leafTimes]*10);
+ fprintf(fileID,' ms\n');
+ fprintf(fileID,'dv:Tf/LeafXPlus%i/Values = %i ', i,leafTimes);
+ fprintf(fileID,'%f ', rightLeafPos(i,:));
+ fprintf(fileID,' mm\n\n');
+ end
+ %Add aditional Leaf at the top and bottom to catch
+ %scattering
+ for i = [0,numOfLeaves+1]
+ fprintf(fileID,'s:Tf/LeafXMinus%i/Function = "Step"\n',i);
+ fprintf(fileID,'dv:Tf/LeafXMinus%i/Times = %i ', i,leafTimes);
+ fprintf(fileID,'%i ',[1:leafTimes]*10);
+ fprintf(fileID,' ms\n');
+ fprintf(fileID,'dv:Tf/LeafXMinus%i/Values = %i ', i,leafTimes);
+ fprintf(fileID,'%f ', zeros(size([1:leafTimes])));
+ fprintf(fileID,' mm\n\n');
+
+ fprintf(fileID,'s:Tf/LeafXPlus%i/Function = "Step"\n',i);
+ fprintf(fileID,'dv:Tf/LeafXPlus%i/Times = %i ',i,leafTimes);
+ fprintf(fileID,'%i ',[1:leafTimes]*10);
+ fprintf(fileID,' ms\n');
+ fprintf(fileID,'dv:Tf/LeafXPlus%i/Values = %i ', i,leafTimes);
+ fprintf(fileID,'%f ', zeros(size([1:leafTimes])));
+ fprintf(fileID,' mm\n\n');
+ end
+
+ fprintf(fileID, 's:Tf/Phasespace/NumberOfHistoriesInRun/Function = "Step" \n');
+ fprintf(fileID, 'dv:Tf/Phasespace/NumberOfHistoriesInRun/Times = %i ', leafTimes);
+ fprintf(fileID,'%i ',[1:leafTimes]*10);
+ fprintf(fileID,' ms\n');
+ fprintf(fileID, 'iv:Tf/Phasespace/NumberOfHistoriesInRun/Values = %i ', leafTimes);
+ fprintf(fileID,'%i ',[dataTOPAS(:).current]);
+ fprintf(fileID,' \n');
+
+ end
+
+ % Translate patient according to beam isocenter
+ fprintf(fileID,'d:Ge/Patient/TransX = %f mm\n',0.5*ct.resolution.x*(ct.cubeDim(2)+1)-stf(beamIx).isoCenter(1));
+ fprintf(fileID,'d:Ge/Patient/TransY = %f mm\n',0.5*ct.resolution.y*(ct.cubeDim(1)+1)-stf(beamIx).isoCenter(2));
+ fprintf(fileID,'d:Ge/Patient/TransZ = %f mm\n',0.5*ct.resolution.z*(ct.cubeDim(3)+1)-stf(beamIx).isoCenter(3));
+ fprintf(fileID,'d:Ge/Patient/RotX=0. deg\n');
+ fprintf(fileID,'d:Ge/Patient/RotY=0. deg\n');
+ fprintf(fileID,'d:Ge/Patient/RotZ=0. deg\n');
+
+ % Load topas modules depending on the particle type
+ fprintf(fileID,'\n# MODULES\n');
+ moduleString = cellfun(@(s) sprintf('"%s"',s),modules,'UniformOutput',false);
+ fprintf(fileID,'sv:Ph/Default/Modules = %d %s\n',length(modules),strjoin(moduleString,' '));
+
+ fclose(fileID);
+ % Write run scripts for TOPAS
+ for runIx = 1:obj.numOfRuns
+ if isfield(ct,'currCtScen')
+ runFileName = sprintf('%s_field%d_ct%d_run%d.txt',obj.label,beamIx,ct.currCtScen,runIx);
+ else
+ runFileName = sprintf('%s_field%d_run%d.txt',obj.label,beamIx,runIx);
+ end
+ fileID = fopen(fullfile(obj.workingDir,runFileName),'w');
+
+ % Write header
+ if isfield(ct,'currCtScen')
+ obj.writeRunHeader(fileID,beamIx,runIx,ct.currCtScen);
+ else
+ obj.writeRunHeader(fileID,beamIx,runIx);
+ end
+
+ % Include path to beamSetup file
+ fprintf(fileID,'includeFile = ./%s\n',fieldSetupFileName);
+
+ % Write lines from scorer files
+ obj.writeScorers(fileID);
+
+ % Write dij-related config lines
+ % TODO: move this to github issue/todo -> We should discuss here if that's something that has to be available for photons as well
+ if ~strcmp(obj.radiationMode,'photons')
+ if obj.scorer.calcDij
+ fprintf(fileID,'\n');
+ fprintf(fileID,'#-- time feature splitting for dij calculation\n');
+ fprintf(fileID,'s:Tf/ImageName/Function = "Step"\n');
+ % create time feature scorer and save with original rays and bixel names
+ imageName = ['sv:Tf/ImageName/Values = ',num2str(cutNumOfBixel),cell2mat(strcat(strcat(' "ray',strsplit(num2str([dataTOPAS.ray]))),strcat('_bixel',strsplit(num2str([dataTOPAS.bixel])),'"')))];
+ fprintf(fileID,'%s\n',imageName);
+ fprintf(fileID,'dv:Tf/ImageName/Times = Tf/Beam/Spot/Times ms\n');
+ end
+ end
+
+ fclose(fileID);
+ end
+ end
+
+ if bixelNotMeetingParticleQuota ~= 0
+ matRad_cfg.dispWarning([num2str(bixelNotMeetingParticleQuota) ' bixels were discarded due to particle threshold.'])
+ end
+
+ % Bookkeeping
+ obj.MCparam.nbParticlesTotal = sum(nBeamParticlesTotal);
+ obj.MCparam.nbHistoriesTotal = sum(historyCount);
+ obj.MCparam.nbParticlesField = nBeamParticlesTotal;
+ obj.MCparam.nbHistoriesField = historyCount;
+ end
+
+ function writePatient(obj,ct,pln)
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % matRad export CT RSP data for TOPAS simulation
+ %
+ % call
+ % obj.writePatient(ct, path, material)
+ %
+ % input
+ % ct: ct cube
+ % pln: plan structure containing doseCalc classes
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ % the image cube contains the indexing of materials
+ % since at the moment TOPAS does not support ushort
+ % the materials should have indexes between 0 and 32767
+ % therefore, the maximum length of the vector is 32768
+
+ matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+ medium = obj.rsp_basematerial;
+ if isequal(obj.arrayOrdering,'C')
+ if matRad_cfg.logLevel > 2
+ matRad_cfg.dispInfo('Exporting cube in C ordering...\n')
+ end
+ permutation = [3 1 2];
+ else
+ if matRad_cfg.logLevel > 2
+ matRad_cfg.dispInfo('Exporting cube in FORTRAN ordering...\n')
+ end
+ permutation = [2 1 3];
+ end
+
+ % Bookkeeping
+ obj.MCparam.imageCubeOrdering = obj.arrayOrdering;
+ obj.MCparam.imageCubeConversionType = obj.materialConverter.mode;
+ obj.MCparam.imageCubeFile = obj.outfilenames.patientCube;
+ obj.MCparam.imageCubeDim = ct.cubeDim;
+ obj.MCparam.imageVoxelDimension = ct.resolution;
+
+ % Save filenames
+ paramFile = obj.outfilenames.patientParam;
+ dataFile = obj.outfilenames.patientCube;
+
+ % Add ctScen number to filenames
+ if isfield(ct,'currCtScen')
+ ctScen = ct.currCtScen;
+ paramFile = strsplit(paramFile,'.');
+ paramFile = strjoin(paramFile,[num2str(ct.currCtScen) '.']);
+
+ dataFile = strsplit(dataFile,'.');
+ dataFile = strjoin(dataFile,[num2str(ct.currCtScen) '.']);
+ else
+ ctScen = 1;
+ end
+
+ % Open file to write in data
+ outfile = fullfile(obj.workingDir, paramFile);
+ matRad_cfg.dispInfo('Writing data to %s\n',outfile)
+ fID = fopen(outfile,'w+');
+
+ % Write material converter
+ switch obj.materialConverter.mode
+ case 'RSP' % Relative stopping power converter
+ rspHlut = matRad_loadHLUT(ct,obj.radiationMode);
+ min_HU = rspHlut(1,1);
+ max_HU = rspHlut(end,1);
+
+ huCube = int32(permute(ct.cubeHU{ctScen},permutation)); % X,Y,Z ordering
+ huCube(huCube < min_HU) = min_HU;
+ huCube(huCube > max_HU) = max_HU;
+
+ unique_hu = unique(huCube(:));
+ unique_rsp = matRad_interp1(rspHlut(:,1),rspHlut(:,2),double(unique_hu));
+ fbase = fopen(['materialConverter/definedMaterials/' medium '.txt'],'r');
+ while ~feof(fbase)
+ strLine = fgets(fbase); %# read line by line
+ fprintf(fID,'%s',strLine);
+ end
+ fclose(fbase);
+
+ unique_materials = cell(1,length(unique_hu));
+ for ix=1:length(unique_hu)
+ unique_materials{ix} = strrep(['Material_HU_',num2str(unique_hu(ix))],'-','m');
+ fprintf(fID,'s:Ma/%s/BaseMaterial = "%s"\n',unique_materials{ix},medium);
+ fprintf(fID,'d:Ma/%s/Density = %f g/cm3\n',unique_materials{ix},unique_rsp(ix));
+ end
+
+ fprintf(fID,'s:Ge/Patient/Parent="World"\n');
+ fprintf(fID,'s:Ge/Patient/Type = "TsImageCube"\n');
+ fprintf(fID,'s:Ge/Patient/InputDirectory = "./"\n');
+ fprintf(fID,'s:Ge/Patient/InputFile = "%s"\n',dataFile);
+ fprintf(fID,'s:Ge/Patient/ImagingtoMaterialConverter = "MaterialTagNumber"\n');
+ fprintf(fID,'i:Ge/Patient/NumberOfVoxelsX = %d\n',ct.cubeDim(2));
+ fprintf(fID,'i:Ge/Patient/NumberOfVoxelsY = %d\n',ct.cubeDim(1));
+ fprintf(fID,'iv:Ge/Patient/NumberOfVoxelsZ = 1 %d\n',ct.cubeDim(3));
+ fprintf(fID,'d:Ge/Patient/VoxelSizeX = %.3f mm\n',ct.resolution.x);
+ fprintf(fID,'d:Ge/Patient/VoxelSizeY = %.3f mm\n',ct.resolution.y);
+ fprintf(fID,'dv:Ge/Patient/VoxelSizeZ = 1 %.3f mm\n',ct.resolution.z);
+ fprintf(fID,'s:Ge/Patient/DataType = "SHORT"\n');
+ fprintf(fID,'iv:Ge/Patient/MaterialTagNumbers = %d ',length(unique_hu));
+ fprintf(fID,num2str(unique_hu','%d '));
+ fprintf(fID,'\n');
+ fprintf(fID,'sv:Ge/Patient/MaterialNames = %d ',length(unique_hu));
+ fprintf(fID,'"%s"',strjoin(unique_materials,'" "'));
+ fprintf(fID,'\n');
+ fclose(fID);
+
+ % write data
+ fID = fopen(fullfile(obj.workingDir, dataFile),'w');
+ fwrite(fID,huCube,'short');
+ fclose(fID);
+ cube = huCube;
+
+
+ case 'HUToWaterSchneider' % Schneider converter
+ rspHlut = matRad_loadHLUT(ct,obj.radiationMode);
+
+ try
+ % Write Schneider Converter
+ if ~obj.materialConverter.loadConverterFromFile
+ % define density correction
+ matRad_cfg.dispInfo('TOPAS: Writing density correction\n');
+ switch obj.materialConverter.densityCorrection
+ case 'rspHLUT'
+ densityCorrection.density = [];
+ for i = 1:size(rspHlut,1)-1
+ startVal = rspHlut(i,1);
+ endVal = rspHlut(i+1,1);
+ range = startVal:1:endVal-1;
+ densityCorrection.density(end+1:end+numel(range)) = matRad_interp1(rspHlut(:,1),rspHlut(:,2),range);
+ end
+ densityCorrection.density(end+1) = rspHlut(end,2); %add last missing value
+ densityCorrection.boundaries = [rspHlut(1,1) numel(densityCorrection.density)-abs(rspHlut(1,1))];
+
+ case {'Schneider_TOPAS','Schneider_matRad'}
+ fname = fullfile(obj.topasFolder,filesep,obj.converterFolder,filesep,obj.infilenames.(['matConv_Schneider_densityCorr_',obj.materialConverter.densityCorrection]));
+ densityFile = fopen(fname);
+ densityCorrection.density = fscanf(densityFile,'%f');
+ fclose(densityFile);
+ densityCorrection.boundaries = [-1000 numel(densityCorrection.density)-1000];
+
+ end
+
+ % define additional density sections
+ switch obj.materialConverter.addSection
+ case 'lung'
+ addSection = [0.00012 1.05];
+ otherwise
+ addSection = [];
+ end
+ if exist('addSection','var') && ~isempty(addSection)
+ densityCorrection.density(end+1:end+numel(addSection)) = addSection;
+ densityCorrection.boundaries(end+1) = densityCorrection.boundaries(end)+numel(addSection);
+ end
+ % define Hounsfield Unit Sections
+ switch obj.materialConverter.HUSection
+ case 'default'
+ densityCorrection.unitSections = [densityCorrection.boundaries];
+ densityCorrection.offset = 1;
+ densityCorrection.factor = 0;
+ densityCorrection.factorOffset = -rspHlut(1,1);
+
+ case 'advanced'
+ densityCorrection.offset = [0.00121 1.018 1.03 1.003 1.017 2.201];
+ densityCorrection.factor = [0.001029700665188 0.000893 0 0.001169 0.000592 0.0005];
+ densityCorrection.factorOffset = [1000 0 1000 0 0 -2000];
+
+ if isfield(obj.materialConverter,'addTitanium') && obj.materialConverter.addTitanium %Titanium independent of set hounsfield unit!
+ densityCorrection.density(end+1) = 1.00275;
+ densityCorrection.boundaries(end+1) = densityCorrection.boundaries(end)+1;
+ densityCorrection.offset(end+1) = 4.54;
+ densityCorrection.factor(end+1) = 0;
+ densityCorrection.factorOffset(end+1) = 0;
+ end
+
+ densityCorrection.unitSections = [densityCorrection.boundaries(1) -98 15 23 101 2001 densityCorrection.boundaries(2:end)];
+ end
+ for i = numel(densityCorrection.offset)+1:numel(densityCorrection.unitSections)-1
+ densityCorrection.offset(i) = 1;
+ densityCorrection.factor(i) = 0;
+ densityCorrection.factorOffset(i) = 0;
+ end
+
+ % write density correction
+ fprintf(fID,'# -- Density correction\n');
+ fprintf(fID,['dv:Ge/Patient/DensityCorrection = %i',repmat(' %.6g',1,numel(densityCorrection.density)),' g/cm3\n'],numel(densityCorrection.density),densityCorrection.density);
+ fprintf(fID,['iv:Ge/Patient/SchneiderHounsfieldUnitSections = %i',repmat(' %g',1,numel(densityCorrection.unitSections)),'\n'],numel(densityCorrection.unitSections),densityCorrection.unitSections);
+ fprintf(fID,['uv:Ge/Patient/SchneiderDensityOffset = %i',repmat(' %g',1,numel(densityCorrection.offset)),'\n'],numel(densityCorrection.offset),densityCorrection.offset);
+ % this is needed for a custom fprintf format which formats integers i to 'i.' and floats without trailing zeros
+ % TODO: check whether this can be removed -> this is potentially not necessary but was done to mimick the original TOPAS Schneider converter file
+ TOPASisFloat = mod(densityCorrection.factor,1)==0;
+ fprintf(fID,['uv:Ge/Patient/SchneiderDensityFactor = %i ',strjoin(cellstr(char('%1.01f '.*TOPASisFloat' + '%1.15g '.*~TOPASisFloat'))),'\n'],numel(densityCorrection.factor),densityCorrection.factor);
+ TOPASisFloat = mod(densityCorrection.factorOffset,1)==0;
+ fprintf(fID,['uv:Ge/Patient/SchneiderDensityFactorOffset = %i ',strjoin(cellstr(char('%1.01f '.*TOPASisFloat' + '%1.15g '.*~TOPASisFloat'))),'\n'],numel(densityCorrection.factorOffset),densityCorrection.factorOffset);
+ % fprintf(fID,'uv:Ge/Patient/SchneiderDensityFactor = 8 0.001029700665188 0.000893 0.0 0.001169 0.000592 0.0005 0.0 0.0\n');
+ % fprintf(fID,'uv:Ge/Patient/SchneiderDensityFactorOffset = 8 1000. 0. 1000. 0. 0. -2000. 0. 0.0\n\n');
+
+ % define HU to material sections
+ matRad_cfg.dispInfo('TOPAS: Writing HU to material sections\n');
+ switch obj.materialConverter.HUToMaterial
+ case 'default'
+ HUToMaterial.sections = rspHlut(2,1);
+ case 'MCsquare'
+ HUToMaterial.sections = [-1000 -950 -120 -82 -52 -22 8 19 80 120 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500];
+ case 'advanced'
+ HUToMaterial.sections = [-950 -120 -83 -53 -23 7 18 80 120 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500];
+ end
+ HUToMaterial.sections = [densityCorrection.boundaries(1) HUToMaterial.sections densityCorrection.boundaries(2:end)];
+ % write HU to material sections
+ % fprintf(fID,'i:Ge/Patient/MinImagingValue = %d\n',densityCorrection.boundaries(1));
+ fprintf(fID,['iv:Ge/Patient/SchneiderHUToMaterialSections = %i ',repmat('%d ',1,numel(HUToMaterial.sections)),'\n\n'],numel(HUToMaterial.sections),HUToMaterial.sections);
+ % load defined material based on materialConverter.HUToMaterial
+
+ fname = fullfile(obj.topasFolder,filesep,obj.converterFolder,filesep,obj.infilenames.matConv_Schneider_definedMaterials.(obj.materialConverter.HUToMaterial));
+ materials = strsplit(fileread(fname),'\n')';
+ switch obj.materialConverter.HUToMaterial
+ case 'default'
+ fprintf(fID,'%s \n',materials{1:end-1});
+ ExcitationEnergies = str2double(strsplit(materials{end}(strfind(materials{end},'=')+4:end-3)));
+ if ~isempty(strfind(lower(obj.materialConverter.addSection),lower('lung')))
+ fprintf(fID,'uv:Ge/Patient/SchneiderMaterialsWeight%i = 5 0.10404040 0.75656566 0.03131313 0.10606061 0.00202020\n',length(materials)-2);
+ ExcitationEnergies = [ExcitationEnergies' 75.3];
+ end
+ fprintf(fID,['dv:Ge/Patient/SchneiderMaterialMeanExcitationEnergy = %i',repmat(' %.6g',1,numel(ExcitationEnergies)),' eV\n'],numel(ExcitationEnergies),ExcitationEnergies);
+ case 'advanced'
+ fprintf(fID,'\n%s\n',materials{:});
+ case 'MCsquare'
+ fprintf(fID,'\n%s\n',materials{:});
+ end
+
+ switch obj.materialConverter.HUToMaterial
+ case 'advanced'
+ counter = 25;
+ if isfield(obj.materialConverter,'addTitanium') && obj.materialConverter.addTitanium
+ fprintf(fID,'uv:Ge/Patient/SchneiderMaterialsWeight%i = 15 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0',counter);
+ counter = counter + 1;
+ end
+ % fprintf(fID,'uv:Ge/Patient/SchneiderMaterialsWeight%i = 15 0.10404040 0.10606061 0.75656566 0.03131313 0.0 0.00202020 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0',counter);
+ fprintf(fID,'uv:Ge/Patient/SchneiderMaterialsWeight%i = 15 0.101278 0.102310 0.028650 0.757072 0.000730 0.000800 0.002250 0.002660 0.0 0.000090 0.001840 0.001940 0.0 0.000370 0.000010',counter);
+ end
+ else
+ fname = fullfile(obj.topasFolder,filesep,obj.converterFolder,filesep,obj.infilenames.matConv_Schneider_loadFromFile);
+ converter = fileread(fname);
+ fprintf(fID,'\n%s\n',converter);
+ end
+
+ % write patient environment
+ matRad_cfg.dispInfo('TOPAS: Writing patient environment\n');
+ fprintf(fID,'\n# -- Patient parameters\n');
+ fprintf(fID,'s:Ge/Patient/Parent="World"\n');
+ fprintf(fID,'s:Ge/Patient/Type = "TsImageCube"\n');
+ fprintf(fID,'b:Ge/Patient/DumpImagingValues = "True"\n');
+ fprintf(fID,'s:Ge/Patient/InputDirectory = "./"\n');
+ fprintf(fID,'s:Ge/Patient/InputFile = "%s"\n',dataFile);
+ fprintf(fID,'s:Ge/Patient/ImagingtoMaterialConverter = "Schneider"\n');
+ fprintf(fID,'i:Ge/Patient/NumberOfVoxelsX = %d\n',ct.cubeDim(2));
+ fprintf(fID,'i:Ge/Patient/NumberOfVoxelsY = %d\n',ct.cubeDim(1));
+ fprintf(fID,'iv:Ge/Patient/NumberOfVoxelsZ = 1 %d\n',ct.cubeDim(3));
+ fprintf(fID,'d:Ge/Patient/VoxelSizeX = %.3f mm\n',ct.resolution.x);
+ fprintf(fID,'d:Ge/Patient/VoxelSizeY = %.3f mm\n',ct.resolution.y);
+ fprintf(fID,'dv:Ge/Patient/VoxelSizeZ = 1 %.3f mm\n',ct.resolution.z);
+ fprintf(fID,'s:Ge/Patient/DataType = "SHORT"\n');
+
+ fclose(fID);
+
+ % write HU data
+ matRad_cfg.dispInfo('TOPAS: Export patient cube\n');
+ huCube = int32(permute(ct.cubeHU{ctScen},permutation));
+ fID = fopen(fullfile(obj.workingDir, dataFile),'w');
+ fwrite(fID,huCube,'short');
+ fclose(fID);
+ cube = huCube;
+ catch ME
+ matRad_cfg.dispWarning(ME.message);
+ matRad_cfg.dispError(['TOPAS: Error in Schneider Converter! (line ',num2str(ME.stack(1).line),')']);
+ end
+
+ otherwise
+ matRad_cfg.dispError('Material Conversion rule "%s" not implemented (yet)!\n',obj.materialConverter.mode);
+ end
+ obj.MCparam.imageCube{ctScen} = cube;
+
+
+ end
+
+ function writeRangeShifter(~,fID,rangeShifter,sourceToNozzleDistance)
+ %TODO: Insert documentation
+ %Hardcoded PMMA range shifter for now
+ pmma_rsp = 1.165;
+ rsWidth = rangeShifter.eqThickness / pmma_rsp;
+
+ fprintf(fID,'s:Ge/%s/Parent = "Nozzle"\n',rangeShifter.topasID);
+ fprintf(fID,'s:Ge/%s/Type = "TsBox"\n',rangeShifter.topasID);
+ fprintf(fID,'s:Ge/%s/Material = "Lucite"\n',rangeShifter.topasID);
+ fprintf(fID,'d:Ge/%s/HLX = 250 mm\n',rangeShifter.topasID);
+ fprintf(fID,'d:Ge/%s/HLY = 250 mm\n',rangeShifter.topasID);
+ fprintf(fID,'d:Ge/%s/HLZ = %f mm\n',rangeShifter.topasID,rsWidth/2);
+ fprintf(fID,'d:Ge/%s/TransX = 500 mm * Tf/Beam/%sOut/Value\n',rangeShifter.topasID,rangeShifter.topasID);
+ fprintf(fID,'d:Ge/%s/TransY = 0 mm\n',rangeShifter.topasID);
+ fprintf(fID,'d:Ge/%s/TransZ = %f mm\n',rangeShifter.topasID,rangeShifter.sourceRashiDistance - sourceToNozzleDistance);
+
+ end
+
+ function writeMCparam(obj)
+ %TODO: Insert documentation
+ %write MCparam file with basic parameters
+ MCparam = obj.MCparam;
+ save(fullfile(obj.workingDir,'MCparam.mat'),'MCparam','-v7');
+ end
+
+ end
+ methods(Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ msg = [];
+ available = false;
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(DoseEngines.matRad_TopasMCEngine.possibleRadiationModes, machine.meta.radiationMode));
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ else
+ available = preCheck;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+
+ end
+ end
+end
+
diff --git a/matRad/doseCalc/MCsquare/matRad_MCsquareBaseData.m b/matRad/doseCalc/MCsquare/matRad_MCsquareBaseData.m
new file mode 100644
index 000000000..af7d89ea4
--- /dev/null
+++ b/matRad/doseCalc/MCsquare/matRad_MCsquareBaseData.m
@@ -0,0 +1,104 @@
+classdef matRad_MCsquareBaseData < matRad_MCemittanceBaseData
+ % matRad_MCsquareBaseData class for calculating MCsquare base data and
+ % writing it to a .txt file, for MCsquare to use
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ methods (Access = public)
+ function obj = matRad_MCsquareBaseData(machine,stf)
+ %Call matRad_MCemmitanceBaseData constructor
+ if nargin < 2
+ stf = [];
+ end
+
+ obj = obj@matRad_MCemittanceBaseData(machine, stf);
+ end
+
+ function obj = writeMCsquareData(obj,filepath)
+ %function that writes a data file containing Monte Carlo base
+ %data for a simulation with MCsquare
+
+ %look up focus indices
+ focusIndex = obj.selectedFocus(obj.energyIndex);
+
+ %save mcData acording to used focus index in selectedData
+ selectedData = [];
+ for i = 1:numel(focusIndex)
+ selectedData = [selectedData, obj.monteCarloData(focusIndex(i), i)];
+ end
+
+ %remove field not needed for MCsquare base data
+% selectedData = rmfield(selectedData, 'FWHMatIso');
+
+ %write MCsqaure data base file
+ try
+
+ fileID = fopen(filepath,'w');
+
+ %Header
+ %fprintf(fileID,'--matRad: Beam Model for machine %s (%s)--\n',machine.meta.machine,machine.meta.dataType);
+ fprintf(fileID,'--UPenn beam model (double gaussian)--\n');
+ fprintf(fileID,'# %s\n', obj.machine.meta.description);
+ fprintf(fileID,'# created by %s on %s\n\n', obj.machine.meta.created_by, obj.machine.meta.created_on);
+
+ fprintf(fileID,'Nozzle exit to Isocenter distance\n');
+ fprintf(fileID,'%.1f\n\n',obj.nozzleToIso);
+
+ fprintf(fileID,'SMX to Isocenter distance\n');
+ fprintf(fileID,'%.1f\n\n',obj.smx);
+
+ fprintf(fileID,'SMY to Isocenter distance\n');
+ fprintf(fileID,'%.1f\n\n',obj.smy);
+
+ for i = 1:length(obj.rangeShifters)
+ raShi = obj.rangeShifters(i);
+ fprintf(fileID,'Range Shifter parameters\n');
+ fprintf(fileID,'RS_ID = %d\n',raShi.ID);
+ fprintf(fileID,'RS_type = binary\n');
+ fprintf(fileID,'RS_material = 64\n'); %Material ID Hardcoded for now (PMMA)
+ fprintf(fileID,'RS_density = 1.19\n'); %Maetiral density Hardcoded for now (PMMA)
+ fprintf(fileID,'RS_WET = %f\n\n',raShi.eqThickness);
+ end
+
+
+ fprintf(fileID,'Beam parameters\n%d energies\n\n',size(selectedData,2));
+
+ fn = fieldnames(selectedData);
+ for names = 1:size(fn,1)
+ fprintf(fileID, fn{names});
+ fprintf(fileID, '\t');
+ end
+ fprintf(fileID, '\n');
+
+ indices = obj.selectedFocus(obj.energyIndex);
+ for k = 1:size(selectedData,2)
+ for m = 1:numel(fn)
+ tmp = selectedData(k).(fn{m});
+ fprintf(fileID, '%g ', tmp(indices(k)));
+ fprintf(fileID, '\t');
+ end
+ fprintf(fileID, '\n');
+ end
+
+ fclose(fileID);
+
+ obj.bdl_path = filepath;
+
+ catch MException
+ error(MException.message);
+ end
+ end
+ end
+end
+
diff --git a/MCsquare/MatRad_MCsquareConfig.m b/matRad/doseCalc/MCsquare/matRad_MCsquareConfig.m
similarity index 89%
rename from MCsquare/MatRad_MCsquareConfig.m
rename to matRad/doseCalc/MCsquare/matRad_MCsquareConfig.m
index 655eeb2f8..e550504f8 100644
--- a/MCsquare/MatRad_MCsquareConfig.m
+++ b/matRad/doseCalc/MCsquare/matRad_MCsquareConfig.m
@@ -1,5 +1,5 @@
-classdef MatRad_MCsquareConfig
-% MatRad_MCsquareConfig class definition
+classdef matRad_MCsquareConfig
+% matRad_MCsquareConfig class definition
%
%
% References
@@ -10,31 +10,31 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+ Num_Primaries = 1e6;
-
- properties
-
%%% Simulation parameters:
Num_Threads = 0; % Number of parallel calculation threads. Default: 0 = max available threads
RNG_Seed = 0; % Seed for the random number generator (deterministic result only with single thread). Default: 0 = seed based on the time
- Num_Primaries = 1e6; % Number of primary protons to simulate. Default: 1e7
+
+ % This parameter can be overwritten through MatRad_Config default parameters
E_Cut_Pro = 0.5; % Energy cut (in MeV) below which heavy charged particles are locally absorbed. Default: 0.5
D_Max = 0.2; % Maximum distance between two step (cm). Default: 0.2
Epsilon_Max = 0.25; % Fractional energy loss (dE/T) per step. Default: 0.25
Te_Min = 0.05; % Threshold energy (MeV) for the production of secondary electrons (currently locally absorbed). Default: 0.05
+ Stat_uncertainty = 0.0 % Maximum statistical uncertainty (in percent). Default: 0.0 = no maximum uncertainty (number of proton = numHistories)
% As a reference: 200 MeV protons can transfer a maximum energy of 0.5 MeV to ?-electrons which correspond to a range of 7 mm in lung tissues.
%%% Input files
CT_File = 'Patient.mhd'; % Name of the CT file. Default: CT.mhd
- HU_Density_Conversion_File = 'Scanners/matRad_water/HU_Density_Conversion.txt'; % Name of the file containing HU to density conversion data. Default: HU_Density_Conversion.txt
- HU_Material_Conversion_File = 'Scanners/matRad_water/HU_Material_Conversion.txt'; % Name of the file containing HU to material conversion data. Default: HU_Material_Conversion.txt
+ HU_Density_Conversion_File = 'Scanners/matRad_default/HU_Density_Conversion.txt'; % Name of the file containing HU to density conversion data. Default: HU_Density_Conversion.txt
+ HU_Material_Conversion_File = 'Scanners/matRad_default/HU_Material_Conversion.txt'; % Name of the file containing HU to material conversion data. Default: HU_Material_Conversion.txt
BDL_Machine_Parameter_File = 'BDL/BDL_matrad.txt'; % Name of the machine parameter file for the beam data library. Default: BDL.txt
BDL_Plan_File = 'PlanPencil.txt'; % Name of the plan file for the beam data library. Default: Plan.txt
@@ -112,27 +112,25 @@
end
methods
- function obj = MatRad_MCsquareConfig()
- %UNTITLED Construct an instance of this class
- % Detailed explanation goes here
- %obj.Property1 = inputArg1 + inputArg2;
+ function obj = matRad_MCsquareConfig()
+ %matRad_MCsquareConfig Configuration Class for MCsquare
end
-
+
+
function write(obj,fid)
-
MCsquareProperties = fieldnames(obj);
-
+
logicalString = {'False', 'True'};
for i = 1:numel(MCsquareProperties)
-
+
% modify fieldnames beginning with "4D"
if strncmp(MCsquareProperties{i},'fourD',5)
writeString = ['4D' MCsquareProperties{i}(6:end)];
else
writeString = MCsquareProperties{i};
end
-
+
if isa(obj.(MCsquareProperties{i}),'logical')
fprintf(fid,[writeString ' ' logicalString{obj.(MCsquareProperties{i})+1} '\n']);
elseif isa(obj.(MCsquareProperties{i}),'double')
@@ -140,13 +138,11 @@ function write(obj,fid)
elseif isa(obj.(MCsquareProperties{i}),'char')
fprintf(fid,[writeString ' ' obj.(MCsquareProperties{i}) '\n']);
else
- error('export not defined');
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Error when trying to write property %s for MCsquare dose calculation!',MCsquareProperties{i});
end
-
end
-
end
-
end
end
diff --git a/MCsquare/matRad_compileMCsquareSparseReader.m b/matRad/doseCalc/MCsquare/matRad_compileMCsquareSparseReader.m
similarity index 96%
rename from MCsquare/matRad_compileMCsquareSparseReader.m
rename to matRad/doseCalc/MCsquare/matRad_compileMCsquareSparseReader.m
index bfe24e71e..0a938c8fd 100644
--- a/MCsquare/matRad_compileMCsquareSparseReader.m
+++ b/matRad/doseCalc/MCsquare/matRad_compileMCsquareSparseReader.m
@@ -24,7 +24,7 @@ function matRad_compileMCsquareSparseReader(dest,sourceFolder)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/MCsquare/matRad_sparseBeamletsReaderMCsquare.cpp b/matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.cpp
similarity index 99%
rename from MCsquare/matRad_sparseBeamletsReaderMCsquare.cpp
rename to matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.cpp
index 65a2f0120..045dbb057 100644
--- a/MCsquare/matRad_sparseBeamletsReaderMCsquare.cpp
+++ b/matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.cpp
@@ -5,7 +5,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexa64 b/matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexa64
old mode 100755
new mode 100644
similarity index 100%
rename from MCsquare/matRad_sparseBeamletsReaderMCsquare.mexa64
rename to matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexa64
diff --git a/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexmaci64 b/matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexmaci64
old mode 100755
new mode 100644
similarity index 100%
rename from MCsquare/matRad_sparseBeamletsReaderMCsquare.mexmaci64
rename to matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexmaci64
diff --git a/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexoct640a64 b/matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexoct640a64
similarity index 100%
rename from MCsquare/matRad_sparseBeamletsReaderMCsquare.mexoct640a64
rename to matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexoct640a64
diff --git a/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexoct640w64 b/matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexoct640w64
similarity index 100%
rename from MCsquare/matRad_sparseBeamletsReaderMCsquare.mexoct640w64
rename to matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexoct640w64
diff --git a/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexw64 b/matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexw64
similarity index 100%
rename from MCsquare/matRad_sparseBeamletsReaderMCsquare.mexw64
rename to matRad/doseCalc/MCsquare/matRad_sparseBeamletsReaderMCsquare.mexw64
diff --git a/matRad/doseCalc/matRad_MCinit.m b/matRad/doseCalc/matRad_MCinit.m
new file mode 100644
index 000000000..07bf0eac1
--- /dev/null
+++ b/matRad/doseCalc/matRad_MCinit.m
@@ -0,0 +1,31 @@
+function MCsettings = matRad_MCinit(pln,MCsettings)
+
+if ~isfield(MCsettings,'MC_PBS')
+ MCsettings.MC_PBS = 0;
+end
+
+if ~isfield(MCsettings,'MCengine')
+ if strcmp(pln.radiationMode,'protons')
+ MCsettings.MCengine = 'MCsquare';
+ else
+ error('No MC engine available for other particles yet!');
+ end
+end
+
+if ~isfield(MCsettings,'runsPath')
+ MCsettings.runsPath = 'MCexport';
+end
+
+if ~isfield(MCsettings,'fractionHistories')
+ MCsettings.fractionHistories = 1e-4;
+end
+
+if ~isfield(MCsettings,'ElectronProdCut_mm')
+ MCsettings.runsPath = 0.5;
+end
+
+if ~isfield(MCsettings,'minRelWeight')
+ MCsettings.minRelWeight = 0;
+end
+
+end
\ No newline at end of file
diff --git a/matRad/doseCalc/matRad_calcDoseDirect.m b/matRad/doseCalc/matRad_calcDoseDirect.m
new file mode 100644
index 000000000..d0c669b16
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcDoseDirect.m
@@ -0,0 +1,61 @@
+function resultGUI = matRad_calcDoseDirect(ct,stf,pln,cst,w,nHistories)
+ % matRad function to bypass dij calculation
+ % Should not be used directly anymore as it is deprecated. Use matRad_calcDoseForward instead.
+ %
+ % call
+ % resultGUI = matRad_calcDoseDirec(ct,stf,pln,cst)
+ % resultGUI = matRad_calcDoseDirec(ct,stf,pln,cst,w)
+ %
+ % input
+ % ct: ct cube
+ % stf: matRad steering information struct
+ % pln: matRad plan meta information struct
+ % cst: matRad cst struct
+ % w: (optional, if no weights available in stf): bixel weight
+ % vector
+ %
+ % output
+ % resultGUI: matRad result struct
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2024 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('This function is deprecated. Please use matRad_calcDoseForward with appropriate engine set in pln.propDoseCalc.');
+
+ % dose calculation
+ switch pln.radiationMode
+ case {'protons','helium','carbon'}
+ engine = DoseEngines.matRad_ParticleHongPencilBeamEngine(pln);
+ case 'photons'
+ engine = DoseEngines.matRad_PhotonPencilBeamSVDEngine(pln);
+ otherwise
+ matRad_cfg.dispError('Radiation mode ''%s'' not supported!',pln.radiationMode)
+ end
+
+ if nargin < 4
+ resultGUI = engine.calcDoseForward(ct,cst,stf);
+ else
+ resultGUI = engine.calcDoseForward(ct,cst,stf,w);
+ end
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/matRad/doseCalc/matRad_calcDoseDirectMC.m b/matRad/doseCalc/matRad_calcDoseDirectMC.m
new file mode 100644
index 000000000..ba286c7b5
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcDoseDirectMC.m
@@ -0,0 +1,68 @@
+function resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w,nHistories)
+% matRad function to bypass dij calculation for MC dose calculation
+% matRad dose calculation wrapper for MC dose calculation algorithms
+% bypassing dij calculation for MC dose calculation algorithms.
+%
+% call
+% resultGUI = matRad_calcDoseDirecMC(ct,stf,pln,cst)
+% resultGUI = matRad_calcDoseDirecMC(ct,stf,pln,cst,w)
+% resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,nHistories)
+% resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w,nHistories)
+%
+% input
+% ct: ct cube
+% stf: matRad steering information struct
+% pln: matRad plan meta information struct
+% cst: matRad cst struct
+% w: (optional, if no weights available in stf): bixel weight
+% vector
+% nHistories: (optional) number of histories
+%
+% output
+% resultGUI: matRad result struct
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispDeprecationWarning('This function is deprecated. Please use matRad_calcDoeDirect with appropriate Monte Carlo engine set in pln.propDoseCalc.');
+
+% dose calculation
+switch pln.radiationMode
+ case 'protons'
+ engine = DoseEngines.matRad_ParticleMCsquareEngine(pln);
+ case 'photons'
+ engine = DoseEngines.matRad_PhotonOmpMCEngine(pln);
+ otherwise
+ matRad_cfg.dispError('Radiation mode ''%s'' not supported!',pln.radiationMode)
+end
+
+if nargin == 5
+ engine.numHistoriesDirect = nHistories;
+end
+
+if nargin < 4
+ resultGUI = engine.calcDoseForward(ct,cst,stf);
+else
+ resultGUI = engine.calcDoseForward(ct,cst,stf,w);
+end
+
+
+
+
+
+
diff --git a/matRad_calcLQParameter.m b/matRad/doseCalc/matRad_calcLQParameter.m
similarity index 95%
rename from matRad_calcLQParameter.m
rename to matRad/doseCalc/matRad_calcLQParameter.m
index 287e281e0..b63a4d7de 100644
--- a/matRad_calcLQParameter.m
+++ b/matRad/doseCalc/matRad_calcLQParameter.m
@@ -22,7 +22,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/doseCalc/matRad_calcParticleDose.m b/matRad/doseCalc/matRad_calcParticleDose.m
new file mode 100644
index 000000000..0465ad535
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcParticleDose.m
@@ -0,0 +1,57 @@
+function dij = matRad_calcParticleDose(ct,stf,pln,cst,calcDoseDirect)
+% matRad particle dose calculation wrapper
+%
+% call
+% dij = matRad_calcParticleDose(ct,stf,pln,cst,calcDoseDirect)
+%
+% input
+% ct: ct cube
+% stf: matRad steering information struct
+% pln: matRad plan meta information struct
+% cst: matRad cst struct
+% calcDoseDirect: boolian switch to bypass dose influence matrix
+% computation and directly calculate dose; only makes
+% sense in combination with matRad_calcDoseDirect.m
+%
+% output
+% dij: matRad dij struct
+%
+% References
+% [1] http://iopscience.iop.org/0031-9155/41/8/005
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispDeprecationWarning('The old dose calculation functions are deprecated! Try to use matRad_calcDoseInfluence with the new engine format from now on!');
+
+% could be also set as pln property e.g pln.propDoseCalc.useDeprecated
+if isfield(pln, 'propDoseCalc') && isfield(pln.propDoseCalc, 'engine')
+ matRad_cfg.dispWarning('You should not use the deprecated MC calculation with the new engine architecture! For backwards compatibility, this function will use the original pencil beam as engine!');
+end
+
+engine = DoseEngines.matRad_ParticleHongPencilBeamEngine(pln);
+
+% set additional args
+if exist('calcDoseDirect','var')
+ engine.calcDoseDirect = calcDoseDirect;
+end
+
+matRad_cfg.dispInfo('Starting dose calculation using %s engine.\n', engine.name);
+pln.propDoseCalc = engine;
+
+% call calcDose from engine
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+
+end
\ No newline at end of file
diff --git a/matRad/doseCalc/matRad_calcParticleDoseMC.m b/matRad/doseCalc/matRad_calcParticleDoseMC.m
new file mode 100644
index 000000000..4e3fab9d1
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcParticleDoseMC.m
@@ -0,0 +1,66 @@
+function dij = matRad_calcParticleDoseMC(ct,stf,pln,cst,nCasePerBixel,calcDoseDirect)
+% matRad MCsqaure monte carlo photon dose calculation wrapper
+%
+% call
+% dij = matRad_calcParticleDoseMc(ct,stf,pln,cst,calcDoseDirect)
+%
+% input
+% ct: matRad ct struct
+% stf: matRad steering information struct
+% pln: matRad plan meta information struct
+% cst: matRad cst struct
+% nCasePerBixel: number of histories per beamlet
+% calcDoseDirect: binary switch to enable forward dose
+% calcualtion
+%
+% output
+% dij: matRad dij struct
+%
+% References
+%
+% https://aapm.onlinelibrary.wiley.com/doi/abs/10.1118/1.4943377
+% http://www.openmcsquare.org/
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispDeprecationWarning('The old dose calculation functions are deprecated! Try to use matRad_calcDoseInfluence with the new engine format from now on!');
+
+% could be also set as pln property e.g pln.propDoseCalc.useDeprecated
+if isfield(pln, 'propDoseCalc') && isfield(pln.propDoseCalc, 'engine')
+ matRad_cfg.dispWarning('You should not use the deprecated MC calculation with the new engine architecture! Setting MCsquare as engine!');
+end
+
+engine = DoseEngines.matRad_ParticleMCsquareEngine(pln);
+
+% set additional args
+% assign old deprecated defaults
+if exist('nCasePerBixel','var')
+ engine.numHistoriesPerBeamlet = nCasePerBixel;
+else
+ engine.numHistoriesPerBeamlet = matRad_cfg.defaults.propDoseCalc.numHistoriesPerBeamlet;
+end
+
+if exist('calcDoseDirect','var')
+ engine.calcDoseDirect = calcDoseDirect;
+end
+matRad_cfg.dispInfo('Starting dose calculation using %s engine.\n', engine.name);
+
+pln.propDoseCalc.engine = engine;
+
+% call calcDose from engine
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+
+end
diff --git a/matRad/doseCalc/matRad_calcPhotonDose.m b/matRad/doseCalc/matRad_calcPhotonDose.m
new file mode 100644
index 000000000..3cdc630be
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcPhotonDose.m
@@ -0,0 +1,55 @@
+function dij = matRad_calcPhotonDose(ct,stf,pln,cst,calcDoseDirect)
+% matRad photon dose calculation wrapper
+%
+% call
+% dij = matRad_calcPhotonDose(ct,stf,pln,cst,calcDoseDirect)
+%
+% input
+% ct: ct cube
+% stf: matRad steering information struct
+% pln: matRad plan meta information struct
+% cst: matRad cst struct
+% calcDoseDirect: boolian switch to bypass dose influence matrix
+% computation and directly calculate dose; only makes
+% sense in combination with matRad_calcDoseDirect.m
+%
+% output
+% dij: matRad dij struct
+%
+% References
+% [1] http://www.ncbi.nlm.nih.gov/pubmed/8497215
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispDeprecationWarning('The old dose calculation functions are deprecated! Try to use matRad_calcDoseInfluence with the new engine format from now on!');
+
+% could be also set as pln property e.g pln.propDoseCalc.useDeprecated
+if isfield(pln, 'propDoseCalc') && isfield(pln.propDoseCalc, 'engine')
+ matRad_cfg.dispWarning('You should not use the deprecated MC calculation with the new engine architecture! Setting SVD pencil beam as engine!');
+end
+
+engine = DoseEngines.matRad_PhotonPencilBeamSVDEngine(pln);
+
+% if additional args are given, configure the engine
+if exist('calcDoseDirect','var')
+ engine.calcDoseDirect = calcDoseDirect;
+end
+matRad_cfg.dispInfo('Starting dose calculation using %s engine.\n', engine.name);
+
+pln.propDoseCalc = engine;
+% call calcDose from engine
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+
+end
diff --git a/matRad/doseCalc/matRad_calcPhotonDoseMC.m b/matRad/doseCalc/matRad_calcPhotonDoseMC.m
new file mode 100644
index 000000000..40cb52e4c
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcPhotonDoseMC.m
@@ -0,0 +1,62 @@
+function dij = matRad_calcPhotonDoseMC(ct,stf,pln,cst,nCasePerBixel,visBool)
+% matRad ompMC monte carlo photon dose calculation wrapper
+%
+% call
+% dij = matRad_calcPhotonDoseMc(ct,stf,pln,cst,visBool)
+%
+% input
+% ct: matRad ct struct
+% stf: matRad steering information struct
+% pln: matRad plan meta information struct
+% cst: matRad cst struct
+% visBool: binary switch to enable visualization
+% output
+% dij: matRad dij struct
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+matRad_cfg = MatRad_Config.instance();
+
+matRad_cfg.dispDeprecationWarning('The old dose calculation functions are deprecated! Try to use matRad_calcDoseInfluence with the new engine format from now on!');
+
+% could be also set as pln property e.g pln.propDoseCalc.useDeprecated
+if isfield(pln, 'propDoseCalc') && isfield(pln.propDoseCalc, 'engine')
+ matRad_cfg.dispWarning('You should not use the deprecated MC calculation with the new engine architecture! Setting ompMC as engine!');
+end
+
+engine = DoseEngines.matRad_PhotonOmpMCEngine(pln);
+
+% assign old deprecated defaults
+if exist('nCasePerBixel','var')
+ engine.numHistoriesPerBeamlet = nCasePerBixel;
+else
+ engine.numHistoriesPerBeamlet = matRad_cfg.defaults.propDoseCalc.numHistoriesPerBeamlet;
+end
+engine.outputMCvariance = matRad_cfg.defaults.propDoseCalc.outputVariance;
+
+if exist('visBool','var')
+ engine.visBool = visBool;
+end
+
+matRad_cfg.dispInfo('Starting dose calculation using %s engine.\n', engine.name);
+
+pln.propDoseCalc = engine;
+
+% call the calcDose from engine
+dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+end
\ No newline at end of file
diff --git a/matRad/doseCalc/matRad_calcSigmaIni.m b/matRad/doseCalc/matRad_calcSigmaIni.m
new file mode 100644
index 000000000..0b5c68828
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcSigmaIni.m
@@ -0,0 +1,75 @@
+function [sigmaIni] = matRad_calcSigmaIni(baseData,rays,SSD)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% This function evaluates simultaneously the initial sigma of the beam for
+% one or more energies
+%
+% call
+% sigmaIni = matRad_calcSigmaIni(machine.data,stf(i).ray,stf(i).ray(j).SSD);
+%
+% input
+% baseData: 'machine.data' file
+% rays: 'stf.ray' file
+% SSD: source-surface difference
+%
+% output
+% sigmaIni: initial sigma of the ray at certain energy (or
+% energies). The data is given in 1xP dimensions,
+% where 'P' represents the number of different
+% energies
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is not part of the offical matRad release
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Finds the energies involved in the process and their focus index
+[energyVec,uniqueEnergyIdx] = unique([rays.energy]);
+tempFocus = [rays.focusIx];
+focusIxVec = tempFocus(uniqueEnergyIdx);
+
+% finds energy index for all the energies
+[~,energyIxVec] = intersect([baseData.energy],energyVec);
+
+if length(energyVec)==1
+ sigmaIni = matRad_interp1(baseData(energyIxVec).initFocus.dist(rays.focusIx,:)',baseData(energyIxVec).initFocus.sigma(rays.focusIx,:)',SSD);
+
+else
+ % finds standard deviation and distance for all the energies
+ focusStruct = [baseData.initFocus];
+ sigmaVec = [focusStruct(energyIxVec).sigma];
+ distVec = [focusStruct(energyIxVec).dist];
+
+ dim = size(focusStruct(1).sigma,2);
+
+ % repeats every index for a number of times equal to size of sigma
+% repFocusIxVec = kron(focusIxVec, ones(1,dim));
+ repFocusIxVec = repelem(focusIxVec,dim);
+
+ % Take the values of sigma and distance that we need for calculation]
+ % The elements I want are the ones which corrispond to index in the same
+ % position in both index vectors, i.e. the diagonal of the resulting matrix
+ sigmaVec = sigmaVec(repFocusIxVec,[1:size(sigmaVec,2)]);
+ sigmaVec = diag(sigmaVec);
+ distVec = distVec(repFocusIxVec,[1:size(distVec,2)]);
+ distVec = diag(distVec);
+
+ % reshape because I want as output a vector of the correct sigmas and dist,
+ % so I interpolate 'dim' elements at the time
+ distMat = reshape(distVec,dim,[]);
+ sigMat = reshape(sigmaVec,dim,[]);
+
+ [x,~]=meshgrid(1:size(distMat,2),1:1);
+ [x1,~]=meshgrid(1:size(distMat,2),1:size(distMat,1));
+
+ % griddata produces interpolation error in octave
+ % sigmaIni = griddata(distMat,x1, sigMat, repmat(SSD,1,size(distMat,2)),x);
+ sigmaIni = interp2(x1,distMat, sigMat,x, repmat(SSD,1,size(distMat,2)));
+end
diff --git a/matRad/doseCalc/matRad_calcSigmaRashi.m b/matRad/doseCalc/matRad_calcSigmaRashi.m
new file mode 100644
index 000000000..26676ff88
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcSigmaRashi.m
@@ -0,0 +1,113 @@
+function sigmaRashi = matRad_calcSigmaRashi(bdEntry,rangeShifter,SSD)
+% calculation of additional beam broadening due to the use of range shifters
+% (only for protons)
+%
+% call
+% sigmaRashi = matRad_calcSigmaRashi(bdEntry,rangeShifter,SSD)
+%
+% input
+% bdEntry: base data entry for energy
+% rangeShifter: structure defining range shifter geometry
+% SSD: source to surface distance
+%
+% output
+% sigmaRashi: sigma of range shifter (to be added ^2) in mm
+%
+% References
+% [1] https://www.ncbi.nlm.nih.gov/pubmed/12375823
+% [2] https://www.ncbi.nlm.nih.gov/pubmed/12701891
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% distance of range shifter to patient surface
+rashiDist = SSD - rangeShifter.sourceRashiDistance;
+
+% convert to mm
+zS = rashiDist / 10; % [cm] (after division)
+t = rangeShifter.eqThickness / 10; % [cm] (after division)
+
+% --> everything here is in [cm]
+
+a1 = 0.21; % [1]
+a2 = 0.77; % [1]
+c0 = 0.0191027; % [cm]
+c1 = 0.0204539; % [1]
+alpha = 0.0022; % [cm MeV ^ (-p)] %
+p = 1.77; % [1] % Exponent of range-energy relation
+
+if isfield(bdEntry,'range')
+ R = bdEntry.range;
+else % convert energy to range
+ energy = bdEntry.energy;
+ R = alpha * (energy ^ p);
+end
+
+% check if valid computation possible or range shifter to thick
+if t / R >= 0.95
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Computation of range shifter sigma invalid. Range shifter is too thick.');
+end
+
+% Improved HONG's Formula [1, Eq. 21]
+s = t / R;
+sigmaT = (a1 * s + a2 * s ^ 2) * (c0 + c1 * R);
+
+
+%See [1, Eq. 11]
+C1 = 13.6; % MeV
+C2 = 0.038;
+
+%See [1, Table 1 & Eq. 13]
+LR_water = 36.08; %rad. length [cm] % cm
+%rSPwater = 1; %rSP [rel. u.]
+%PrSFwater = 1; %lateral scaling factor, [rel.u.]
+%rho_water = 1; %density [g/cm^3]
+
+LR_pmma = 40.55; %rad. length [cm] % cm
+rSP_pmma = 1.165; %rSP [rel. u.]
+rho_pmma = 1.19; %density [g/cm^3]
+PrSF_pmma = sqrt(rSP_pmma^3 * LR_water/LR_pmma * (1 + 2*C2*log(rSP_pmma * LR_water / LR_pmma))); %1.1877;
+
+LR_lexan = 41.46; %rad. length [cm]
+rSP_lexan = 1.165; %rSP [rel. u.]
+rho_lexan = 1.20; %density [g/cm^3]
+
+
+
+
+%{
+L_air = 30.4e3; %rad. length [cm]
+PSP_air = 1.07e-3; %rSP [rel. u.]
+rho_air = 1.205e-3; %density [g/cm^3]
+PrSF_air = 990.81; %lateral scaling factor, [rel.u.]
+%}
+
+F1part1 = (2 * C1 ^ 2 * alpha ^ (2 / p)) / (4 * LR_pmma * (2 / p - 1));
+F1part2 = (((1 - s) ^ (2 - 2 / p) - 1) / (2 / p - 2) - s);
+F1part3 = R ^ (2 - 2 / p);
+
+F1 = F1part1 * F1part2 * F1part3;
+
+F2part1 = (C1 ^ 2 * alpha ^ (2 / p)) / (4 * LR_pmma * (2 / p - 1));
+F2part2 = (((1 - s) ^ (1 - 2 / p) - 1)) / 1;
+F2part3 = R ^ (1 - 2 / p);
+
+F2 = F2part1 * F2part2 * F2part3;
+
+sigmaProjSq = PrSF_pmma ^ 2 * (sigmaT ^ 2 + F1 .* zS * rSP_pmma + F2 * (zS * rSP_pmma) .^ 2); % [cm ^ 2]
+
+% <-- below in [mm]
+
+sigmaRashi = 10 * sqrt(sigmaProjSq); % [mm] (after factor 10)
+
diff --git a/matRad/doseCalc/matRad_calculateProbabilisticQuantities.m b/matRad/doseCalc/matRad_calculateProbabilisticQuantities.m
new file mode 100644
index 000000000..b2ba9d505
--- /dev/null
+++ b/matRad/doseCalc/matRad_calculateProbabilisticQuantities.m
@@ -0,0 +1,110 @@
+function [dij] = matRad_calculateProbabilisticQuantities(dij,cst,pln,mode4D)
+% matRad helper function to compute probabilistic quantities, i.e. expected
+% dose influence and variance omega matrices for probabilistic
+% optimization
+%
+% call
+% [dij,cst] = matRad_calculateProbabilisticQuantities(dij,cst,pln)
+%
+% input
+% dij: matRad dij struct
+% cst: matRad cst struct (in dose grid resolution)
+% pln: matRad pln struct
+% mode4D: (Optional) Handling of 4D phases:
+% - 'all' : include 4D scen in statistic
+% - 'phase' : create statistics per phase (default)
+%
+% output
+% dij: dij with added probabilistic quantities
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2020 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+if nargin < 4
+ mode4D = 'phase';
+elseif ~any(strcmpi(mode4D,{'phase','all'}))
+ matRad_cfg.dispError('4D calculation mode %s unknown, allowed is only ''phase'' or ''all''',mode4D);
+end
+
+
+matRad_cfg.dispInfo('Calculating probabilistic quantities E[D] & Omega[D] ...\n');
+
+if ~pln.bioParam.bioOpt
+ fNames = {'physicalDose'};
+else
+ fNames = {'mAlphaDose','mSqrtBetaDose'};
+end
+
+% create placeholders for expected influence matrices
+
+voiIx = [];
+for i = 1:size(cst,1)
+ if ~isempty(cst{i,6})
+ voiIx = [voiIx i];
+ end
+end
+
+scens = find(pln.multScen.scenMask);
+
+%Create structures
+for i = 1:numel(fNames)
+ matRad_cfg.dispInfo('\tE[D] & Omega[D] for ''%s'':\n',fNames{1,i});
+ if strcmpi(mode4D,'phase')
+ dij.([fNames{1,i} 'Exp']){1:pln.multScen.numOfCtScen} = spalloc(dij.doseGrid.numOfVoxels,dij.totalNumOfBixels,1);
+ dij.([fNames{1,i} 'Omega']) = cell(size(cst,1),pln.multScen.numOfCtScen);
+
+ ixTmp = cell(ndims(pln.multScen.scenMask),1);
+ [ixTmp{:}] = ind2sub(size(pln.multScen.scenMask),scens);
+ ctIxMap = ixTmp{1};
+ else
+ dij.([fNames{1,i} 'Exp']){1} = spalloc(dij.doseGrid.numOfVoxels,dij.totalNumOfBixels,1);
+ dij.([fNames{1,i} 'Omega']) = cell(size(cst,1),1);
+ ctIxMap = ones(numel(scens),1);
+ end
+ [dij.([fNames{1,i} 'Omega']){voiIx,:}] = deal(zeros(dij.totalNumOfBixels));
+
+ %Now loop over the scenarios
+
+ for s = 1:numel(scens)
+ matRad_cfg.dispInfo('\t\tScenario %d/%d...',s,numel(scens));
+ ctIx = ctIxMap(s);
+ scenIx = scens(s);
+
+ %Add up Expected value
+ dij.([fNames{1,i} 'Exp']){ctIx} = dij.([fNames{ctIx,i} 'Exp']){ctIx} + dij.([fNames{1,i}]){scenIx} .* pln.multScen.scenProb(s);
+
+ %Add up Omega
+ for v = voiIx
+ dij.([fNames{1,i} 'Omega']){v,ctIx} = dij.([fNames{1,i} 'Omega']){v,ctIx} + ...
+ (((dij.(fNames{1,i}){scenIx}(cst{v,4}{ctIx},:)' * pln.multScen.scenProb(s)) * ...
+ (dij.(fNames{1,i}){scenIx}(cst{v,4}{ctIx},:)) * pln.multScen.scenProb(s)));
+ end
+ matRad_cfg.dispInfo('Done!\n');
+ end
+ matRad_cfg.dispInfo('\tFinalizing Omega...');
+ %Finalize Omega matrices
+ unCtIx = unique(ctIx);
+ for ctIx = unCtIx
+ for v = voiIx
+ dij.([fNames{1,i} 'Omega']){v,ctIx} = dij.([fNames{1,i} 'Omega']){v,ctIx} - (dij.([fNames{1,i} 'Exp']){ctIx}(cst{v,4}{ctIx},:)' * dij.([fNames{1,i} 'Exp']){ctIx}(cst{v,4}{ctIx},:));
+ end
+ end
+ matRad_cfg.dispInfo('\tDone!\n');
+end
+
+
diff --git a/matRad_computeSSD.m b/matRad/doseCalc/matRad_computeSSD.m
similarity index 65%
rename from matRad_computeSSD.m
rename to matRad/doseCalc/matRad_computeSSD.m
index 7c3e9c169..6b07721bf 100644
--- a/matRad_computeSSD.m
+++ b/matRad/doseCalc/matRad_computeSSD.m
@@ -1,15 +1,20 @@
-function stf = matRad_computeSSD(stf,ct,mode)
+function stf = matRad_computeSSD(stf,ct,varargin)
% matRad SSD calculation
%
% call
% stf = matRad_computeSSD(stf,ct)
-% stf = matRad_computeSSD(stf,ct,mode)
+% stf = matRad_computeSSD(stf,ct,Name,Value)
%
% input
% ct: ct cube
% stf: matRad steering information struct
+%
+% Optional Name/Value Properties:
% mode: optional parameter specifying how to handle multiple
-% cubes to compute one SSD
+% cubes to compute one SSD. Only 'first' isimplemented
+%
+% densityThreshold: value determining the skin threshold.
+%
% output
% stf: matRad steering information struct
%
@@ -22,36 +27,44 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
matRad_cfg = MatRad_Config.instance();
-if nargin < 3
- mode = 'first';
-end
+%Parse arguments
+p = inputParser();
+%We leave the required parameters to avoid duplicates
+%p.addRequired('stf',@isstruct);
+%p.addRequired('ct',@isstruct);
+p.addParameter('mode','first');
+p.addParameter('densityThreshold',matRad_cfg.defaults.propDoseCalc.ssdDensityThreshold,@(x) isnumeric(x) && isscalar(x));
+p.addParameter('showWarning',true,@(x) islogical(x) && isscalar(x));
+
+p.parse(varargin{:});
+
+mode = p.Results.mode;
+densityThreshold = p.Results.densityThreshold;
+boolShowWarning = p.Results.showWarning;
-% booleon to show warnings only once in the console
-boolShowWarning = true;
-% set density threshold for SSD computation
-densityThreshold = matRad_cfg.propDoseCalc.defaultSsdDensityThreshold;
if strcmp(mode,'first')
-
for i = 1:size(stf,2)
SSD = cell(1,stf(i).numOfRays);
for j = 1:stf(i).numOfRays
- [alpha,~,rho,~,~] = matRad_siddonRayTracer(stf(i).isoCenter, ...
+
+ cubeIsoCenter = matRad_world2cubeCoords(stf(i).isoCenter,ct);
+
+ [alpha,~,rho,d12,~] = matRad_siddonRayTracer(cubeIsoCenter, ...
ct.resolution, ...
stf(i).sourcePoint, ...
stf(i).ray(j).targetPoint, ...
- {ct.cube{1}});
+ {ct.cube{1}}); %Is this correct for multiple scenarios?
ixSSD = find(rho{1} > densityThreshold,1,'first');
if boolShowWarning
@@ -63,9 +76,9 @@
boolShowWarning = false;
end
end
-
+
% calculate SSD
- SSD{j} = double(2 * stf(i).SAD * alpha(ixSSD));
+ SSD{j} = double(d12* alpha(ixSSD));
stf(i).ray(j).SSD = SSD{j};
end
@@ -77,9 +90,11 @@
stf(i).ray(j).SSD = matRad_closestNeighbourSSD(rayPos_bev, SSD, rayPos_bev(j,:));
end
end
+
end
+
else
- matRad_cfg.dispError('mode not defined for SSD calculation');
+ matRad_cfg.dispError('Invalid mode %s for SSD calculation',mode);
end
end
@@ -97,7 +112,7 @@
end
if any(isempty(bestSSD))
matRad_cfg = MatRad_Config.instance();
- matRad_cfg.dispError('Could not fix SSD calculation.');
+ matRad_cfg.dispError('Error in SSD calculation: Could not fix SSD calculation by using closest neighbouring ray.');
end
-end
-
+
+end
\ No newline at end of file
diff --git a/matRad_interpRadDepth.m b/matRad/doseCalc/matRad_interpRadDepth.m
similarity index 57%
rename from matRad_interpRadDepth.m
rename to matRad/doseCalc/matRad_interpRadDepth.m
index 26f98847c..eba1b23c9 100644
--- a/matRad_interpRadDepth.m
+++ b/matRad/doseCalc/matRad_interpRadDepth.m
@@ -1,12 +1,12 @@
-function radDepthVcoarse = matRad_interpRadDepth(ct,ctScenNum,V,Vcoarse,vXgrid,vYgrid,vZgrid,radDepthV)
+function radDepthVcoarse = matRad_interpRadDepth(ct,V,Vcoarse,vXgrid,vYgrid,vZgrid,radDepthV)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% down/up sampling the radiological depth dose cubes
%
% call
-% radDepthVcoarse = matRad_interpRadDepth(ct,ctScenNum,V,Vcoarse,vXgrid,vYgrid,vZgrid,radDepthV)
+% radDepthVcoarse = matRad_interpRadDepth(ct,V,Vcoarse,vXgrid,vYgrid,vZgrid,radDepthV)
%
% input
% ct: matRad ct structure
-% ctScenNum: selcted CT scenario
% V: linear voxel indices of the cst
% Vcoarse: linear voxel indices of the down sampled grid resolution
% vXgrid: query points of now location in x dimension
@@ -26,19 +26,24 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-radDepthCube = NaN*ones(ct.cubeDim);
-radDepthCube(V(~isnan(radDepthV{1}))) = radDepthV{ctScenNum}(~isnan(radDepthV{1}));
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispDeprecationWarning('This function is obsolete and will be removed in a future release');
+
+for ctScen = 1:ct.numOfCtScen
+ radDepthCube = NaN*ones(ct.cubeDim);
+ radDepthCube(V(~isnan(radDepthV{1}))) = radDepthV{ctScen}(~isnan(radDepthV{1}));
-% interpolate cube - cube is now stored in Y X Z
-coarseRadDepthCube = matRad_interp3(ct.x,ct.y',ct.z,radDepthCube,vXgrid,vYgrid',vZgrid);
-radDepthVcoarse{ctScenNum} = coarseRadDepthCube(Vcoarse);
+ % interpolate cube - cube is now stored in Y X Z
+ coarseRadDepthCube = matRad_interp3(ct.x,ct.y',ct.z,radDepthCube,vXgrid,vYgrid',vZgrid);
+ radDepthVcoarse{ctScen} = coarseRadDepthCube(Vcoarse);
+end
end
diff --git a/matRad/doseCalc/matRad_projectOnComponents.m b/matRad/doseCalc/matRad_projectOnComponents.m
new file mode 100644
index 000000000..790e967e0
--- /dev/null
+++ b/matRad/doseCalc/matRad_projectOnComponents.m
@@ -0,0 +1,94 @@
+function [projCoord,idx,targetPoint, sourcePoint] = matRad_projectOnComponents(initIx,dim,sourcePoint_bev,targetPoint_bev,isoCenter, res, Dx, Dz, rotMat)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% This function projects a point on a certain ray and returns both the index
+% of the projected point in the reference system of the ct cube and its
+% coordinates
+%
+% call
+% [projCoord,idx,targetPoint, sourcePoint] =
+% matRad_projectOnComponents(initIx,dim,sourcePoint_bev,targetPoint_bev,isoCenter, res, Dx, Dz, rotMat)
+%
+% input
+% initIx: initial indices of the points
+% cubeDim: dimension of the ct cube (i.e. ct.cubeDim)
+% sourcePoint_bev: source point of the ray in bev
+% targetPoint_bev: target point of the ray in bev
+% isoCenter: isocenter coordinates (in cube system)
+% res: resolution in x,y, and z direction
+% Dx: displacement on x axis
+% Dz: displacement on z axis
+%
+% output
+% idx: projected indeces
+% projCoord: projected coordinates
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% add offset to target and source point in bev
+d = [Dx zeros(size(Dx)) Dz];
+targetPoint = (targetPoint_bev + d) * rotMat' + isoCenter;
+sourcePoint = (sourcePoint_bev + d) * rotMat' + isoCenter;
+
+
+% convert index in coordinates, it does the same job as:
+% [coord(:,2),coord(:,1),coord(:,3)] = ind2sub(dim,initIx);
+%{
+if max(initIx) > prod(dim) || min(initIx) < 0
+ error('Index exceeds matrix dimensions')
+else
+ v1 = floor(initIx ./ dim(1));
+ coord(:,2) = rem(initIx, dim(1)); coord(coord(:,2) == 0, 2) = dim(1);
+ coord(:,1) = rem(v1,dim(2)) + 1;
+ coord(coord(:,2) == dim(1), 1) = coord(coord(:,2) == dim(1), 1) - 1;
+ coord(coord(:,1) == 0, 1) = dim(2);
+ coord(:,3) = floor((v1) ./ dim(2)) + 1;
+end
+%}
+
+[coord(:,2),coord(:,1),coord(:,3)] = ind2sub(dim,initIx);
+
+%Multiply with resolution
+coord = coord.*res;
+
+% distance of points Bvec from the projection of points coord onto the line between A
+A2Bnorm = (targetPoint-sourcePoint)/norm(targetPoint-sourcePoint);
+
+% We just use the first row of targetPoint because the distance from one
+% end to the projected point does not change for parallel translations of
+% the ray
+pointToEndDist = (coord - targetPoint(1,:))*A2Bnorm';
+
+% add translation to the extreme of the ray, according to spherical coord,
+% in order to obtain the coord of the projected points
+% signvec = sign(Bvec-Avec);
+%projCoord = bsxfun(@plus,reshape(targetPoint',[1,3,length(Dx)]),pointToEndDist*A2Bnorm);
+projCoord = reshape(targetPoint',[1 3 size(targetPoint,1)]) + pointToEndDist*A2Bnorm;
+
+% round to voxel coords
+% D = round(bsxfun(@rdivide,projCoord,res));
+D = round(projCoord./res);
+
+% delete every point which goes out of the matrix
+% D( D(:,1)<1 | D(:,1)>dim(1) | D(:,2)<1 | D(:,2)>dim(2) | D(:,3)<1 | D(:,3)>dim(3), :) = [];
+
+% index the found coordinates, it does the same thing as:
+% idx = sub2ind(dim,D(:,2),D(:,1),D(:,3));
+ idx = D(:,2) + (D(:,1)-1)*dim(1) + (D(:,3)-1)*dim(1)*dim(2);
+
+
diff --git a/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_biGaussian.txt.in b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_biGaussian.txt.in
new file mode 100644
index 000000000..1a7fbdf73
--- /dev/null
+++ b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_biGaussian.txt.in
@@ -0,0 +1,19 @@
+s:Ge/BeamSpot/Parent = "Nozzle"
+s:Ge/BeamSpot/Type = "Group"
+d:Ge/BeamSpot/TransX = Tf/Beam/PosX/Value mm
+d:Ge/BeamSpot/TransY = Tf/Beam/PosY/Value mm
+
+s:So/PencilBeam/BeamParticle = Sim/ParticleName
+i:So/PencilBeam/NumberOfHistoriesInRun = Tf/Beam/Current/Value
+u:So/PencilBeam/BeamEnergySpread = Tf/Beam/EnergySpread/Value
+d:So/PencilBeam/BeamEnergy = Tf/Beam/Energy/Value MeV
+d:So/PencilBeam/SigmaX = Tf/Beam/SigmaX/Value mm
+u:So/PencilBeam/SigmaXprime = Tf/Beam/SigmaXprime/Value
+u:So/PencilBeam/CorrelationX = Tf/Beam/CorrelationX/Value
+d:So/PencilBeam/SigmaY = Tf/Beam/SigmaY/Value mm
+u:So/PencilBeam/SigmaYprime = Tf/Beam/SigmaYprime/Value
+u:So/PencilBeam/CorrelationY = Tf/Beam/CorrelationY/Value
+
+s:So/PencilBeam/Type = "Emittance" # Beam, Isotropic, Emittance or PhaseSpace
+s:So/PencilBeam/Distribution = "BiGaussian" #
+s:So/PencilBeam/Component = "BeamSpot"
diff --git a/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_generic.txt.in b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_generic.txt.in
new file mode 100644
index 000000000..da4458724
--- /dev/null
+++ b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_generic.txt.in
@@ -0,0 +1,18 @@
+s:Ge/BeamSpot/Parent = "Nozzle"
+s:Ge/BeamSpot/Type = "Group"
+d:Ge/BeamSpot/TransX = Tf/Beam/PosX/Value mm
+d:Ge/BeamSpot/TransY = Tf/Beam/PosY/Value mm
+
+s:So/PencilBeam/BeamParticle = Sim/ParticleName
+d:So/PencilBeam/BeamEnergy = Tf/Beam/Energy/Value MeV
+d:So/PencilBeam/BeamPositionSpreadX = Tf/Beam/FocusFWHM/Value mm * Sim/FWHM2SIGMA
+d:So/PencilBeam/BeamPositionSpreadY = Tf/Beam/FocusFWHM/Value mm * Sim/FWHM2SIGMA
+i:So/PencilBeam/NumberOfHistoriesInRun = Tf/Beam/Current/Value
+u:So/PencilBeam/BeamEnergySpread = 0. #
+s:So/PencilBeam/Type = "Beam" # Beam, Isotropic, Emittance or PhaseSpace
+s:So/PencilBeam/Component = "BeamSpot"
+s:So/PencilBeam/BeamPositionDistribution = "Gaussian" # None, Flat or Gaussian
+s:So/PencilBeam/BeamPositionCutoffShape = "Ellipse" # Rectangle or Ellipse (if Flat or Gaussian)
+d:So/PencilBeam/BeamPositionCutoffX = 100. mm # X extent of position (if Flat or Gaussian)
+d:So/PencilBeam/BeamPositionCutoffY = 100. mm # Y extent of position (if Flat or Gaussian)
+s:So/PencilBeam/BeamAngularDistribution = "None" # None, Flat or Gaussian
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_mlc.txt.in b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_mlc.txt.in
new file mode 100644
index 000000000..9077118d8
--- /dev/null
+++ b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_mlc.txt.in
@@ -0,0 +1,10 @@
+s:Ge/MultiLeafCollimatorA/Type = "TsMultiLeafCollimator"
+s:Ge/MultiLeafCollimatorA/Parent = "Nozzle"
+s:Ge/MultiLeafCollimatorA/Material = "G4_W"
+d:Ge/MultiLeafCollimatorA/TransX = 0.0 cm
+d:Ge/MultiLeafCollimatorA/TransY = 0.0 cm
+d:Ge/MultiLeafCollimatorA/TransZ = Sim/Ge/MultiLeafCollimatorA/TransZ cm
+d:Ge/MultiLeafCollimatorA/RotX = 0.0 deg
+d:Ge/MultiLeafCollimatorA/RotY = 0.0 deg
+d:Ge/MultiLeafCollimatorA/RotZ = 90.0 deg
+s:Ge/MultiLeafCollimatorA/DrawingStyle = "Solid"
diff --git a/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_phasespace.txt.in b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_phasespace.txt.in
new file mode 100644
index 000000000..62143c902
--- /dev/null
+++ b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_phasespace.txt.in
@@ -0,0 +1,9 @@
+s:So/Phasespace/Type = "PhaseSpace"
+s:So/Phasespace/Component = "Nozzle"
+#b:So/Example/LimitedAssumeFirstParticleIsNewHistory = "true"
+#b:So/Example/LimitedAssumeEveryParticleIsNewHistory = "true"
+#b:So/Example/LimitedAssumePhotonIsNewHistory = "true"
+i:So/Phasespace/PhaseSpaceMultipleUse = 0
+i:So/Phasespace/NumberOfHistoriesInRun = Tf/Phasespace/NumberOfHistoriesInRun/Value
+i:So/Phasespace/PreCheckShowParticleCountAtInterval = 100000
+b:So/Phasespace/PhaseSpacePreCheck = "False"
diff --git a/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_uniform.txt.in b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_uniform.txt.in
new file mode 100644
index 000000000..9a36fac51
--- /dev/null
+++ b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_uniform.txt.in
@@ -0,0 +1,15 @@
+s:Ge/BeamSpot/Parent = "Nozzle"
+s:Ge/BeamSpot/Type = "Group"
+d:Ge/BeamSpot/TransX = Tf/Beam/PosX/Value mm
+d:Ge/BeamSpot/TransY = Tf/Beam/PosY/Value mm
+
+s:So/PencilBeam/BeamParticle = Sim/ParticleName
+i:So/PencilBeam/NumberOfHistoriesInRun = Tf/Beam/Current/Value
+u:So/PencilBeam/BeamEnergySpread = Tf/Beam/EnergySpread/Value
+d:So/PencilBeam/BeamEnergy = Tf/Beam/Energy/Value MeV
+s:So/PencilBeam/BeamPositionCutoffShape = "Rectangle" # Rectangle or Ellipse (if Flat or Gaussian)
+s:So/PencilBeam/Type = "Beam" # Beam, Isotropic, Emittance or PhaseSpace
+s:So/PencilBeam/BeamPositionDistribution = "Flat" #
+s:So/PencilBeam/Component = "BeamSpot"
+s:So/PencilBeam/BeamAngularDistribution = "None" # None, Flat or Gaussian
+
diff --git a/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_virtualGaussian.txt.in b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_virtualGaussian.txt.in
new file mode 100644
index 000000000..f777af0ba
--- /dev/null
+++ b/matRad/doseCalc/topas/beamSetup/TOPAS_beamSetup_virtualGaussian.txt.in
@@ -0,0 +1,16 @@
+s:Ge/BeamSpot/Parent = "Nozzle"
+s:Ge/BeamSpot/Type = "Group"
+d:Ge/BeamSpot/TransX = Tf/Beam/PosX/Value mm
+d:Ge/BeamSpot/TransY = Tf/Beam/PosY/Value mm
+
+s:So/PencilBeam/BeamParticle = Sim/ParticleName
+i:So/PencilBeam/NumberOfHistoriesInRun = Tf/Beam/Current/Value
+u:So/PencilBeam/BeamEnergySpread = Tf/Beam/EnergySpread/Value
+d:So/PencilBeam/BeamEnergy = Tf/Beam/Energy/Value MeV
+s:So/PencilBeam/BeamPositionCutoffShape = "Ellipse" # Rectangle or Ellipse (if Flat or Gaussian)
+s:So/PencilBeam/Type = "Beam" # Beam, Isotropic, Emittance or PhaseSpace
+s:So/PencilBeam/BeamPositionDistribution = "Gaussian" #
+s:So/PencilBeam/Component = "BeamSpot"
+d:So/PencilBeam/BeamPositionCutoffX = 200 mm
+d:So/PencilBeam/BeamPositionCutoffY = 200 mm
+s:So/PencilBeam/BeamAngularDistribution = "None" # None, Flat or Gaussian
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/materialConverter/TOPAS_SchneiderConverter.txt.in b/matRad/doseCalc/topas/materialConverter/TOPAS_SchneiderConverter.txt.in
new file mode 100644
index 000000000..dcb5a4af1
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/TOPAS_SchneiderConverter.txt.in
@@ -0,0 +1,3 @@
+# Here you can put a full schneider converter as from the TOPAS example, i.e., including material composition, density corrections, HU sections, density factor/offsets.
+# To bypass default converter generation, set switch obj.materialConverter.loadConverterFromFile = true.
+
diff --git a/matRad/doseCalc/topas/materialConverter/definedMaterials/G4_WATER.txt b/matRad/doseCalc/topas/materialConverter/definedMaterials/G4_WATER.txt
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/definedMaterials/G4_WATER.txt
@@ -0,0 +1 @@
+
diff --git a/matRad/doseCalc/topas/materialConverter/definedMaterials/MCsquare.txt.in b/matRad/doseCalc/topas/materialConverter/definedMaterials/MCsquare.txt.in
new file mode 100644
index 000000000..6deb183f1
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/definedMaterials/MCsquare.txt.in
@@ -0,0 +1,27 @@
+# -- These Materials recreate the Material lookup as defined by MCsquare for 100% compatability. Note that the reference does some approximations (e.g., no Argon in Air) to spare Material overhead.
+sv:Ge/Patient/SchneiderElements = 7 "Hydrogen" "Carbon" "Nitrogen" "Oxygen" "Phosphorus" "Calcium" "Titanium"
+uv:Ge/Patient/SchneiderMaterialsWeight1 = 7 0.0 0.0001 0.7650 0.2349 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight2 = 7 0.1040 0.1061 0.0313 0.7566 0.002 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight3 = 7 0.116 0.683 0.002 0.199 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight4 = 7 0.113 0.569 0.009 0.309 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight5 = 7 0.111 0.460 0.015 0.413 0.001 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight6 = 7 0.1085 0.3575 0.022 0.511 0.001 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight7 = 7 0.1065 0.2855 0.026 0.581 0.001 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight8 = 7 0.104 0.135 0.030 0.729 0.002 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight9 = 7 0.0955 0.210 0.063 0.6315 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight10 = 7 0.095 0.457 0.025 0.357 0.021 0.045 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight11 = 7 0.089 0.425 0.027 0.365 0.030 0.064 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight12 = 7 0.082 0.393 0.029 0.374 0.039 0.083 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight13 = 7 0.076 0.363 0.030 0.382 0.047 0.102 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight14 = 7 0.0713 0.3363 0.0321 0.3886 0.0542 0.1175 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight15 = 7 0.066 0.311 0.033 0.396 0.061 0.133 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight16 = 7 0.061 0.288 0.035 0.402 0.067 0.147 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight17 = 7 0.056 0.267 0.036 0.4075 0.0735 0.160 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight18 = 7 0.0525 0.2475 0.037 0.4135 0.0785 0.171 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight19 = 7 0.049 0.228 0.038 0.419 0.084 0.182 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight20 = 7 0.045 0.211 0.039 0.423 0.089 0.193 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight21 = 7 0.042 0.195 0.040 0.428 0.093 0.202 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight22 = 7 0.039 0.180 0.041 0.432 0.097 0.211 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight23 = 7 0.036 0.166 0.042 0.435 0.101 0.220 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight24 = 7 0.034 0.156 0.042 0.438 0.104 0.226 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight25 = 7 0.0 0.0 0.0 0.0 0.0 0.0 1.0
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/materialConverter/definedMaterials/Water.txt b/matRad/doseCalc/topas/materialConverter/definedMaterials/Water.txt
new file mode 100644
index 000000000..5a7981061
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/definedMaterials/Water.txt
@@ -0,0 +1,5 @@
+# Hand-defined water
+sv:Ma/Water/Components = 2 "Hydrogen" "Oxygen"
+uv:Ma/Water/Fractions = 2 0.111894 0.888106
+d:Ma/Water/Density = 1.0 g/cm3
+d:Ma/Water/MeanExcitationEnergy = 78.0 eV
diff --git a/matRad/doseCalc/topas/materialConverter/definedMaterials/advanced.txt.in b/matRad/doseCalc/topas/materialConverter/definedMaterials/advanced.txt.in
new file mode 100644
index 000000000..247140abd
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/definedMaterials/advanced.txt.in
@@ -0,0 +1,26 @@
+# -- Define Materials used for HU
+sv:Ge/Patient/SchneiderElements = 15 "Hydrogen" "Carbon" "Nitrogen" "Oxygen" "Magnesium" "Phosphorus" "Sulfur" "Chlorine" "Argon" "Calcium" "Sodium" "Potassium" "Titanium" "Iron" "Zinc"
+uv:Ge/Patient/SchneiderMaterialsWeight1 = 15 0.0 0.0 0.755 0.232 0.0 0.0 0.0 0.0 0.013 0.0 0.0 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight2 = 15 0.103 0.105 0.031 0.749 0.0 0.002 0.003 0.003 0.0 0.0 0.002 0.002 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight3 = 15 0.116 0.681 0.002 0.198 0.0 0.0 0.001 0.001 0.0 0.0 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight4 = 15 0.113 0.567 0.009 0.308 0.0 0.0 0.001 0.001 0.0 0.0 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight5 = 15 0.110 0.458 0.015 0.411 0.0 0.001 0.002 0.002 0.0 0.0 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight6 = 15 0.108 0.356 0.022 0.509 0.0 0.001 0.002 0.002 0.0 0.0 0.0 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight7 = 15 0.106 0.284 0.026 0.578 0.0 0.001 0.002 0.002 0.0 0.0 0.0 0.001 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight8 = 15 0.103 0.134 0.030 0.723 0.0 0.002 0.002 0.002 0.0 0.0 0.002 0.002 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight9 = 15 0.094 0.207 0.062 0.622 0.0 0.0 0.006 0.003 0.0 0.0 0.006 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight10 = 15 0.095 0.455 0.025 0.355 0.0 0.021 0.001 0.001 0.0 0.045 0.001 0.001 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight11 = 15 0.089 0.423 0.027 0.363 0.0 0.030 0.001 0.001 0.0 0.064 0.001 0.001 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight12 = 15 0.082 0.391 0.029 0.372 0.0 0.039 0.001 0.001 0.0 0.083 0.001 0.001 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight13 = 15 0.076 0.361 0.030 0.380 0.001 0.047 0.002 0.001 0.0 0.101 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight14 = 15 0.071 0.335 0.032 0.387 0.001 0.054 0.002 0.0 0.0 0.117 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight15 = 15 0.066 0.310 0.033 0.394 0.001 0.061 0.002 0.0 0.0 0.132 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight16 = 15 0.061 0.287 0.035 0.400 0.001 0.067 0.002 0.0 0.0 0.146 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight17 = 15 0.056 0.265 0.036 0.405 0.002 0.073 0.003 0.0 0.0 0.159 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight18 = 15 0.052 0.246 0.037 0.411 0.002 0.078 0.003 0.0 0.0 0.170 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight19 = 15 0.049 0.227 0.038 0.416 0.002 0.083 0.003 0.0 0.0 0.181 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight20 = 15 0.045 0.210 0.039 0.420 0.002 0.088 0.003 0.0 0.0 0.192 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight21 = 15 0.042 0.194 0.040 0.425 0.002 0.092 0.003 0.0 0.0 0.201 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight22 = 15 0.039 0.179 0.041 0.429 0.002 0.096 0.003 0.0 0.0 0.210 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight23 = 15 0.036 0.165 0.042 0.432 0.002 0.100 0.003 0.0 0.0 0.219 0.001 0.0 0.0 0.0 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight24 = 15 0.034 0.155 0.042 0.435 0.002 0.103 0.003 0.0 0.0 0.225 0.001 0.0 0.0 0.0 0.0
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/materialConverter/definedMaterials/default.txt.in b/matRad/doseCalc/topas/materialConverter/definedMaterials/default.txt.in
new file mode 100644
index 000000000..f24940bf2
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/definedMaterials/default.txt.in
@@ -0,0 +1,5 @@
+# -- Define Materials used for HU
+sv:Ge/Patient/SchneiderElements = 5 "Hydrogen" "Oxygen" "Nitrogen" "Carbon" "Phosphorus"
+uv:Ge/Patient/SchneiderMaterialsWeight1 = 5 0.0 0.23479269 0.76508170 0.00012561 0.0
+uv:Ge/Patient/SchneiderMaterialsWeight2 = 5 0.111894 0.888106 0.0 0.0 0.0
+dv:Ge/Patient/SchneiderMaterialMeanExcitationEnergy = 2 85.7 78.0 eV
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/materialConverter/densityCorrection/Schneider_TOPAS.dat b/matRad/doseCalc/topas/materialConverter/densityCorrection/Schneider_TOPAS.dat
new file mode 100644
index 000000000..658600b16
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/densityCorrection/Schneider_TOPAS.dat
@@ -0,0 +1 @@
+9.35212 5.55269 4.14652 3.41395 2.9645 2.66061 2.44144 2.27588 2.1464 2.04239 1.95698 1.8856 1.82506 1.77307 1.72791 1.68835 1.6534 1.62229 1.59442 1.56932 1.54659 1.52591 1.50701 1.48968 1.47373 1.45899 1.44534 1.43265 1.42084 1.40981 1.39949 1.3898 1.38071 1.37214 1.36406 1.35643 1.34921 1.34237 1.33587 1.3297 1.32383 1.31824 1.31291 1.30781 1.30295 1.29829 1.29383 1.28956 1.28546 1.28152 1.1284 1.12519 1.12209 1.11912 1.11625 1.11348 1.11081 1.10823 1.10574 1.10333 1.101 1.09875 1.09657 1.09445 1.0924 1.09041 1.08848 1.08661 1.08479 1.08303 1.08131 1.07964 1.07801 1.07643 1.0749 1.0734 1.07194 1.07052 1.06913 1.06778 1.06646 1.06518 1.06392 1.0627 1.0615 1.06033 1.05919 1.05808 1.05698 1.05592 1.05487 1.05386 1.05286 1.05188 1.05092 1.04999 1.04907 1.04817 1.04728 1.04643 1.04558 1.04475 1.04394 1.04314 1.04235 1.04158 1.04084 1.04009 1.03936 1.03866 1.03796 1.03726 1.0366 1.03593 1.03527 1.03463 1.03401 1.03339 1.03277 1.03218 1.03159 1.03101 1.03044 1.02988 1.02933 1.02878 1.02825 1.02772 1.0272 1.0267 1.02619 1.0257 1.02522 1.02473 1.02426 1.02379 1.02334 1.02288 1.02243 1.022 1.02156 1.02113 1.02072 1.02029 1.01988 1.01948 1.01909 1.01869 1.0183 1.01793 1.01754 1.01717 1.0168 1.01644 1.01608 1.01572 1.01538 1.01503 1.01469 1.01436 1.01403 1.01369 1.01337 1.01306 1.01273 1.01242 1.01212 1.01181 1.0115 1.01121 1.01092 1.01062 1.01033 1.01006 1.00977 1.00949 1.00922 1.00895 1.00867 1.00842 1.00815 1.00789 1.00763 1.00738 1.00713 1.00687 1.00663 1.00639 1.00614 1.00591 1.00568 1.00544 1.0052 1.00498 1.00476 1.00453 1.00431 1.00409 1.00387 1.00366 1.00345 1.00323 1.00302 1.00282 1.00261 1.00241 1.00221 1.00201 1.00181 1.00162 1.00143 1.00123 1.00104 1.00086 1.00067 1.00049 1.0003 1.00012 0.99994 0.99977 0.99959 0.99941 0.99924 0.99907 0.9989 0.99873 0.99856 0.9984 0.99824 0.99807 0.99791 0.99775 0.99759 0.99744 0.99728 0.99713 0.99697 0.99682 0.99667 0.99652 0.99637 0.99623 0.99608 0.99594 0.99579 0.99565 0.99551 0.99537 0.99523 0.99509 0.99496 0.99482 0.99469 0.99455 0.99442 0.99429 0.99416 0.99403 0.9939 0.99378 0.99365 0.99352 0.9934 0.99328 0.99315 0.99303 0.99292 0.99279 0.99267 0.99256 0.99244 0.99232 0.99221 0.99209 0.99198 0.99187 0.99176 0.99164 0.99153 0.99143 0.99131 0.9912 0.9911 0.991 0.99088 0.99078 0.99068 0.99057 0.99047 0.99037 0.99027 0.99016 0.99006 0.98997 0.98987 0.98977 0.98967 0.98958 0.98948 0.98939 0.98929 0.98919 0.9891 0.98901 0.98892 0.98882 0.98873 0.98864 0.98855 0.98846 0.98838 0.98828 0.9882 0.98811 0.98803 0.98794 0.98785 0.98777 0.98768 0.9876 0.98752 0.98743 0.98735 0.98727 0.98719 0.9871 0.98703 0.98695 0.98687 0.98679 0.98671 0.98663 0.98655 0.98648 0.9864 0.98633 0.98625 0.98617 0.9861 0.98602 0.98595 0.98588 0.9858 0.98573 0.98566 0.98559 0.98552 0.98545 0.98538 0.9853 0.98524 0.98517 0.9851 0.98503 0.98496 0.98489 0.98482 0.98476 0.98469 0.98462 0.98456 0.98449 0.98443 0.98436 0.9843 0.98423 0.98417 0.98411 0.98404 0.98398 0.98392 0.98386 0.98379 0.98373 0.98367 0.98361 0.98355 0.98349 0.98343 0.98337 0.98331 0.98325 0.98319 0.98314 0.98308 0.98302 0.98296 0.98291 0.98285 0.98279 0.98274 0.98268 0.98263 0.98257 0.98251 0.98246 0.98241 0.98235 0.9823 0.98224 0.98219 0.98214 0.98208 0.98203 0.98198 0.98193 0.98188 0.98182 0.98177 0.98172 0.98167 0.98162 0.98157 0.98152 0.98147 0.98142 0.98137 0.98132 0.98127 0.98122 0.98118 0.98113 0.98108 0.98103 0.98098 0.98094 0.98089 0.98084 0.98079 0.98075 0.98071 0.98066 0.98061 0.98057 0.98052 0.98047 0.98043 0.98039 0.98034 0.9803 0.98025 0.98021 0.98016 0.98012 0.98008 0.98003 0.97999 0.97995 0.9799 0.97986 0.97982 0.97978 0.97974 0.9797 0.97966 0.97961 0.97957 0.97953 0.97949 0.97945 0.97941 0.97937 0.97933 0.97929 0.97925 0.97921 0.97917 0.97913 0.97909 0.97905 0.97902 0.97898 0.97894 0.9789 0.97886 0.97882 0.97878 0.97875 0.97871 0.97867 0.97864 0.9786 0.97856 0.97853 0.97849 0.97845 0.97841 0.97838 0.97835 0.97831 0.97827 0.97824 0.9782 0.97817 0.97813 0.9781 0.97806 0.97803 0.97799 0.97796 0.97793 0.97789 0.97786 0.97782 0.97779 0.97776 0.97772 0.97769 0.97766 0.97762 0.97759 0.97756 0.97753 0.97749 0.97746 0.97743 0.9774 0.97736 0.97733 0.9773 0.97727 0.97724 0.97721 0.97717 0.97714 0.97711 0.97708 0.97705 0.97702 0.97699 0.97696 0.97693 0.9769 0.97687 0.97684 0.97681 0.97678 0.97675 0.97672 0.97669 0.97666 0.97663 0.9766 0.97658 0.97654 0.97651 0.97649 0.97646 0.97643 0.9764 0.97637 0.97634 0.97632 0.97629 0.97626 0.97623 0.9762 0.97618 0.97615 0.97612 0.9761 0.97607 0.97604 0.97602 0.97599 0.97596 0.97593 0.97591 0.97588 0.97585 0.97583 0.9758 0.97577 0.97575 0.97573 0.9757 0.97567 0.97565 0.97562 0.97559 0.97557 0.97555 0.97552 0.9755 0.97547 0.97544 0.97542 0.9754 0.97537 0.97534 0.97532 0.9753 0.97527 0.97525 0.97522 0.9752 0.97517 0.97515 0.97513 0.9751 0.97508 0.97506 0.97503 0.97501 0.97499 0.97496 0.97494 0.97492 0.97489 0.97487 0.97485 0.97482 0.9748 0.97478 0.97475 0.97473 0.97471 0.97469 0.97466 0.97464 0.97462 0.9746 0.97458 0.97455 0.97453 0.97451 0.97449 0.97447 0.97444 0.97442 0.9744 0.97438 0.97436 0.97433 0.97432 0.97429 0.97427 0.97425 0.97423 0.97421 0.97419 0.97417 0.97415 0.97413 0.97411 0.97409 0.97407 0.97404 0.97402 0.974 0.97398 0.97396 0.97394 0.97392 0.9739 0.97388 0.97386 0.97384 0.97382 0.9738 0.97378 0.97376 0.97375 0.97373 0.97371 0.97369 0.97367 0.97365 0.97363 0.97361 0.97359 0.97357 0.97355 0.97353 0.97352 0.9735 0.97348 0.97346 0.97344 0.97342 0.97341 0.97338 0.97337 0.97335 0.97333 0.97331 0.97329 0.97328 0.97326 0.97324 0.97322 0.9732 0.97319 0.97317 0.97315 0.97313 0.97311 0.9731 0.97308 0.97306 0.97305 0.97303 0.97301 0.97299 0.97298 0.97296 0.97294 0.97292 0.97291 0.97289 0.97287 0.97286 0.97284 0.97282 0.97281 0.97279 0.97277 0.97276 0.97274 0.97272 0.97271 0.97269 0.97267 0.97266 0.97264 0.97262 0.97261 0.97259 0.97258 0.97256 0.97254 0.97253 0.97251 0.97249 0.97248 0.97246 0.97245 0.97243 0.97242 0.9724 0.97239 0.97237 0.97235 0.97234 0.97232 0.97231 0.97229 0.97228 0.97226 0.97225 0.97223 0.97222 0.9722 0.97218 0.97217 0.97216 0.97214 0.97213 0.97211 0.9721 0.97208 0.97207 0.97205 0.97203 0.97202 0.97201 0.97199 0.97198 0.97196 0.97195 0.97193 0.97192 0.97191 0.97189 0.97188 0.97186 0.97185 0.97183 0.97182 0.97181 0.97179 0.97178 0.97176 0.97175 0.97174 0.97172 0.97171 0.97169 0.97168 0.97167 0.97165 0.97164 0.97163 0.97161 0.9716 0.97158 0.97157 0.97156 0.97154 0.97153 0.97152 0.9715 0.97149 0.97148 0.97146 0.97145 0.97144 0.97142 0.97141 0.9714 0.97139 0.97137 0.97136 0.97135 0.97133 0.97132 0.97131 0.9713 0.97128 0.97127 0.97126 0.97124 0.97123 0.97122 0.97121 0.97119 0.97118 0.97117 0.97116 0.97114 0.97113 0.97112 0.97111 0.97109 0.97108 0.97107 0.97106 0.97105 0.97103 0.97102 0.97101 0.971 0.97098 0.97097 0.97096 0.97095 0.97094 0.97093 0.97091 0.9709 0.97089 0.97088 0.97086 0.97085 0.97084 0.97083 0.97082 0.97081 0.9708 0.97078 0.97077 0.97076 0.97075 0.97074 0.97073 0.97072 0.9707 0.97069 0.97068 0.97067 0.97066 0.97065 0.97064 0.97062 0.97061 0.9706 0.97059 0.97058 0.97057 0.97056 0.94514 0.94512 0.94511 0.9451 0.94509 0.94508 0.94507 0.94506 0.94505 0.94504 0.94503 0.94502 0.94501 0.945 0.94499 0.94498 0.94497 0.94496 0.94495 0.94494 0.94492 0.94492 0.9449 0.94489 0.94488 0.94487 0.94486 0.94485 0.94484 0.94483 0.94482 0.94481 0.94569 0.94582 0.94594 0.94607 0.94619 0.95156 0.95168 0.95181 0.95194 0.95206 0.95219 0.95231 0.95244 0.95256 0.95268 0.95281 0.95293 0.95306 0.95318 0.9533 0.95342 0.95355 0.95367 0.95379 0.95391 0.95403 0.95416 0.95428 0.9544 0.95452 0.95464 0.95476 0.95488 0.955 0.95512 0.96074 0.96086 0.96098 0.9611 0.96122 0.96134 0.96146 0.96158 0.9617 0.96181 0.96193 0.96205 0.96217 0.96229 0.9624 0.96252 0.96264 0.96275 0.96287 0.96298 0.9631 0.96322 0.96333 0.96345 0.96356 0.96367 0.96379 0.9639 0.96402 0.96413 0.96818 0.96829 0.96841 0.96852 0.96864 0.96874 0.96886 0.96897 0.96909 0.96919 0.96931 0.96943 0.96953 0.96964 0.96976 0.96986 0.96998 0.97009 0.9702 0.97031 0.97042 0.97053 0.97064 0.97075 0.97087 0.97098 0.9711 0.97123 0.97135 0.97146 0.97526 0.97538 0.97549 0.97561 0.97573 0.97584 0.97596 0.97608 0.97752 0.97848 0.97944 0.98701 0.98798 0.98895 0.98992 0.99089 0.99197 0.99181 0.99166 0.99151 0.99135 0.99119 0.99104 0.99088 0.99073 0.99057 0.99042 0.99027 0.99012 0.98997 0.98982 0.98967 0.98951 0.98936 0.98874 0.98811 0.98749 0.98687 0.98625 0.98563 0.98502 0.9844 0.98378 0.98317 0.98256 0.98195 0.98134 0.98073 0.98013 0.97952 0.97892 0.97832 0.97771 0.97711 0.97651 0.97592 0.97532 0.97472 0.97414 0.97355 0.97295 0.97236 0.97178 0.97119 0.9706 0.97002 0.96943 0.96885 0.96828 0.9677 0.96712 0.96654 0.96596 0.97464 0.97406 0.97348 0.97291 0.97233 0.97176 0.97119 0.97062 0.97005 0.96948 0.96891 0.96834 0.96777 0.96721 0.96665 0.96609 0.96553 0.96497 0.96441 0.96385 0.9633 1.00233 1.00225 1.00216 1.00208 1.002 1.00192 1.00184 1.00175 1.00167 1.00159 1.00151 1.00143 1.00134 1.00126 1.00118 1.0011 1.00102 1.00093 1.00085 1.00035 1.00027 1.00019 1.0001 1.00002 0.99994 0.99987 0.99979 0.9997 0.99962 0.99954 0.99946 0.99938 0.9993 0.99922 0.99914 0.99906 0.99899 0.9989 0.99882 0.99874 0.99867 0.99859 0.9985 0.99842 0.99835 0.99827 0.99819 0.99811 0.99803 0.99795 0.99788 0.9978 0.99771 0.99764 0.99756 0.99749 0.99741 0.99732 0.99725 0.99717 0.9971 0.99702 0.99694 0.99686 0.99678 0.99671 0.99663 0.99655 0.99647 0.9964 0.99632 0.99625 0.99616 0.99609 0.99601 0.99594 0.99586 0.99578 0.99571 0.99563 0.99556 0.99548 0.9954 0.99533 0.99525 0.99518 0.99511 0.99502 0.99495 0.99488 0.9948 0.99473 0.99465 0.99457 0.9945 0.99443 0.99435 0.99427 0.9942 1.00458 1.0045 1.00443 1.00435 1.00428 1.0042 1.00413 1.00406 1.00397 1.0039 1.00383 1.00376 1.00368 1.0036 1.00353 1.00346 1.00339 1.00331 1.00323 1.00316 1.00309 1.00301 1.00294 1.00286 1.00279 1.00272 1.00265 1.00258 1.0025 1.00243 1.00235 1.00228 1.00221 1.00213 1.00206 1.00199 1.00192 1.00185 1.00177 1.0017 1.00163 1.00156 1.00149 1.00141 1.00134 1.00127 1.0012 1.00113 1.00105 1.00098 1.00091 1.00084 1.00077 1.00069 1.00062 1.00055 1.00048 1.00041 1.00033 1.00027 1.0002 1.00013 1.00006 0.99998 0.99991 0.99984 0.99977 0.99971 0.99963 0.99956 0.99949 0.99942 0.99935 0.99928 0.99921 0.99914 0.99907 0.99901 0.99893 0.99886 0.99879 0.99873 0.99866 0.99858 0.99852 0.99845 0.99838 0.99831 0.99824 0.99817 0.9981 0.99804 0.99797 0.99789 0.99783 0.99776 0.99769 0.99763 0.99755 0.99749 1.00929 1.00922 1.00916 1.00908 1.00901 1.00895 1.00888 1.00881 1.00874 1.00867 1.00861 1.00854 1.00847 1.0084 1.00833 1.00827 1.0082 1.00813 1.00806 1.00799 1.00793 1.00786 1.0078 1.00772 1.00766 1.00759 1.00753 1.00746 1.00739 1.00732 1.00726 1.00719 1.00713 1.00705 1.00699 1.00692 1.00686 1.00679 1.00672 1.00666 1.00659 1.00653 1.00646 1.00639 1.00633 1.00626 1.0062 1.00613 1.00606 1.006 1.00593 1.00587 1.0058 1.00573 1.00567 1.00561 1.00554 1.00548 1.00541 1.00534 1.00528 1.00522 1.00515 1.00508 1.00502 1.00496 1.00489 1.00483 1.00476 1.0047 1.00463 1.00457 1.00451 1.00444 1.00438 1.00431 1.00425 1.00419 1.00412 1.00406 1.00399 1.00393 1.00387 1.0038 1.00374 1.00368 1.00361 1.00355 1.00348 1.00342 1.00336 1.0033 1.00324 1.00317 1.00311 1.00305 1.00298 1.00292 1.00285 1.00279 1.01335 1.01329 1.01323 1.01316 1.0131 1.01304 1.01298 1.01292 1.01285 1.01278 1.01272 1.01266 1.0126 1.01253 1.01247 1.01241 1.01235 1.01229 1.01222 1.01216 1.0121 1.01204 1.01198 1.01191 1.01185 1.01179 1.01173 1.01167 1.0116 1.01154 1.01148 1.01142 1.01136 1.0113 1.01124 1.01118 1.01112 1.01106 1.01099 1.01093 1.01087 1.01081 1.01075 1.01069 1.01063 1.01057 1.01051 1.01045 1.01038 1.01033 1.01027 1.01021 1.01015 1.01008 1.01003 1.00997 1.00991 1.00985 1.00978 1.00972 1.00967 1.00961 1.00955 1.00948 1.00943 1.00937 1.00931 1.00925 1.00919 1.00913 1.00907 1.00902 1.00896 1.00889 1.00883 1.00878 1.00872 1.00866 1.0086 1.00854 1.00848 1.00843 1.00837 1.0083 1.00825 1.00819 1.00813 1.00808 1.00801 1.00796 1.0079 1.00784 1.00779 1.00772 1.00767 1.00761 1.00755 1.0075 1.00743 1.00738 1.01627 1.01621 1.01615 1.01609 1.01603 1.01598 1.01592 1.01587 1.0158 1.01575 1.01569 1.01563 1.01558 1.01551 1.01546 1.0154 1.01535 1.01529 1.01523 1.01517 1.01512 1.01506 1.015 1.01494 1.01489 1.01483 1.01478 1.01472 1.01466 1.0146 1.01455 1.01449 1.01444 1.01438 1.01432 1.01427 1.01421 1.01416 1.01409 1.01404 1.01398 1.01393 1.01388 1.01381 1.01376 1.01371 1.01365 1.0136 1.01353 1.01348 1.01343 1.01337 1.01332 1.01326 1.0132 1.01315 1.0131 1.01304 1.01298 1.01293 1.01287 1.01282 1.01277 1.01271 1.01265 1.0126 1.01255 1.01249 1.01243 1.01238 1.01232 1.01227 1.01222 1.01216 1.01211 1.01205 1.012 1.01195 1.01189 1.01183 1.01178 1.01173 1.01168 1.01162 1.01156 1.01151 1.01146 1.01141 1.01135 1.01129 1.01124 1.01119 1.01114 1.01108 1.01103 1.01097 1.01092 1.01087 1.01081 1.01076 1.01979 1.01974 1.01968 1.01962 1.01957 1.01952 1.01947 1.01942 1.01936 1.0193 1.01925 1.0192 1.01915 1.01909 1.01904 1.01899 1.01894 1.01888 1.01883 1.01877 1.01872 1.01867 1.01862 1.01856 1.01851 1.01846 1.01841 1.01836 1.0183 1.01825 1.0182 1.01815 1.0181 1.01804 1.01799 1.01794 1.01789 1.01783 1.01778 1.01773 1.01768 1.01763 1.01758 1.01752 1.01747 1.01742 1.01737 1.01732 1.01726 1.01721 1.01716 1.01711 1.01706 1.017 1.01695 1.0169 1.01685 1.0168 1.01675 1.0167 1.01665 1.0166 1.01655 1.01649 1.01644 1.01639 1.01634 1.0163 1.01624 1.01619 1.01614 1.01609 1.01604 1.01599 1.01594 1.01589 1.01584 1.01579 1.01573 1.01569 1.01564 1.01559 1.01554 1.01548 1.01543 1.01539 1.01534 1.01529 1.01523 1.01518 1.01514 1.01509 1.01504 1.01499 1.01494 1.01489 1.01484 1.01479 1.01474 1.01469 1.02359 1.02354 1.02349 1.02344 1.02339 1.02334 1.02329 1.02324 1.02319 1.02314 1.02309 1.02304 1.023 1.02294 1.02289 1.02285 1.0228 1.02275 1.0227 1.02265 1.0226 1.02255 1.02251 1.02245 1.0224 1.02236 1.02231 1.02226 1.02221 1.02216 1.02211 1.02207 1.02202 1.02196 1.02192 1.02187 1.02182 1.02178 1.02172 1.02168 1.02163 1.02158 1.02154 1.02148 1.02144 1.02139 1.02134 1.0213 1.02124 1.0212 1.02115 1.0211 1.02106 1.021 1.02096 1.02091 1.02087 1.02082 1.02077 1.02072 1.02067 1.02063 1.02058 1.02053 1.02049 1.02043 1.02039 1.02035 1.02029 1.02025 1.0202 1.02016 1.02011 1.02006 1.02002 1.01996 1.01992 1.01988 1.01983 1.01978 1.01973 1.01969 1.01964 1.01959 1.01955 1.0195 1.01945 1.01941 1.01936 1.01932 1.01927 1.01922 1.01918 1.01913 1.01909 1.01903 1.01899 1.01895 1.0189 1.01886 1.02794 1.02789 1.02785 1.0278 1.02776 1.02771 1.02767 1.02762 1.02756 1.02752 1.02748 1.02744 1.02739 1.02734 1.02729 1.02725 1.0272 1.02717 1.02711 1.02706 1.02702 1.02697 1.02693 1.02688 1.02684 1.02679 1.02675 1.0267 1.02666 1.02661 1.02657 1.02652 1.02648 1.02643 1.02638 1.02635 1.0263 1.02626 1.0262 1.02616 1.02611 1.02607 1.02603 1.02598 1.02594 1.02589 1.02585 1.0258 1.02576 1.02571 1.02567 1.02563 1.02558 1.02554 1.02549 1.02545 1.02541 1.02536 1.02531 1.02526 1.02523 1.02519 1.02514 1.02509 1.02505 1.025 1.02496 1.02493 1.02487 1.02483 1.02479 1.02474 1.0247 1.02465 1.02461 1.02457 1.02453 1.02448 1.02444 1.0244 1.02435 1.02431 1.02427 1.02421 1.02417 1.02414 1.02409 1.02405 1.024 1.02396 1.02391 1.02387 1.02384 1.02379 1.02374 1.0237 1.02366 1.02361 1.02357 1.02353 1.03085 1.03081 1.03077 1.03073 1.03068 1.03064 1.0306 1.03055 1.03051 1.03046 1.03042 1.03038 1.03034 1.03029 1.03025 1.03021 1.03017 1.03012 1.03008 1.03004 1.03 1.02995 1.02992 1.02986 1.02983 1.02978 1.02975 1.0297 1.02966 1.02961 1.02957 1.02954 1.02949 1.02945 1.0294 1.02937 1.02932 1.02928 1.02923 1.02919 1.02916 1.02911 1.02908 1.02902 1.02899 1.02894 1.0289 1.02887 1.02882 1.02878 1.02873 1.0287 1.02866 1.02861 1.02857 1.02853 1.02849 1.02845 1.0284 1.02836 1.02833 1.02828 1.02825 1.02819 1.02816 1.02812 1.02807 1.02804 1.02799 1.02795 1.02791 1.02788 1.02783 1.02779 1.02775 1.0277 1.02767 1.02763 1.02758 1.02754 1.02751 1.02746 1.02742 1.02738 1.02735 1.0273 1.02726 1.02723 1.02717 1.02714 1.0271 1.02707 1.02702 1.02698 1.02694 1.0269 1.02686 1.02682 1.02678 1.02673 1.03276 1.03272 1.03268 1.03264 1.03259 1.03256 1.03252 1.03248 1.03243 1.0324 1.03235 1.03232 1.03228 1.03224 1.03219 1.03216 1.03212 1.03208 1.03204 1.032 1.03196 1.03192 1.03188 1.03183 1.0318 1.03176 1.03172 1.03169 1.03164 1.0316 1.03156 1.03153 1.03149 1.03144 1.03141 1.03136 1.03133 1.03129 1.03124 1.03121 1.03117 1.03113 1.0311 1.03105 1.03101 1.03097 1.03094 1.0309 1.03085 1.03082 1.03078 1.03074 1.03071 1.03067 1.03062 1.03059 1.03055 1.03051 1.03047 1.03043 1.03039 1.03036 1.03032 1.03027 1.03024 1.0302 1.03016 1.03013 1.03009 1.03004 1.03001 1.02998 1.02993 1.02989 1.02986 1.02981 1.02978 1.02975 1.02971 1.02966 1.02963 1.02959 1.02955 1.02951 1.02948 1.02944 1.0294 1.02936 1.02932 1.02929 1.02925 1.02921 1.02918 1.02914 1.02909 1.02906 1.02903 1.02899 1.02894 1.02891 1.03634 1.03631 1.03626 1.03623 1.03619 1.03615 1.03611 1.03608 1.03603 1.036 1.03596 1.03593 1.03589 1.03585 1.03581 1.03578 1.03574 1.03571 1.03566 1.03563 1.03559 1.03556 1.03552 1.03548 1.03544 1.03541 1.03537 1.03534 1.03529 1.03526 1.03522 1.03518 1.03515 1.03511 1.03507 1.03503 1.035 1.03496 1.03492 1.03488 1.03485 1.03482 1.03478 1.03474 1.0347 1.03467 1.03463 1.0346 1.03456 1.03452 1.03449 1.03445 1.03442 1.03438 1.03434 1.03431 1.03427 1.03423 1.0342 1.03416 1.03412 1.03408 1.03405 1.03402 1.03397 1.03394 1.03391 1.03387 1.03383 1.0338 1.03376 1.03373 1.03369 1.03365 1.03362 1.03358 1.03355 1.03352 1.03347 1.03344 1.03341 1.03337 1.03334 1.0333 1.03326 1.03323 1.03319 1.03316 1.03312 1.03308 1.03305 1.03301 1.03298 1.03294 1.0329 1.03287 1.03284 1.0328 1.03276 1.03273 1.03851 1.03848 1.03844 1.03841 1.03837 1.03833 1.0383 1.03826 1.03823 1.03819 1.03816 1.03812 1.03809 1.03805 1.03802 1.03798 1.03795 1.03792 1.03788 1.03785 1.03781 1.03778 1.03774 1.03771 1.03767 1.03764 1.0376 1.03757 1.03752 1.0375 1.03746 1.03743 1.0374 1.03735 1.03732 1.03729 1.03726 1.03722 1.03718 1.03715 1.03711 1.03709 1.03705 1.03701 1.03697 1.03694 1.03692 1.03688 1.03684 1.0368 1.03677 1.03674 1.03671 1.03667 1.03663 1.0366 1.03657 1.03653 1.0365 1.03646 1.03643 1.0364 1.03636 1.03633 1.0363 1.03626 1.03623 1.03619 1.03616 1.03613 1.03609 1.03606 1.03603 1.03599 1.03596 1.03593 1.03589 1.03586 1.03583 1.03579 1.03576 1.03573 1.03569 1.03566 1.03563 1.03559 1.03556 1.03553 1.03548 1.03546 1.03543 1.03539 1.03536 1.03532 1.03528 1.03526 1.03523 1.03519 1.03515 1.03512 1.04093 1.0409 1.04087 1.04083 1.0408 1.04076 1.04074 1.0407 1.04067 1.04063 1.0406 1.04057 1.04054 1.0405 1.04047 1.04043 1.04041 1.04037 1.04034 1.0403 1.04027 1.04024 1.04021 1.04017 1.04014 1.04011 1.04008 1.04004 1.04001 1.03998 1.03994 1.03992 1.03988 1.03985 1.03981 1.03978 1.03975 1.03972 1.03968 1.03965 1.03962 1.03959 1.03956 1.03952 1.03949 1.03945 1.03943 1.0394 1.03936 1.03933 1.03929 1.03927 1.03923 1.0392 1.03917 1.03913 1.03911 1.03907 1.03904 1.03901 1.03898 1.03895 1.03891 1.03888 1.03885 1.03882 1.03879 1.03875 1.03872 1.03869 1.03866 1.03863 1.03859 1.03856 1.03853 1.0385 1.03847 1.03844 1.0384 1.03837 1.03835 1.03831 1.03828 1.03825 1.03821 1.03819 1.03815 1.03812 1.03809 1.03805 1.03803 1.038 1.03796 1.03793 1.0379 1.03787 1.03784 1.03781 1.03777 1.03774 1.04358 1.04356 1.04352 1.04349 1.04346 1.04343 1.0434 1.04337 1.04333 1.0433 1.04327 1.04324 1.04321 1.04317 1.04315 1.04311 1.04309 1.04305 1.04302 1.04299 1.04296 1.04293 1.04291 1.04286 1.04284 1.04281 1.04278 1.04275 1.04272 1.04268 1.04266 1.04262 1.0426 1.04256 1.04253 1.0425 1.04247 1.04244 1.04241 1.04237 1.04235 1.04231 1.04229 1.04225 1.04222 1.04219 1.04217 1.04213 1.0421 1.04208 1.04204 1.04202 1.04198 1.04195 1.04192 1.04189 1.04186 1.04183 1.0418 1.04177 1.04174 1.04171 1.04168 1.04164 1.04162 1.04159 1.04156 1.04154 1.04149 1.04147 1.04144 1.04141 1.04138 1.04135 1.04132 1.04129 1.04126 1.04123 1.0412 1.04117 1.04114 1.04111 1.04108 1.04105 1.04102 1.04099 1.04096 1.04093 1.0409 1.04087 1.04084 1.04081 1.04079 1.04075 1.04072 1.04069 1.04067 1.04064 1.0406 1.04058 1.04455 1.04453 1.0445 1.04447 1.04443 1.04441 1.04438 1.04435 1.04431 1.04429 1.04426 1.04423 1.0442 1.04417 1.04414 1.04411 1.04408 1.04406 1.04402 1.044 1.04397 1.04394 1.04391 1.04388 1.04385 1.04382 1.04379 1.04377 1.04373 1.0437 1.04367 1.04365 1.04362 1.04359 1.04356 1.04353 1.0435 1.04347 1.04344 1.04341 1.04339 1.04336 1.04333 1.04329 1.04327 1.04324 1.04321 1.04319 1.04315 1.04313 1.0431 1.04307 1.04304 1.04301 1.04299 1.04295 1.04293 1.0429 1.04287 1.04284 1.04281 1.04279 1.04276 1.04273 1.0427 1.04267 1.04265 1.04261 1.04259 1.04255 1.04253 1.04251 1.04247 1.04244 1.04241 1.04239 1.04236 1.04233 1.0423 1.04228 1.04225 1.04222 1.04219 1.04216 1.04214 1.0421 1.04208 1.04205 1.04202 1.042 1.04196 1.04194 1.04192 1.04188 1.04185 1.04183 1.0418 1.04178 1.04175 1.04171 1.04169 1.04166 1.04163 1.0416 1.04158 1.04155 1.04153 1.04149 1.04146 1.04144 1.04141 1.04138 1.04136 1.04133 1.0413 1.04128 1.04125 1.04122 1.04119 1.04117 1.04113 1.04111 1.04109 1.04105 1.04103 1.041 1.04097 1.04095 1.04092 1.04089 1.04086 1.04084 1.04081 1.04077 1.04075 1.04073 1.0407 1.04067 1.04064 1.04062 1.04059 1.04056 1.04054 1.04051 1.04048 1.04046 1.04043 1.0404 1.04037 1.04035 1.04032 1.04029 1.04027 1.04024 1.04021 1.04018 1.04016 1.04014 1.04011 1.04007 1.04005 1.04003 1.04 1.03997 1.03994 1.03992 1.03989 1.03986 1.03983 1.03981 1.03979 1.03975 1.03973 1.0397 1.03968 1.03965 1.03962 1.0396 1.03957 1.03955 1.03951 1.03949 1.03947 1.03944 1.03941 1.03938 1.03936 1.03934 1.03931 1.03928 1.03925 1.03923 1.03921 1.03918 1.03915 1.03912 1.0391 1.03907 1.03904 1.03902 1.03899 1.03897 1.03894 1.03891 1.03889 1.03886 1.03883 1.03881 1.03878 1.03876 1.03873 1.0387 1.03868 1.03865 1.03863 1.0386 1.03858 1.03855 1.03852 1.0385 1.03847 1.03845 1.03842 1.03839 1.03836 1.03834 1.03832 1.03829 1.03826 1.03824 1.03821 1.03819 1.03816 1.03813 1.03811 1.03809 1.03806 1.03803 1.038 1.03798 1.03796 1.03793 1.03791 1.03788 1.03786 1.03783 1.0378 1.03778 1.03775 1.03773 1.0377 1.03768 1.03766 1.03763 1.0376 1.03757 1.03755 1.03753 1.0375 1.03747 1.03745 1.03743 1.0374 1.03737 1.03735 1.03732 1.03729 1.03727 1.03724 1.03722 1.0372 1.03717 1.03715 1.03712 1.0371 1.03707 1.03704 1.03702 1.03699 1.03697 1.03694 1.03692 1.0369 1.03687 1.03684 1.03682 1.0368 1.03678 1.03675 1.03672 1.0367 1.03667 1.03665 1.03662 1.03659 1.03657 1.03655 1.03652 1.03649 1.03647 1.03645 1.03642 1.0364 1.03637 1.03635 1.03633 1.0363 1.03628 1.03625 1.03623 1.0362 1.03618 1.03616 1.03613 1.03611 1.03608 1.03606 1.03603 1.03601 1.03598 1.03596 1.03593 1.03591 1.03589 1.03586 1.03584 1.03581 1.03579 1.03576 1.03574 1.03571 1.03569 1.03567 1.03564 1.03562 1.03559 1.03557 1.03555 1.03552 1.0355 1.03547 1.03545 1.03543 1.0354 1.03538 1.03535 1.03533 1.03531 1.03528 1.03525 1.03523 1.03521 1.03519 1.03516 1.03513 1.03511 1.03509 1.03506 1.03503 1.03501 1.03499 1.03497 1.03494 1.03491 1.03489 1.03487 1.03485 1.03482 1.0348 1.03478 1.03475 1.03473 1.03471 1.03468 1.03466 1.03463 1.03461 1.03459 1.03456 1.03453 1.03451 1.03449 1.03447 1.03445 1.03442 1.0344 1.03438 1.03435 1.03432 1.0343 1.03428 1.03426 1.03423 1.0342 1.03418 1.03416 1.03414 1.03412 1.03409 1.03407 1.03405 1.03402 1.034 1.03397 1.03395 1.03393 1.03391 1.03389 1.03386 1.03384 1.03381 1.03379 1.03377 1.03374 1.03372 1.0337 1.03368 1.03365 1.03363 1.0336 1.03358 1.03356 1.03353 1.03351 1.03349 1.03347 1.03344 1.03342 1.03339 1.03337 1.03335 1.03333 1.03331 1.03328 1.03326 1.03323 1.03321 1.03319 1.03317 1.03315 1.03312 1.0331 1.03308 1.03305 1.03303 1.03301 1.03299 1.03297 1.03294 1.03291 1.03289 1.03287 1.03285 1.03282 1.0328 1.03278 1.03276 1.03273 1.03271 1.03269 1.03267 1.03264 1.03262 1.0326 1.03258 1.03255 1.03253 1.03251 1.03248 1.03246 1.03244 1.03242 1.0324 1.03237 1.03235 1.03233 1.03231 1.03229 1.03226 1.03224 1.03222 1.0322 1.03217 1.03214 1.03212 1.03211 1.03209 1.03206 1.03203 1.03201 1.032 1.03197 1.03195 1.03192 1.0319 1.03189 1.03186 1.03184 1.03181 1.03179 1.03177 1.03181 1.03185 1.03189 1.03193 1.03197 1.03201 1.03204 1.03209 1.03212 1.03216 1.0322 1.03224 1.03227 1.03232 1.03236 1.03239 1.03244 1.03247 1.03251 1.03255 1.03259 1.03262 1.03267 1.0327 1.03274 1.03278 1.03282 1.03286 1.0329 1.03294 1.03297 1.03301 1.03305 1.03309 1.03313 1.03317 1.0332 1.03324 1.03328 1.03332 1.03336 1.0334 1.03344 1.03347 1.03351 1.03355 1.03359 1.03362 1.03366 1.0337 1.03374 1.03378 1.03382 1.03386 1.03389 1.03393 1.03397 1.03401 1.03404 1.03408 1.03412 1.03416 1.03419 1.03424 1.03428 1.03431 1.03435 1.03439 1.03443 1.03446 1.0345 1.03454 1.03458 1.03461 1.03465 1.03469 1.03473 1.03477 1.0348 1.03484 1.03488 1.03492 1.03495 1.03499 1.03503 1.03507 1.0351 1.03514 1.03518 1.03522 1.03526 1.03529 1.03533 1.03536 1.03541 1.03544 1.03548 1.03551 1.03555 1.03559 1.03563 1.03567 1.0357 1.03574 1.03578 1.03582 1.03585 1.03589 1.03592 1.03596 1.036 1.03604 1.03608 1.03611 1.03615 1.03618 1.03623 1.03626 1.0363 1.03633 1.03637 1.03641 1.03645 1.03648 1.03652 1.03656 1.03659 1.03663 1.03667 1.03671 1.03674 1.03678 1.03681 1.03685 1.03688 1.03692 1.03696 1.037 1.03704 1.03707 1.03711 1.03714 1.03718 1.03722 1.03726 1.03729 1.03733 1.03736 1.0374 1.03744 1.03747 1.03751 1.03755 1.03759 1.03762 1.03766 1.03769 1.03773 1.03776 1.0378 1.03783 1.03787 1.03791 1.03795 1.03799 1.03802 1.03806 1.03809 1.03813 1.03816 1.0382 1.03823 1.03827 1.03831 1.03835 1.03838 1.03842 1.03846 1.03849 1.03853 1.03856 1.0386 1.03863 1.03867 1.0387 1.03874 1.03878 1.03881 1.03885 1.03888 1.03892 1.03896 1.03899 1.03903 1.03907 1.0391 1.03914 1.03918 1.03921 1.03925 1.03928 1.03932 1.03935 1.03939 1.03942 1.03946 1.03949 1.03953 1.03956 1.0396 1.03964 1.03967 1.03971 1.03974 1.03978 1.03981 1.03985 1.03988 1.03992 1.03995 1.03999 1.04002 1.04006 1.0401 1.04013 1.04017 1.0402 1.04024 1.04027 1.04031 1.04034 1.04038 1.04041 1.04045 1.04049 1.04052 1.04056 1.04059 1.04063 1.04066 1.0407 1.04073 1.04077 1.0408 1.04084 1.04087 1.04091 1.04094 1.04098 1.04101 1.04105 1.04108 1.04111 1.04115 1.04118 1.04122 1.04125 1.04129 1.04133 1.04136 1.0414 1.04143 1.04147 1.0415 1.04154 1.04157 1.04161 1.04164 1.04167 1.0417 1.04174 1.04178 1.04181 1.04185 1.04188 1.04192 1.04195 1.04199 1.04202 1.04205 1.04209 1.04212 1.04215 1.04219 1.04223 1.04226 1.0423 1.04233 1.04237 1.0424 1.04243 1.04246 1.0425 1.04253 1.04257 1.04261 1.04264 1.04268 1.04271 1.04274 1.04277 1.04281 1.04284 1.04288 1.04291 1.04295 1.04298 1.04301 1.04305 1.04308 1.04312 1.04315 1.04319 1.04322 1.04325 1.04329 1.04332 1.04335 1.04339 1.04343 1.04346 1.04349 1.04352 1.04356 1.04359 1.04363 1.04366 1.0437 1.04373 1.04376 1.04379 1.04383 1.04387 1.0439 1.04393 1.04396 1.044 1.04403 1.04407 1.0441 1.04413 1.04416 1.0442 1.04423 1.04427 1.0443 1.04433 1.04437 1.0444 1.04444 1.04447 1.0445 1.04453 1.04457 1.0446 1.04464 1.04467 1.0447 1.04474 1.04477 1.04481 1.04484 1.04487 1.0449 1.04494 1.04497 1.045 1.04503 1.04507 1.04511 1.04514 1.04517 1.0452 1.04524 1.04527 1.0453 1.04533 1.04537 1.0454 1.04543 1.04547 1.0455 1.04554 1.04557 1.0456 1.04563 1.04567 1.0457 1.04573 1.04576 1.0458 1.04583 1.04586 1.0459 1.04593 1.04596 1.04599 1.04603 1.04606 1.0461 1.04612 1.04616 1.04619 1.04623 1.04625 1.04629 1.04633 1.04636 1.04639 1.04642 1.04646 1.04648 1.04652 1.04655 1.04659 1.04661 1.04665 1.04669 1.04671 1.04675 1.04678 1.04681 1.04684 1.04688 1.04691 1.04694 1.04697 1.04701 1.04704 1.04707 1.04711 1.04714 1.04717 1.0472 1.04724 1.04726 1.0473 1.04733 1.04736 1.04739 1.04743 1.04746 1.04749 1.04753 1.04756 1.04759 1.04762 1.04766 1.04768 1.04772 1.04775 1.04778 1.04781 1.04785 1.04788 1.04791 1.04795 1.04797 1.04801 1.04804 1.04807 1.0481 1.04814 1.04816 1.0482 1.04823 1.04826 1.0483 1.04833 1.04836 1.04839 1.04842 1.04845 1.04849 1.04852 1.04855 1.04858 1.04861 1.04865 1.04868 1.04871 1.04874 1.04877 1.0488 1.04884 1.04886 1.0489 1.04893 1.04896 1.04899 1.04903 1.04906 1.04909 1.04912 1.04915 1.04918 1.04921 1.04925 1.04928 1.04931 1.04934 1.04937 1.04941 1.04943 1.04947 1.0495 1.04953 1.04956 1.04959 1.04962 1.04966 1.04968 1.04972 1.04975 1.04978 1.04981 1.04984 1.04988 1.0499 1.04994 1.04997 1.05 1.05003 1.05006 1.05009 1.05012 1.05015 1.05019 1.05022 1.05025 1.05028 1.05031 1.05034 1.05037 1.05041 1.05043 1.05047 1.05049 1.05053 1.05056 1.05059 1.05062 1.05065 1.05068 1.05071 1.05075 1.05077 1.05081 1.05083 1.05087 1.0509 1.05093 1.05096 1.05099 1.05102 1.05105 1.05109 1.05111 1.05115 1.05117 1.05121 1.05123 1.05127 1.0513 1.05133 1.05136 1.05139 1.05142 1.05145 1.05148 1.05151 1.05155 1.05157 1.05161 1.05163 1.05167 1.0517 1.05173 1.05176 1.05179 1.05182 1.05185 1.05188 1.05191 1.05194 1.05197 1.052 1.05203 1.05206 1.0521 1.05212 1.05216 1.05218 1.05222 1.05224 1.05228 1.0523 1.05234 1.05236 1.0524 1.05243 1.05246 1.05249 1.05252 1.05255 1.05258 1.05261 1.05264 1.05267 1.0527 1.05273 1.05276 1.05279 1.05282 1.05285 1.05288 1.05291 1.05294 1.05297 1.053 1.05303 1.05306 1.05309 1.05312 1.05315 1.05318 1.05321 1.05324 1.05327 1.0533 1.05333 1.05336 1.05339 1.05342 1.05345 1.05347 1.05351 1.05354 1.05357 1.0536 1.05363 1.05366 1.05368 1.05372 1.05374 1.05378 1.0538 1.05383 1.05386 1.05389 1.05393 1.05395 1.05398 1.05401 1.05404 1.05407 1.0541 1.05413 1.05416 1.05419 1.05422 1.05425 1.05428 1.05431 1.05434 1.05437 1.05439 1.05443 1.05445 1.05449 1.05451 1.05454 1.05457 1.0546 1.05463 1.05466 1.05469 1.05472 1.05475 1.05478 1.05481 1.05483 1.05487 1.05489 1.05492 1.05496 1.05498 1.05501 1.05504 1.05507 1.0551 1.05513 1.05516 1.05519 1.05521 1.05525 1.05527 1.0553 1.05534 1.05536 1.05539 1.05542 1.05545 1.05548 1.05551 1.05553 1.05557 1.05559 1.05562 1.05565 1.05568 1.05571 1.05574 1.05577 1.0558 1.05583 1.05585 1.05588 1.05591 1.05594 1.05597 1.056 1.05603 1.05606 1.05609 1.05611 1.05614 1.05617 1.0562 1.05623 1.05626 1.05628 1.05632 1.05634 1.05637 1.0564 1.05643 1.05646 1.05649 1.05652 1.05654 1.05657 1.0566 1.05663 1.05666 1.05669 1.05672 1.05674 1.05678 1.0568 1.05683 1.05686 1.05689 1.05691 1.05695 1.05697 1.057 1.05703 1.05706 1.05709 1.05711 1.05715 1.05717 1.0572 1.05723 1.05726 1.05728 1.05731 1.05734 1.05737 1.05739 1.05743 1.05746 1.05748 1.05751 1.05754 1.05757 1.05759 1.05763 1.05765 1.05768 1.05771 1.05774 1.05777 1.05779 1.05782 1.05785 1.05788 1.0579 1.05793 1.05796 1.05799 1.05802 1.05805 1.05807 1.0581 1.05813 1.05816 1.05819 1.05821 1.05824 1.05827 1.0583 1.05832 1.05835 1.05838 1.05841 1.05844 1.05846 1.0585 1.05852 1.05855 1.05858 1.05861 1.05863 1.05866 1.05869 1.05872 1.05874 1.05877 1.0588 1.05883 1.05886 1.05888 1.05891 1.05894 1.05897 1.05899 1.05902 1.05905 1.05908 1.0591 1.05913 1.05916 1.05919 1.05922 1.05924 1.05927 1.05929 1.05933 1.05935 1.05938 1.0594 1.05943 1.05946 1.05949 1.05952 1.05954 1.05957 1.0596 1.05963 1.05965 1.05968 1.05971 1.05974 1.05976 1.05979 1.05982 1.05984 1.05988 1.0599 1.05993 1.05995 1.05998 1.06001 1.06004 1.06006 1.06009 1.06012 1.06015 1.06018 1.0602 1.06023 1.06025 1.06028 1.06031 1.06034 1.06036 1.06039 1.06041 1.06044 1.06047 1.0605 1.06053 1.06055 1.06058 1.06061 1.06064 1.06066 1.06069 1.06071 1.06074 1.06077 1.0608 1.06083 1.06085 1.06088 1.0609 1.06093 1.06096 1.06099 1.06101 1.06104 1.06106 1.06109 1.06112 1.06115 1.06118 1.0612 1.06123 1.06125 1.06128 1.06131 1.06133 1.06136 1.06139 1.06141 1.06144 1.06147 1.06149 1.06152 1.06155 1.06158 1.0616 1.06163 1.06165 1.06168 1.06171 1.06173 1.06176 1.06179 1.06182 1.06184 1.06187 1.06189 1.06192 1.06195 1.06197 1.062 1.06203 1.06205 1.06208 1.06211 1.06213 1.06216 1.06218 1.06221 1.06224 1.06227 1.06229 1.06232 1.06234 1.06237 1.06239 1.06242 1.06245 1.06248 1.0625 1.06253 1.06256 1.06258 1.06261 1.06263 1.06266 1.06268 1.06271 1.06274 1.06277 1.06279 1.06282 1.06285 1.06287 1.0629 1.06292 1.06295 1.06297 1.063 1.06302 1.06305 1.06308 1.06311 1.06313 1.06316 1.06319 1.06321 1.06324 1.06326 1.06329 1.06331 1.06334 1.06315 1.06295 1.06275 1.06255
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/materialConverter/densityCorrection/Schneider_matRad.dat b/matRad/doseCalc/topas/materialConverter/densityCorrection/Schneider_matRad.dat
new file mode 100644
index 000000000..3529f1d90
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/densityCorrection/Schneider_matRad.dat
@@ -0,0 +1 @@
+0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.001000 0.002000 0.003000 0.004000 0.005000 0.006000 0.007000 0.008000 0.009000 0.010000 0.011000 0.012000 0.013000 0.014000 0.015000 0.016000 0.017000 0.018000 0.019000 0.020000 0.021000 0.022000 0.023000 0.024000 0.025000 0.026000 0.027000 0.028000 0.029000 0.030000 0.031000 0.032000 0.033000 0.034000 0.035000 0.036000 0.037000 0.038000 0.039000 0.040000 0.041000 0.042000 0.043000 0.044000 0.045000 0.046000 0.047000 0.048000 0.049000 0.050000 0.051000 0.052000 0.053000 0.054000 0.055000 0.056000 0.057000 0.058000 0.059000 0.060000 0.061000 0.062000 0.063000 0.064000 0.065000 0.066000 0.067000 0.068000 0.069000 0.070000 0.071000 0.072000 0.073000 0.074000 0.075000 0.076000 0.077000 0.078000 0.079000 0.080000 0.081000 0.082000 0.083000 0.084000 0.085000 0.086000 0.087000 0.088000 0.089000 0.090000 0.091000 0.092000 0.093000 0.094000 0.095000 0.096000 0.097000 0.098000 0.099000 0.100000 0.101000 0.102000 0.103000 0.104000 0.105000 0.106000 0.107000 0.108000 0.109000 0.110000 0.111000 0.112000 0.113000 0.114000 0.115000 0.116000 0.117000 0.118000 0.119000 0.120000 0.121000 0.122000 0.123000 0.124000 0.125000 0.126000 0.127000 0.128000 0.129000 0.130000 0.131000 0.132000 0.133000 0.134000 0.135000 0.136000 0.137000 0.138000 0.139000 0.140000 0.141000 0.142000 0.143000 0.144000 0.145000 0.146000 0.147000 0.148000 0.149000 0.150000 0.151000 0.152000 0.153000 0.154000 0.155000 0.156000 0.157000 0.158000 0.159000 0.160000 0.161000 0.162000 0.163000 0.164000 0.165000 0.166000 0.167000 0.168000 0.169000 0.170000 0.171000 0.172000 0.173000 0.174000 0.175000 0.176000 0.177000 0.178000 0.179000 0.180000 0.181000 0.182000 0.183000 0.184000 0.185000 0.186000 0.187000 0.188000 0.189000 0.190000 0.191000 0.192000 0.193000 0.194000 0.195000 0.196000 0.197000 0.198000 0.199000 0.200000 0.201000 0.202000 0.203000 0.204000 0.205000 0.206000 0.207000 0.208000 0.209000 0.210000 0.211000 0.212000 0.213000 0.214000 0.215000 0.216000 0.217000 0.218000 0.219000 0.220000 0.221000 0.222000 0.223000 0.224000 0.225000 0.226000 0.227000 0.228000 0.229000 0.230000 0.231000 0.232000 0.233000 0.234000 0.235000 0.236000 0.237000 0.238000 0.239000 0.240000 0.241000 0.242000 0.243000 0.244000 0.245000 0.246000 0.247000 0.248000 0.249000 0.250000 0.251000 0.252000 0.253000 0.254000 0.255000 0.256000 0.257000 0.258000 0.259000 0.260000 0.261000 0.262000 0.263000 0.264000 0.265000 0.266000 0.267000 0.268000 0.269000 0.270000 0.271000 0.272000 0.273000 0.274000 0.275000 0.276000 0.277000 0.278000 0.279000 0.280000 0.281000 0.282000 0.283000 0.284000 0.285000 0.286000 0.287000 0.288000 0.289000 0.290000 0.291000 0.292000 0.293000 0.294000 0.295000 0.296000 0.297000 0.298000 0.299000 0.300000 0.301000 0.302000 0.303000 0.304000 0.305000 0.306000 0.307000 0.308000 0.309000 0.310000 0.311000 0.312000 0.313000 0.314000 0.315000 0.316000 0.317000 0.318000 0.319000 0.320000 0.321000 0.322000 0.323000 0.324000 0.325000 0.326000 0.327000 0.328000 0.329000 0.330000 0.331000 0.332000 0.333000 0.334000 0.335000 0.336000 0.337000 0.338000 0.339000 0.340000 0.341000 0.342000 0.343000 0.344000 0.345000 0.346000 0.347000 0.348000 0.349000 0.350000 0.351000 0.352000 0.353000 0.354000 0.355000 0.356000 0.357000 0.358000 0.359000 0.360000 0.361000 0.362000 0.363000 0.364000 0.365000 0.366000 0.367000 0.368000 0.369000 0.370000 0.371000 0.372000 0.373000 0.374000 0.375000 0.376000 0.377000 0.378000 0.379000 0.380000 0.381000 0.382000 0.383000 0.384000 0.385000 0.386000 0.387000 0.388000 0.389000 0.390000 0.391000 0.392000 0.393000 0.394000 0.395000 0.396000 0.397000 0.398000 0.399000 0.400000 0.401000 0.402000 0.403000 0.404000 0.405000 0.406000 0.407000 0.408000 0.409000 0.410000 0.411000 0.412000 0.413000 0.414000 0.415000 0.416000 0.417000 0.418000 0.419000 0.420000 0.421000 0.422000 0.423000 0.424000 0.425000 0.426000 0.427000 0.428000 0.429000 0.430000 0.431000 0.432000 0.433000 0.434000 0.435000 0.436000 0.437000 0.438000 0.439000 0.440000 0.441000 0.442000 0.443000 0.444000 0.445000 0.446000 0.447000 0.448000 0.449000 0.450000 0.451000 0.452000 0.453000 0.454000 0.455000 0.456000 0.457000 0.458000 0.459000 0.460000 0.461000 0.462000 0.463000 0.464000 0.465000 0.466000 0.467000 0.468000 0.469000 0.470000 0.471000 0.472000 0.473000 0.474000 0.475000 0.476000 0.477000 0.478000 0.479000 0.480000 0.481000 0.482000 0.483000 0.484000 0.485000 0.486000 0.487000 0.488000 0.489000 0.490000 0.491000 0.492000 0.493000 0.494000 0.495000 0.496000 0.497000 0.498000 0.499000 0.500000 0.501000 0.502000 0.503000 0.504000 0.505000 0.506000 0.507000 0.508000 0.509000 0.510000 0.511000 0.512000 0.513000 0.514000 0.515000 0.516000 0.517000 0.518000 0.519000 0.520000 0.521000 0.522000 0.523000 0.524000 0.525000 0.526000 0.527000 0.528000 0.529000 0.530000 0.531000 0.532000 0.533000 0.534000 0.535000 0.536000 0.537000 0.538000 0.539000 0.540000 0.541000 0.542000 0.543000 0.544000 0.545000 0.546000 0.547000 0.548000 0.549000 0.550000 0.551000 0.552000 0.553000 0.554000 0.555000 0.556000 0.557000 0.558000 0.559000 0.560000 0.561000 0.562000 0.563000 0.564000 0.565000 0.566000 0.567000 0.568000 0.569000 0.570000 0.571000 0.572000 0.573000 0.574000 0.575000 0.576000 0.577000 0.578000 0.579000 0.580000 0.581000 0.582000 0.583000 0.584000 0.585000 0.586000 0.587000 0.588000 0.589000 0.590000 0.591000 0.592000 0.593000 0.594000 0.595000 0.596000 0.597000 0.598000 0.599000 0.600000 0.601000 0.602000 0.603000 0.604000 0.605000 0.606000 0.607000 0.608000 0.609000 0.610000 0.611000 0.612000 0.613000 0.614000 0.615000 0.616000 0.617000 0.618000 0.619000 0.620000 0.621000 0.622000 0.623000 0.624000 0.625000 0.626000 0.627000 0.628000 0.629000 0.630000 0.631000 0.632000 0.633000 0.634000 0.635000 0.636000 0.637000 0.638000 0.639000 0.640000 0.641000 0.642000 0.643000 0.644000 0.645000 0.646000 0.647000 0.648000 0.649000 0.650000 0.651000 0.652000 0.653000 0.654000 0.655000 0.656000 0.657000 0.658000 0.659000 0.660000 0.661000 0.662000 0.663000 0.664000 0.665000 0.666000 0.667000 0.668000 0.669000 0.670000 0.671000 0.672000 0.673000 0.674000 0.675000 0.676000 0.677000 0.678000 0.679000 0.680000 0.681000 0.682000 0.683000 0.684000 0.685000 0.686000 0.687000 0.688000 0.689000 0.690000 0.691000 0.692000 0.693000 0.694000 0.695000 0.696000 0.697000 0.698000 0.699000 0.700000 0.701000 0.702000 0.703000 0.704000 0.705000 0.706000 0.707000 0.708000 0.709000 0.710000 0.711000 0.712000 0.713000 0.714000 0.715000 0.716000 0.717000 0.718000 0.719000 0.720000 0.721000 0.722000 0.723000 0.724000 0.725000 0.726000 0.727000 0.728000 0.729000 0.730000 0.731000 0.732000 0.733000 0.734000 0.735000 0.736000 0.737000 0.738000 0.739000 0.740000 0.741000 0.742000 0.743000 0.744000 0.745000 0.746000 0.747000 0.748000 0.749000 0.750000 0.751000 0.752000 0.753000 0.754000 0.755000 0.756000 0.757000 0.758000 0.759000 0.760000 0.761000 0.762000 0.763000 0.764000 0.765000 0.766000 0.767000 0.768000 0.769000 0.770000 0.771000 0.772000 0.773000 0.774000 0.775000 0.776000 0.777000 0.778000 0.779000 0.780000 0.781000 0.782000 0.783000 0.784000 0.785000 0.786000 0.787000 0.788000 0.789000 0.790000 0.791000 0.792000 0.793000 0.794000 0.795000 0.796000 0.797000 0.798000 0.799000 0.800000 0.801000 0.802000 0.803000 0.804000 0.805000 0.806000 0.807000 0.808000 0.809000 0.810000 0.811000 0.812000 0.813000 0.814000 0.815000 0.816000 0.817000 0.818000 0.819000 0.820000 0.821000 0.822000 0.823000 0.824000 0.825000 0.826000 0.827000 0.828000 0.829000 0.830000 0.831000 0.832000 0.833000 0.834000 0.835000 0.836000 0.837000 0.838000 0.839000 0.840000 0.841000 0.842000 0.843000 0.844000 0.845000 0.846000 0.847000 0.848000 0.849000 0.850000 0.851000 0.852000 0.853000 0.854000 0.855000 0.856000 0.857000 0.858000 0.859000 0.860000 0.861000 0.862000 0.863000 0.864000 0.865000 0.866000 0.867000 0.868000 0.869000 0.870000 0.871000 0.872000 0.873000 0.874000 0.875000 0.876000 0.877000 0.878000 0.879000 0.880000 0.881000 0.882000 0.883000 0.884000 0.885000 0.886000 0.887000 0.888000 0.889000 0.890000 0.891000 0.892000 0.893000 0.894000 0.895000 0.896000 0.897000 0.898000 0.899000 0.900000 0.901000 0.902000 0.903000 0.904000 0.905000 0.906000 0.907000 0.908000 0.909000 0.910000 0.911000 0.912000 0.913000 0.914000 0.915000 0.916000 0.917000 0.918000 0.919000 0.920000 0.921000 0.922000 0.923000 0.924000 0.925000 0.926000 0.927000 0.928000 0.929000 0.930000 0.931000 0.932000 0.933000 0.934000 0.935000 0.936000 0.937000 0.938000 0.939000 0.940000 0.941000 0.942000 0.943000 0.944000 0.945000 0.946000 0.947000 0.948000 0.949000 0.950000 0.951000 0.952000 0.953000 0.954000 0.955000 0.956000 0.957000 0.958000 0.959000 0.960000 0.961000 0.962000 0.963000 0.964000 0.965000 0.966000 0.967000 0.968000 0.969000 0.970000 0.971000 0.972000 0.973000 0.974000 0.975000 0.976000 0.977000 0.978000 0.979000 0.980000 0.981000 0.982000 0.983000 0.984000 0.985000 0.986000 0.987000 0.988000 0.989000 0.990000 0.991000 0.992000 0.993000 0.994000 0.995000 0.996000 0.997000 0.998000 0.999000 1.000000 1.001000 1.002000 1.003000 1.004000 1.005000 1.006000 1.007000 1.008000 1.009000 1.010000 1.011000 1.012000 1.013000 1.014000 1.015000 1.016000 1.017000 1.018000 1.019000 1.020000 1.021000 1.022000 1.023000 1.024000 1.025000 1.026000 1.027000 1.028000 1.029000 1.030000 1.031000 1.032000 1.033000 1.034000 1.035000 1.036000 1.037000 1.038000 1.039000 1.040000 1.041000 1.042000 1.043000 1.044000 1.045000 1.046000 1.047000 1.048000 1.049000 1.050000 1.051000 1.052000 1.053000 1.054000 1.055000 1.056000 1.057000 1.058000 1.059000 1.060000 1.061000 1.062000 1.063000 1.064000 1.065000 1.066000 1.067000 1.068000 1.069000 1.070000 1.071000 1.072000 1.073000 1.074000 1.075000 1.076000 1.077000 1.078000 1.079000 1.080000 1.081000 1.082000 1.083000 1.084000 1.085000 1.086000 1.087000 1.088000 1.089000 1.090000 1.091000 1.092000 1.093000 1.094000 1.095000 1.096000 1.097000 1.098000 1.099000 1.100000 1.101000 1.102000 1.103000 1.104000 1.105000 1.106000 1.107000 1.108000 1.109000 1.110000 1.111000 1.112000 1.113000 1.114000 1.115000 1.116000 1.117000 1.118000 1.119000 1.120000 1.121000 1.122000 1.123000 1.124000 1.125000 1.126000 1.127000 1.128000 1.129000 1.130000 1.131000 1.132000 1.133000 1.134000 1.135000 1.136000 1.137000 1.138000 1.139000 1.140000 1.141000 1.142000 1.143000 1.144000 1.145000 1.146000 1.147000 1.148000 1.149000 1.150000 1.151000 1.152000 1.153000 1.154000 1.155000 1.156000 1.157000 1.158000 1.159000 1.160000 1.161000 1.162000 1.163000 1.164000 1.165000 1.166000 1.167000 1.168000 1.169000 1.170000 1.171000 1.172000 1.173000 1.174000 1.175000 1.176000 1.177000 1.178000 1.179000 1.180000 1.181000 1.182000 1.183000 1.184000 1.185000 1.186000 1.187000 1.188000 1.189000 1.190000 1.191000 1.192000 1.193000 1.194000 1.195000 1.196000 1.197000 1.198000 1.199000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200000 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200001 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200002 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200003 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200004 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200005 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200006 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200007 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200008 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200009 1.200010 1.200010 1.200010 1.200010 1.200010 1.200010 1.200010 1.200010 1.200010 1.200010 1.200010 1.200010 1.200010 1.200842 1.201674 1.202506 1.203339 1.204171 1.205003 1.205835 1.206667 1.207499 1.208331 1.209164 1.209996 1.210828 1.211660 1.212492 1.213324 1.214156 1.214989 1.215821 1.216653 1.217485 1.218317 1.219149 1.219981 1.220814 1.221646 1.222478 1.223310 1.224142 1.224974 1.225806 1.226638 1.227471 1.228303 1.229135 1.229967 1.230799 1.231631 1.232463 1.233296 1.234128 1.234960 1.235792 1.236624 1.237456 1.238288 1.239121 1.239953 1.240785 1.241617 1.242449 1.243281 1.244113 1.244946 1.245778 1.246610 1.247442 1.248274 1.249106 1.249938 1.250771 1.251603 1.252435 1.253267 1.254099 1.254931 1.255763 1.256596 1.257428 1.258260 1.259092 1.259924 1.260756 1.261588 1.262421 1.263253 1.264085 1.264917 1.265749 1.266581 1.267413 1.268246 1.269078 1.269910 1.270742 1.271574 1.272406 1.273238 1.274071 1.274903 1.275735 1.276567 1.277399 1.278231 1.279063 1.279895 1.280728 1.281560 1.282392 1.283224 1.284056 1.284888 1.285720 1.286553 1.287385 1.288217 1.289049 1.289881 1.290713 1.291545 1.292378 1.293210 1.294042 1.294874 1.295706 1.296538 1.297370 1.298203 1.299035 1.299867 1.300699 1.301531 1.302363 1.303195 1.304028 1.304860 1.305692 1.306524 1.307356 1.308188 1.309020 1.309853 1.310685 1.311517 1.312349 1.313181 1.314013 1.314845 1.315678 1.316510 1.317342 1.318174 1.319006 1.319838 1.320670 1.321503 1.322335 1.323167 1.323999 1.324831 1.325663 1.326495 1.327328 1.328160 1.328992 1.329824 1.330656 1.331488 1.332320 1.333152 1.333985 1.334817 1.335649 1.336481 1.337313 1.338145 1.338977 1.339810 1.340642 1.341474 1.342306 1.343138 1.343970 1.344802 1.345635 1.346467 1.347299 1.348131 1.348963 1.349795 1.350627 1.351460 1.352292 1.353124 1.353956 1.354788 1.355620 1.356452 1.357285 1.358117 1.358949 1.359781 1.360613 1.361445 1.362277 1.363110 1.363942 1.364774 1.365606 1.366438 1.367270 1.368102 1.368935 1.369767 1.370599 1.371431 1.372263 1.373095 1.373927 1.374760 1.375592 1.376424 1.377256 1.378088 1.378920 1.379752 1.380585 1.381417 1.382249 1.383081 1.383913 1.384745 1.385577 1.386409 1.387242 1.388074 1.388906 1.389738 1.390570 1.391402 1.392234 1.393067 1.393899 1.394731 1.395563 1.396395 1.397227 1.398059 1.398892 1.399724 1.400556 1.401388 1.402220 1.403052 1.403884 1.404717 1.405549 1.406381 1.407213 1.408045 1.408877 1.409709 1.410542 1.411374 1.412206 1.413038 1.413870 1.414702 1.415534 1.416367 1.417199 1.418031 1.418863 1.419695 1.420527 1.421359 1.422192 1.423024 1.423856 1.424688 1.425520 1.426352 1.427184 1.428017 1.428849 1.429681 1.430513 1.431345 1.432177 1.433009 1.433841 1.434674 1.435506 1.436338 1.437170 1.438002 1.438834 1.439666 1.440499 1.441331 1.442163 1.442995 1.443827 1.444659 1.445491 1.446324 1.447156 1.447988 1.448820 1.449652 1.450484 1.451316 1.452149 1.452981 1.453813 1.454645 1.455477 1.456309 1.457141 1.457974 1.458806 1.459638 1.460470 1.461302 1.462134 1.462966 1.463799 1.464631 1.465463 1.466295 1.467127 1.467959 1.468791 1.469624 1.470456 1.471288 1.472120 1.472952 1.473784 1.474616 1.475449 1.476281 1.477113 1.477945 1.478777 1.479609 1.480441 1.481274 1.482106 1.482938 1.483770 1.484602 1.485434 1.486266 1.487098 1.487931 1.488763 1.489595 1.490427 1.491259 1.492091 1.492923 1.493756 1.494588 1.495420 1.496252 1.497084 1.497916 1.498748 1.499581 1.500413 1.501245 1.502077 1.502909 1.503741 1.504573 1.505406 1.506238 1.507070 1.507902 1.508734 1.509566 1.510398 1.511231 1.512063 1.512895 1.513727 1.514559 1.515391 1.516223 1.517056 1.517888 1.518720 1.519552 1.520384 1.521216 1.522048 1.522881 1.523713 1.524545 1.525377 1.526209 1.527041 1.527873 1.528706 1.529538 1.530370 1.531202 1.532034 1.532866 1.533698 1.534531 1.535363 1.536195 1.537027 1.537859 1.538691 1.539523 1.540355 1.541188 1.542020 1.542852 1.543684 1.544516 1.545348 1.546180 1.547013 1.547845 1.548677 1.549509 1.550341 1.551173 1.552005 1.552838 1.553670 1.554502 1.555334 1.556166 1.556998 1.557830 1.558663 1.559495 1.560327 1.561159 1.561991 1.562823 1.563655 1.564488 1.565320 1.566152 1.566984 1.567816 1.568648 1.569480 1.570313 1.571145 1.571977 1.572809 1.573641 1.574473 1.575305 1.576138 1.576970 1.577802 1.578634 1.579466 1.580298 1.581130 1.581963 1.582795 1.583627 1.584459 1.585291 1.586123 1.586955 1.587787 1.588620 1.589452 1.590284 1.591116 1.591948 1.592780 1.593612 1.594445 1.595277 1.596109 1.596941 1.597773 1.598605 1.599437 1.600270 1.601102 1.601934 1.602766 1.603598 1.604430 1.605262 1.606095 1.606927 1.607759 1.608591 1.609423 1.610255 1.611087 1.611920 1.612752 1.613584 1.614416 1.615248 1.616080 1.616912 1.617745 1.618577 1.619409 1.620241 1.621073 1.621905 1.622737 1.623570 1.624402 1.625234 1.626066 1.626898 1.627730 1.628562 1.629395 1.630227 1.631059 1.631891 1.632723 1.633555 1.634387 1.635220 1.636052 1.636884 1.637716 1.638548 1.639380 1.640212 1.641044 1.641877 1.642709 1.643541 1.644373 1.645205 1.646037 1.646869 1.647702 1.648534 1.649366 1.650198 1.651030 1.651862 1.652694 1.653527 1.654359 1.655191 1.656023 1.656855 1.657687 1.658519 1.659352 1.660184 1.661016 1.661848 1.662680 1.663512 1.664344 1.665177 1.666009 1.666841 1.667673 1.668505 1.669337 1.670169 1.671002 1.671834 1.672666 1.673498 1.674330 1.675162 1.675994 1.676827 1.677659 1.678491 1.679323 1.680155 1.680987 1.681819 1.682652 1.683484 1.684316 1.685148 1.685980 1.686812 1.687644 1.688477 1.689309 1.690141 1.690973 1.691805 1.692637 1.693469 1.694301 1.695134 1.695966 1.696798 1.697630 1.698462 1.699294 1.700126 1.700959 1.701791 1.702623 1.703455 1.704287 1.705119 1.705951 1.706784 1.707616 1.708448 1.709280 1.710112 1.710944 1.711776 1.712609 1.713441 1.714273 1.715105 1.715937 1.716769 1.717601 1.718434 1.719266 1.720098 1.720930 1.721762 1.722594 1.723426 1.724259 1.725091 1.725923 1.726755 1.727587 1.728419 1.729251 1.730084 1.730916 1.731748 1.732580 1.733412 1.734244 1.735076 1.735909 1.736741 1.737573 1.738405 1.739237 1.740069 1.740901 1.741734 1.742566 1.743398 1.744230 1.745062 1.745894 1.746726 1.747558 1.748391 1.749223 1.750055 1.750887 1.751719 1.752551 1.753383 1.754216 1.755048 1.755880 1.756712 1.757544 1.758376 1.759208 1.760041 1.760873 1.761705 1.762537 1.763369 1.764201 1.765033 1.765866 1.766698 1.767530 1.768362 1.769194 1.770026 1.770858 1.771691 1.772523 1.773355 1.774187 1.775019 1.775851 1.776683 1.777516 1.778348 1.779180 1.780012 1.780844 1.781676 1.782508 1.783341 1.784173 1.785005 1.785837 1.786669 1.787501 1.788333 1.789166 1.789998 1.790830 1.791662 1.792494 1.793326 1.794158 1.794990 1.795823 1.796655 1.797487 1.798319 1.799151 1.799983 1.800815 1.801648 1.802480 1.803312 1.804144 1.804976 1.805808 1.806640 1.807473 1.808305 1.809137 1.809969 1.810801 1.811633 1.812465 1.813298 1.814130 1.814962 1.815794 1.816626 1.817458 1.818290 1.819123 1.819955 1.820787 1.821619 1.822451 1.823283 1.824115 1.824948 1.825780 1.826612 1.827444 1.828276 1.829108 1.829940 1.830773 1.831605 1.832437 1.833269 1.834101 1.834933 1.835765 1.836598 1.837430 1.838262 1.839094 1.839926 1.840758 1.841590 1.842423 1.843255 1.844087 1.844919 1.845751 1.846583 1.847415 1.848247 1.849080 1.849912 1.850744 1.851576 1.852408 1.853240 1.854072 1.854905 1.855737 1.856569 1.857401 1.858233 1.859065 1.859897 1.860730 1.861562 1.862394 1.863226 1.864058 1.864890 1.865722 1.866555 1.867387 1.868219 1.869051 1.869883 1.870715 1.871547 1.872380 1.873212 1.874044 1.874876 1.875708 1.876540 1.877372 1.878205 1.879037 1.879869 1.880701 1.881533 1.882365 1.883197 1.884030 1.884862 1.885694 1.886526 1.887358 1.888190 1.889022 1.889855 1.890687 1.891519 1.892351 1.893183 1.894015 1.894847 1.895680 1.896512 1.897344 1.898176 1.899008 1.899840 1.900672 1.901504 1.902337 1.903169 1.904001 1.904833 1.905665 1.906497 1.907329 1.908162 1.908994 1.909826 1.910658 1.911490 1.912322 1.913154 1.913987 1.914819 1.915651 1.916483 1.917315 1.918147 1.918979 1.919812 1.920644 1.921476 1.922308 1.923140 1.923972 1.924804 1.925637 1.926469 1.927301 1.928133 1.928965 1.929797 1.930629 1.931462 1.932294 1.933126 1.933958 1.934790 1.935622 1.936454 1.937287 1.938119 1.938951 1.939783 1.940615 1.941447 1.942279 1.943112 1.943944 1.944776 1.945608 1.946440 1.947272 1.948104 1.948936 1.949769 1.950601 1.951433 1.952265 1.953097 1.953929 1.954761 1.955594 1.956426 1.957258 1.958090 1.958922 1.959754 1.960586 1.961419 1.962251 1.963083 1.963915 1.964747 1.965579 1.966411 1.967244 1.968076 1.968908 1.969740 1.970572 1.971404 1.972236 1.973069 1.973901 1.974733 1.975565 1.976397 1.977229 1.978061 1.978894 1.979726 1.980558 1.981390 1.982222 1.983054 1.983886 1.984719 1.985551 1.986383 1.987215 1.988047 1.988879 1.989711 1.990544 1.991376 1.992208 1.993040 1.993872 1.994704 1.995536 1.996369 1.997201 1.998033 1.998865 1.999697 2.000529 2.001361 2.002193 2.003026 2.003858 2.004690 2.005522 2.006354 2.007186 2.008018 2.008851 2.009683 2.010515 2.011347 2.012179 2.013011 2.013843 2.014676 2.015508 2.016340 2.017172 2.018004 2.018836 2.019668 2.020501 2.021333 2.022165 2.022997 2.023829 2.024661 2.025493 2.026326 2.027158 2.027990 2.028822 2.029654 2.030486 2.031318 2.032151 2.032983 2.033815 2.034647 2.035479 2.036311 2.037143 2.037976 2.038808 2.039640 2.040472 2.041304 2.042136 2.042968 2.043801 2.044633 2.045465 2.046297 2.047129 2.047961 2.048793 2.049626 2.050458 2.051290 2.052122 2.052954 2.053786 2.054618 2.055450 2.056283 2.057115 2.057947 2.058779 2.059611 2.060443 2.061275 2.062108 2.062940 2.063772 2.064604 2.065436 2.066268 2.067100 2.067933 2.068765 2.069597 2.070429 2.071261 2.072093 2.072925 2.073758 2.074590 2.075422 2.076254 2.077086 2.077918 2.078750 2.079583 2.080415 2.081247 2.082079 2.082911 2.083743 2.084575 2.085408 2.086240 2.087072 2.087904 2.088736 2.089568 2.090400 2.091233 2.092065 2.092897 2.093729 2.094561 2.095393 2.096225 2.097058 2.097890 2.098722 2.099554 2.100386 2.101218 2.102050 2.102883 2.103715 2.104547 2.105379 2.106211 2.107043 2.107875 2.108707 2.109540 2.110372 2.111204 2.112036 2.112868 2.113700 2.114532 2.115365 2.116197 2.117029 2.117861 2.118693 2.119525 2.120357 2.121190 2.122022 2.122854 2.123686 2.124518 2.125350 2.126182 2.127015 2.127847 2.128679 2.129511 2.130343 2.131175 2.132007 2.132840 2.133672 2.134504 2.135336 2.136168 2.137000 2.137832 2.138665 2.139497 2.140329 2.141161 2.141993 2.142825 2.143657 2.144490 2.145322 2.146154 2.146986 2.147818 2.148650 2.149482 2.150315 2.151147 2.151979 2.152811 2.153643 2.154475 2.155307 2.156139 2.156972 2.157804 2.158636 2.159468 2.160300 2.161132 2.161964 2.162797 2.163629 2.164461 2.165293 2.166125 2.166957 2.167789 2.168622 2.169454 2.170286 2.171118 2.171950 2.172782 2.173614 2.174447 2.175279 2.176111 2.176943 2.177775 2.178607 2.179439 2.180272 2.181104 2.181936 2.182768 2.183600 2.184432 2.185264 2.186097 2.186929 2.187761 2.188593 2.189425 2.190257 2.191089 2.191922 2.192754 2.193586 2.194418 2.195250 2.196082 2.196914 2.197747 2.198579 2.199411 2.200243 2.201075 2.201907 2.202739 2.203572 2.204404 2.205236 2.206068 2.206900 2.207732 2.208564 2.209396 2.210229 2.211061 2.211893 2.212725 2.213557 2.214389 2.215221 2.216054 2.216886 2.217718 2.218550 2.219382 2.220214 2.221046 2.221879 2.222711 2.223543 2.224375 2.225207 2.226039 2.226871 2.227704 2.228536 2.229368 2.230200 2.231032 2.231864 2.232696 2.233529 2.234361 2.235193 2.236025 2.236857 2.237689 2.238521 2.239354 2.240186 2.241018 2.241850 2.242682 2.243514 2.244346 2.245179 2.246011 2.246843 2.247675 2.248507 2.249339 2.250171 2.251004 2.251836 2.252668 2.253500 2.254332 2.255164 2.255996 2.256829 2.257661 2.258493 2.259325 2.260157 2.260989 2.261821 2.262653 2.263486 2.264318 2.265150 2.265982 2.266814 2.267646 2.268478 2.269311 2.270143 2.270975 2.271807 2.272639 2.273471 2.274303 2.275136 2.275968 2.276800 2.277632 2.278464 2.279296 2.280128 2.280961 2.281793 2.282625 2.283457 2.284289 2.285121 2.285953 2.286786 2.287618 2.288450 2.289282 2.290114 2.290946 2.291778 2.292611 2.293443 2.294275 2.295107 2.295939 2.296771 2.297603 2.298436 2.299268 2.300100 2.300932 2.301764 2.302596 2.303428 2.304261 2.305093 2.305925 2.306757 2.307589 2.308421 2.309253 2.310085 2.310918 2.311750 2.312582 2.313414 2.314246 2.315078 2.315910 2.316743 2.317575 2.318407 2.319239 2.320071 2.320903 2.321735 2.322568 2.323400 2.324232 2.325064 2.325896 2.326728 2.327560 2.328393 2.329225 2.330057 2.330889 2.331721 2.332553 2.333385 2.334218 2.335050 2.335882 2.336714 2.337546 2.338378 2.339210 2.340043 2.340875 2.341707 2.342539 2.343371 2.344203 2.345035 2.345868 2.346700 2.347532 2.348364 2.349196 2.350028 2.350860 2.351693 2.352525 2.353357 2.354189 2.355021 2.355853 2.356685 2.357518 2.358350 2.359182 2.360014 2.360846 2.361678 2.362510 2.363342 2.364175 2.365007 2.365839 2.366671 2.367503 2.368335 2.369167 2.370000 2.370832 2.371664 2.372496 2.373328 2.374160 2.374992 2.375825 2.376657 2.377489 2.378321 2.379153 2.379985 2.380817 2.381650 2.382482 2.383314 2.384146 2.384978 2.385810 2.386642 2.387475 2.388307 2.389139 2.389971 2.390803 2.391635 2.392467 2.393300 2.394132 2.394964 2.395796 2.396628 2.397460 2.398292 2.399125 2.399957 2.400789 2.401621 2.402453 2.403285 2.404117 2.404950 2.405782 2.406614 2.407446 2.408278 2.409110 2.409942 2.410775 2.411607 2.412439 2.413271 2.414103 2.414935 2.415767 2.416599 2.417432 2.418264 2.419096 2.419928 2.420760 2.421592 2.422424 2.423257 2.424089 2.424921 2.425753 2.426585 2.427417 2.428249 2.429082 2.429914 2.430746 2.431578 2.432410 2.433242 2.434074 2.434907 2.435739 2.436571 2.437403 2.438235 2.439067 2.439899 2.440732 2.441564 2.442396 2.443228 2.444060 2.444892 2.445724 2.446557 2.447389 2.448221 2.449053 2.449885 2.450717 2.451549 2.452382 2.453214 2.454046 2.454878 2.455710 2.456542 2.457374 2.458207 2.459039 2.459871 2.460703 2.461535 2.462367 2.463199 2.464032 2.464864 2.465696 2.466528 2.467360 2.468192 2.469024 2.469856 2.470689 2.471521 2.472353 2.473185 2.474017 2.474849 2.475681 2.476514 2.477346 2.478178 2.479010 2.479842 2.480674 2.481506 2.482339 2.483171 2.484003 2.484835 2.485667 2.486499 2.487331 2.488164 2.488996 2.489828 2.490660 2.491492 2.492324 2.493156 2.493988 2.494820 2.495653 2.496485 2.497317 2.498149 2.498981 2.499813 2.500645 2.501477 2.502309 2.503141 2.503973 2.504805 2.505638 2.506470 2.507302 2.508134 2.508966 2.509798 2.510630 2.511462 2.512294 2.513126 2.513958 2.514790 2.515623 2.516455 2.517287 2.518119 2.518951 2.519783 2.520615 2.521447 2.522279 2.523111 2.523943 2.524775 2.525608 2.526440 2.527272 2.528104 2.528936 2.529768 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530600 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530601 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530602 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530603 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530604 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530605 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530606 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530607 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530608 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530609 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610 2.530610
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/materialConverter/densityCorrection/readme.md b/matRad/doseCalc/topas/materialConverter/densityCorrection/readme.md
new file mode 100644
index 000000000..71bceeade
--- /dev/null
+++ b/matRad/doseCalc/topas/materialConverter/densityCorrection/readme.md
@@ -0,0 +1,2 @@
+Schneider_TOPAS (default): taken from official TOPAS example "https://topas.readthedocs.io/en/latest/examples-docs/Patient/HUtoMaterialSchneider.html"
+Schneider_matRad: density conversion interpolated from matRad default HLUT, rspHlut = matRad_readHLUT('matRad_default.hlut'), same as 'rspHLUT' option, if the default HLUT is used
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/rangeShifter/TOPAS_generic_RS_PMMA.txt.in b/matRad/doseCalc/topas/rangeShifter/TOPAS_generic_RS_PMMA.txt.in
new file mode 100644
index 000000000..010405425
--- /dev/null
+++ b/matRad/doseCalc/topas/rangeShifter/TOPAS_generic_RS_PMMA.txt.in
@@ -0,0 +1,11 @@
+s:Ge/RangeShifter/Parent = "Nozzle"
+s:Ge/RangeShifter/Type = "TsBox"
+s:Ge/RangeShifter/Material = "Lucite"
+s:Ge/RangeShifter/HLX = 500 mm
+s:Ge/RangeShifter/HLY = 500 mm
+s:Ge/RangeShifter/HLZ = Tf/Beam/RangeShifterWidth/Value
+s:Ge/RangeShifter/TransX = 2 * TF/Beam/RangeShifterOut * Ge/RangeShifter/HLX
+s:Ge/RangeShifter/TransY = 0 mm
+s:Ge/RangeShifter/TransZ = Sim/SourceToRangeShifterDistance
+
+
diff --git a/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_LEM1.txt.in b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_LEM1.txt.in
new file mode 100644
index 000000000..f0426a8fd
--- /dev/null
+++ b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_LEM1.txt.in
@@ -0,0 +1,50 @@
+# LEM1 RBE scorer
+
+s:Sc/tabulatedAlpha/Quantity = "RBE_TabulatedAlphaBeta"
+s:Sc/tabulatedAlpha/Component = "Patient"
+s:Sc/tabulatedAlpha/OutputQuantity = "alpha"
+d:Sc/tabulatedAlpha/PrescribedDose = Sc/PrescribedDose Gy
+sv:Sc/tabulatedAlpha/CellLines = Sc/CellLines
+s:Sc/tabulatedAlpha/OutputType = Sim/DoseScorerOutputType
+s:Sc/tabulatedAlpha/OutputFile = Sim/ScoreLabel + "_alpha_LEM"
+s:Sc/tabulatedAlpha/IfOutputFileAlreadyExists = "Overwrite"
+s:Sc/tabulatedAlpha/ModelName = "HCP"
+
+s:Sc/tabulatedBeta/Quantity = "RBE_TabulatedAlphaBeta"
+s:Sc/tabulatedBeta/Component = "Patient"
+s:Sc/tabulatedBeta/OutputQuantity = "beta"
+d:Sc/tabulatedBeta/PrescribedDose = Sc/PrescribedDose Gy
+sv:Sc/tabulatedBeta/CellLines = Sc/CellLines
+s:Sc/tabulatedBeta/OutputType = Sim/DoseScorerOutputType
+s:Sc/tabulatedBeta/OutputFile = Sim/ScoreLabel + "_beta_LEM"
+s:Sc/tabulatedBeta/IfOutputFileAlreadyExists = "Overwrite"
+s:Sc/tabulatedBeta/ModelName = "HCP"
+
+#s:Sc/RBE/Quantity = "RBE_TabulatedAlphaBeta"
+#s:Sc/RBE/Component = "Patient"
+#s:Sc/RBE/OutputQuantity = "rbe"
+#d:Sc/RBE/PrescribedDose = Sc/PrescribedDose Gy
+#sv:Sc/RBE/CellLines = Sc/CellLines
+#s:Sc/RBE/OutputType = Sim/DoseScorerOutputType
+#s:Sc/RBE/OutputFile = Sim/ScoreLabel + "_RBE_LEM"
+#s:Sc/RBE/IfOutputFileAlreadyExists = "Overwrite"
+#s:Sc/RBE/ModelName = "HCP"
+
+### HCP Tabulated ###
+sv:Sc/CellGeneric_abR2/HCP/ParticleName = 6 "Proton" "Helium" "Lithium" "Beryllium" "Boron" "Carbon"
+iv:Sc/CellGeneric_abR2/HCP/ParticleZ = 6 1 2 3 4 5 6
+dv:Sc/CellGeneric_abR2/HCP/KineticEnergyPerNucleon = 47 0.1 0.15 0.2 0.3 0.4 0.5 0.7 1 1.2 1.5 2 2.5 3 4 5 6 7 8 10 12 15 20 25 30 40 50 60 70 80 90 100 120 130 135 150 195 200 250 270 300 330 400 500 600 700 800 1000 MeV
+
+dv:Sc/CellGeneric_abR2/HCP/Proton/Alpha = 47 2.9718 3.0321 3.0728 3.1316 3.1731 3.2077 3.2393 3.2042 3.121 2.8657 2.3959 2.066 1.8338 1.5282 1.3327 1.1945 1.0902 1.0079 0.88461 0.79522 0.69751 0.58723 0.50721 0.44794 0.36413 0.3063 0.26317 0.23122 0.20791 0.19018 0.17627 0.15581 0.14808 0.14466 0.13584 0.11779 0.1163 0.10471 0.10129 0.097001 0.093488 0.087298 0.081368 0.077319 0.074352 0.072067 0.068719 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Helium/Alpha = 47 2.6517 2.6209 2.6172 2.6628 2.7301 2.7977 2.8966 2.9725 2.9936 2.9791 2.798 2.5347 2.3115 1.9906 1.7745 1.6179 1.4979 1.4023 1.2577 1.152 1.0357 0.90374 0.80862 0.73802 0.63798 0.56882 0.51713 0.4765 0.44343 0.41575 0.39196 0.35337 0.33741 0.33009 0.31032 0.26524 0.26119 0.22771 0.21707 0.20322 0.19163 0.17166 0.1532 0.1411 0.13254 0.12617 0.1172 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Lithium/Alpha = 47 2.4593 2.3758 2.3298 2.3088 2.3294 2.3672 2.4511 2.5665 2.6301 2.6957 2.7116 2.5979 2.4411 2.1717 1.972 1.8207 1.7018 1.6053 1.4568 1.3465 1.2237 1.0828 0.98073 0.90453 0.79603 0.72065 0.66414 0.61963 0.58332 0.55289 0.52672 0.48418 0.46655 0.45845 0.43657 0.38648 0.38197 0.34456 0.33261 0.31702 0.30368 0.27886 0.25349 0.23535 0.22172 0.21112 0.19555 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Beryllium/Alpha = 47 2.1755 2.0197 1.9574 1.9532 1.9994 2.0549 2.1528 2.2654 2.3273 2.4005 2.4691 2.4631 2.386 2.201 2.0413 1.9112 1.8044 1.7152 1.5741 1.4666 1.3445 1.2018 1.0972 1.0184 0.90544 0.82644 0.76699 0.72002 0.68162 0.6494 0.62165 0.57646 0.55771 0.54909 0.52576 0.47224 0.46741 0.42726 0.41441 0.39761 0.3832 0.3563 0.32865 0.30873 0.29366 0.28185 0.26436 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Boron/Alpha = 47 1.9391 1.7518 1.6732 1.6512 1.6863 1.7333 1.8211 1.9305 1.9947 2.0766 2.175 2.2299 2.2258 2.1332 2.0254 1.9263 1.8393 1.7634 1.6382 1.5391 1.4235 1.2845 1.1807 1.1016 0.98693 0.90608 0.84491 0.79641 0.75666 0.72322 0.6944 0.64736 0.6278 0.6188 0.59444 0.5384 0.53333 0.49115 0.47762 0.45991 0.44469 0.41622 0.38682 0.36554 0.34937 0.33664 0.31766 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Carbon/Alpha = 47 1.7461 1.5389 1.4497 1.4125 1.4364 1.4737 1.5473 1.6449 1.705 1.785 1.8906 1.9711 2.0151 2.0041 1.9503 1.8869 1.8242 1.7656 1.6626 1.5765 1.4718 1.3413 1.2411 1.1636 1.0496 0.96834 0.90641 0.85708 0.81652 0.78232 0.75278 0.70446 0.68433 0.67507 0.64994 0.59202 0.58678 0.54304 0.52898 0.51055 0.4947 0.46498 0.43419 0.41183 0.39477 0.38129 0.36112 /Gy
+
+dv:Sc/CellGeneric_abR2/HCP/Proton/Beta = 47 0.00038053 2.9671e-05 5.7006e-05 0.00016439 0.0001788 8.1661e-05 0.00017233 0.0013199 0.0029488 0.0074839 0.015662 0.021396 0.025424 0.030718 0.034094 0.036474 0.038265 0.039676 0.041783 0.043306 0.044966 0.046834 0.048184 0.049183 0.050592 0.051562 0.052285 0.05282 0.05321 0.053507 0.05374 0.054082 0.054211 0.054269 0.054416 0.054718 0.054743 0.054936 0.054994 0.055065 0.055124 0.055227 0.055326 0.055394 0.055443 0.055481 0.055537 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Helium/Beta = 47 0.00030292 2.1916e-05 4.0513e-05 0.00011406 0.00011567 1.8493e-05 -4.9309e-05 0.00022058 0.00057723 0.0016181 0.005593 0.010707 0.015008 0.021151 0.025256 0.028207 0.030449 0.032224 0.034884 0.036808 0.038905 0.041256 0.042929 0.044162 0.045896 0.047087 0.047972 0.048665 0.049228 0.049699 0.050102 0.050755 0.051025 0.051149 0.051482 0.052242 0.05231 0.052873 0.053051 0.053284 0.053478 0.053813 0.054123 0.054325 0.054469 0.054575 0.054725 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Lithium/Beta = 47 0.00026055 1.7964e-05 3.1976e-05 8.5097e-05 8.1965e-05 7.2841e-06 -6.1809e-05 3.5166e-05 0.00015345 0.00051395 0.0020187 0.0052312 0.0089437 0.015031 0.019427 0.022696 0.025226 0.02725 0.030308 0.032533 0.034961 0.037683 0.039604 0.041016 0.042995 0.044349 0.045353 0.046138 0.046775 0.047306 0.047761 0.048498 0.048802 0.048941 0.049317 0.050175 0.050252 0.050889 0.051092 0.051357 0.051583 0.052003 0.052431 0.052737 0.052967 0.053145 0.053407 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Beryllium/Beta = 47 0.00020389 1.2956e-05 2.2506e-05 6.0656e-05 5.9727e-05 4.0152e-06 -5.3261e-05 6.6391e-07 5.7807e-05 0.00022712 0.00092829 0.0025761 0.0052711 0.010612 0.014921 0.018297 0.020987 0.023179 0.026546 0.029026 0.031751 0.034816 0.03697 0.03855 0.040757 0.042261 0.043373 0.04424 0.044942 0.045526 0.046026 0.046834 0.047166 0.047319 0.047731 0.048669 0.048753 0.04945 0.049672 0.049962 0.05021 0.050671 0.051144 0.051484 0.05174 0.05194 0.052237 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Boron/Beta = 47 0.00016197 9.7368e-06 1.6423e-05 4.3258e-05 4.2208e-05 2.1754e-06 -4.0827e-05 -1.1801e-05 1.538e-05 9.7247e-05 0.00044774 0.0012787 0.0029941 0.0073206 0.011284 0.014587 0.017321 0.019605 0.023191 0.025882 0.028874 0.032267 0.034649 0.036396 0.038833 0.040488 0.041707 0.042655 0.04342 0.044056 0.044599 0.045474 0.045835 0.046 0.046444 0.047456 0.047547 0.048297 0.048537 0.048849 0.049116 0.049613 0.050124 0.050492 0.05077 0.050989 0.051314 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Carbon/Beta = 47 0.00013133 7.5091e-06 1.2318e-05 3.1613e-05 3.0505e-05 1.2878e-06 -3.0592e-05 -1.3569e-05 2.4024e-07 4.2428e-05 0.00022925 0.00067252 0.0016786 0.0049503 0.0083909 0.011468 0.014129 0.016418 0.020118 0.022964 0.026181 0.029878 0.032484 0.0344 0.037071 0.038881 0.040211 0.041241 0.042072 0.04276 0.043347 0.044291 0.044679 0.044856 0.045334 0.046417 0.046515 0.047317 0.047572 0.047906 0.048191 0.048721 0.049267 0.04966 0.049958 0.050193 0.050542 /Gy2
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_McNamara.txt.in b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_McNamara.txt.in
new file mode 100644
index 000000000..1a2eba6c9
--- /dev/null
+++ b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_McNamara.txt.in
@@ -0,0 +1,21 @@
+# McNamara proton RBE scorer
+
+s:Sc/McNamaraAlpha/Quantity = "RBE_McNamara"
+s:Sc/McNamaraAlpha/Component = "Patient"
+s:Sc/McNamaraAlpha/OutputQuantity = "alpha"
+d:Sc/McNamaraAlpha/PrescribedDose = Sc/PrescribedDose Gy
+sv:Sc/McNamaraAlpha/CellLines = Sc/CellLines
+b:Sc/McNamaraAlpha/SimultaneousExposure = Sc/SimultaneousExposure
+s:Sc/McNamaraAlpha/OutputType = Sim/DoseScorerOutputType
+s:Sc/McNamaraAlpha/OutputFile = Sim/ScoreLabel + "_alpha_MCN"
+s:Sc/McNamaraAlpha/IfOutputFileAlreadyExists = "Overwrite"
+
+s:Sc/McNamaraBeta/Quantity = "RBE_McNamara"
+s:Sc/McNamaraBeta/Component = "Patient"
+s:Sc/McNamaraBeta/OutputQuantity = "beta"
+d:Sc/McNamaraBeta/PrescribedDose = Sc/PrescribedDose Gy
+sv:Sc/McNamaraBeta/CellLines = Sc/CellLines
+b:Sc/McNamaraBeta/SimultaneousExposure = Sc/SimultaneousExposure
+s:Sc/McNamaraBeta/OutputType = Sim/DoseScorerOutputType
+s:Sc/McNamaraBeta/OutputFile = Sim/ScoreLabel + "_beta_MCN"
+s:Sc/McNamaraBeta/IfOutputFileAlreadyExists = "Overwrite"
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_Wedenberg.txt.in b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_Wedenberg.txt.in
new file mode 100644
index 000000000..fdd2bb861
--- /dev/null
+++ b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_Wedenberg.txt.in
@@ -0,0 +1,21 @@
+# Wedenberg proton RBE scorer
+
+s:Sc/WedenbergAlpha/Quantity = "RBE_Wedenberg"
+s:Sc/WedenbergAlpha/Component = "Patient"
+s:Sc/WedenbergAlpha/OutputQuantity = "alpha"
+d:Sc/WedenbergAlpha/PrescribedDose = Sc/PrescribedDose Gy
+sv:Sc/WedenbergAlpha/CellLines = Sc/CellLines
+b:Sc/WedenbergAlpha/SimultaneousExposure = Sc/SimultaneousExposure
+s:Sc/WedenbergAlpha/OutputType = Sim/DoseScorerOutputType
+s:Sc/WedenbergAlpha/OutputFile = Sim/ScoreLabel + "_alpha_WED"
+s:Sc/WedenbergAlpha/IfOutputFileAlreadyExists = "Overwrite"
+
+s:Sc/WedenbergBeta/Quantity = "RBE_Wedenberg"
+s:Sc/WedenbergBeta/Component = "Patient"
+s:Sc/WedenbergBeta/OutputQuantity = "beta"
+d:Sc/WedenbergBeta/PrescribedDose = Sc/PrescribedDose Gy
+sv:Sc/WedenbergBeta/CellLines = Sc/CellLines
+b:Sc/WedenbergBeta/SimultaneousExposure = Sc/SimultaneousExposure
+s:Sc/WedenbergBeta/OutputType = Sim/DoseScorerOutputType
+s:Sc/WedenbergBeta/OutputFile = Sim/ScoreLabel + "_beta_WED"
+s:Sc/WedenbergBeta/IfOutputFileAlreadyExists = "Overwrite"
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_libamtrack.txt.in b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_libamtrack.txt.in
new file mode 100644
index 000000000..76e83e4f5
--- /dev/null
+++ b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseRBE_libamtrack.txt.in
@@ -0,0 +1,54 @@
+# libamtrack RBE scorer
+
+s:Sc/tabulatedAlpha/Quantity = "RBE_TabulatedAlphaBeta"
+s:Sc/tabulatedAlpha/Component = "Patient"
+s:Sc/tabulatedAlpha/OutputQuantity = "alpha"
+d:Sc/tabulatedAlpha/PrescribedDose = Sc/PrescribedDose Gy
+sv:Sc/tabulatedAlpha/CellLines = Sc/CellLines
+s:Sc/tabulatedAlpha/OutputType = Sim/DoseScorerOutputType
+s:Sc/tabulatedAlpha/OutputFile = Sim/ScoreLabel + "_alpha_libamtrack"
+s:Sc/tabulatedAlpha/IfOutputFileAlreadyExists = "Overwrite"
+s:Sc/tabulatedAlpha/ModelName = "HCP"
+
+s:Sc/tabulatedBeta/Quantity = "RBE_TabulatedAlphaBeta"
+s:Sc/tabulatedBeta/Component = "Patient"
+s:Sc/tabulatedBeta/OutputQuantity = "beta"
+d:Sc/tabulatedBeta/PrescribedDose = Sc/PrescribedDose Gy
+sv:Sc/tabulatedBeta/CellLines = Sc/CellLines
+s:Sc/tabulatedBeta/OutputType = Sim/DoseScorerOutputType
+s:Sc/tabulatedBeta/OutputFile = Sim/ScoreLabel + "_beta_libamtrack"
+s:Sc/tabulatedBeta/IfOutputFileAlreadyExists = "Overwrite"
+s:Sc/tabulatedBeta/ModelName = "HCP"
+
+#s:Sc/RBE/Quantity = "RBE_TabulatedAlphaBeta"
+#s:Sc/RBE/Component = "Patient"
+#s:Sc/RBE/OutputQuantity = "rbe"
+#d:Sc/RBE/PrescribedDose = Sc/PrescribedDose Gy
+#sv:Sc/RBE/CellLines = Sc/CellLines
+#s:Sc/RBE/OutputType = Sim/DoseScorerOutputType
+#s:Sc/RBE/OutputFile = Sim/ScoreLabel + "_RBE_libamtrack"
+#s:Sc/RBE/IfOutputFileAlreadyExists = "Overwrite"
+#s:Sc/RBE/ModelName = "HCP"
+
+### HCP Tabulated ###
+sv:Sc/CellGeneric_abR2/HCP/ParticleName = 8 "Proton" "Helium" "Lithium" "Beryllium" "Boron" "Carbon" "Nitrogen" "Oxygen"
+iv:Sc/CellGeneric_abR2/HCP/ParticleZ = 8 1 2 3 4 5 6 7 8
+dv:Sc/CellGeneric_abR2/HCP/KineticEnergyPerNucleon = 351 0.001000 0.001047 0.001096 0.001148 0.001202 0.001259 0.001318 0.001380 0.001445 0.001514 0.001585 0.001660 0.001738 0.001820 0.001905 0.001995 0.002089 0.002188 0.002291 0.002399 0.002512 0.002630 0.002754 0.002884 0.003020 0.003162 0.003311 0.003467 0.003631 0.003802 0.003981 0.004169 0.004365 0.004571 0.004786 0.005012 0.005248 0.005495 0.005754 0.006026 0.006310 0.006607 0.006918 0.007244 0.007586 0.007943 0.008318 0.008710 0.009120 0.009550 0.010000 0.010471 0.010965 0.011482 0.012023 0.012589 0.013183 0.013804 0.014454 0.015136 0.015849 0.016596 0.017378 0.018197 0.019055 0.019953 0.020893 0.021878 0.022909 0.023988 0.025119 0.026303 0.027542 0.028840 0.030200 0.031623 0.033113 0.034674 0.036308 0.038019 0.039811 0.041687 0.043652 0.045709 0.047863 0.050119 0.052481 0.054954 0.057544 0.060256 0.063096 0.066069 0.069183 0.072444 0.075858 0.079433 0.083176 0.087096 0.091201 0.095499 0.100000 0.104713 0.109648 0.114815 0.120226 0.125893 0.131826 0.138038 0.144544 0.151356 0.158489 0.165959 0.173780 0.181970 0.190546 0.199526 0.208930 0.218776 0.229087 0.239883 0.251189 0.263027 0.275423 0.288403 0.301995 0.316228 0.331131 0.346737 0.363078 0.380189 0.398107 0.416869 0.436516 0.457088 0.478630 0.501187 0.524807 0.549541 0.575440 0.602560 0.630957 0.660693 0.691831 0.724436 0.758578 0.794328 0.831764 0.870964 0.912011 0.954993 1.000000 1.047129 1.096478 1.148154 1.202264 1.258925 1.318257 1.380384 1.445440 1.513561 1.584893 1.659587 1.737801 1.819701 1.905461 1.995262 2.089296 2.187762 2.290868 2.398833 2.511886 2.630268 2.754229 2.884032 3.019952 3.162278 3.311311 3.467369 3.630781 3.801894 3.981072 4.168694 4.365158 4.570882 4.786301 5.011872 5.248075 5.495409 5.754399 6.025596 6.309573 6.606934 6.918310 7.244360 7.585776 7.943282 8.317638 8.709636 9.120108 9.549926 10.000000 10.471285 10.964782 11.481536 12.022644 12.589254 13.182567 13.803843 14.454398 15.135612 15.848932 16.595869 17.378008 18.197009 19.054607 19.952623 20.892961 21.877616 22.908677 23.988329 25.118864 26.302680 27.542287 28.840315 30.199517 31.622777 33.113112 34.673685 36.307805 38.018940 39.810717 41.686938 43.651583 45.708819 47.863009 50.118723 52.480746 54.954087 57.543994 60.255959 63.095734 66.069345 69.183097 72.443596 75.857758 79.432823 83.176377 87.096359 91.201084 95.499259 100.000000 104.712855 109.647820 114.815362 120.226443 125.892541 131.825674 138.038426 144.543977 151.356125 158.489319 165.958691 173.780083 181.970086 190.546072 199.526231 208.929613 218.776162 229.086765 239.883292 251.188643 263.026799 275.422870 288.403150 301.995172 316.227766 331.131121 346.736850 363.078055 380.189396 398.107171 416.869383 436.515832 457.088190 478.630092 501.187234 524.807460 549.540874 575.439937 602.559586 630.957344 660.693448 691.830971 724.435960 758.577575 794.328235 831.763771 870.963590 912.010839 954.992586 1000.000000 1047.128548 1096.478196 1148.153621 1202.264435 1258.925412 1318.256739 1380.384265 1445.439771 1513.561248 1584.893192 1659.586907 1737.800829 1819.700859 1905.460718 1995.262315 2089.296131 2187.761624 2290.867653 2398.832919 2511.886432 2630.267992 2754.228703 2884.031503 3019.951720 3162.277660 3311.311215 3467.368505 3630.780548 3801.893963 3981.071706 4168.693835 4365.158322 4570.881896 4786.300923 5011.872336 5248.074602 5495.408739 5754.399373 6025.595861 6309.573445 6606.934480 6918.309709 7244.359601 7585.775750 7943.282347 8317.637711 8709.635900 9120.108394 9549.925860 10000.000000 MeV
+
+dv:Sc/CellGeneric_abR2/HCP/Proton/Alpha = 351 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099998 2.099998 2.099998 2.099998 2.099997 2.099997 2.099997 2.099996 2.099996 2.099995 2.099995 2.099994 2.099993 2.099992 2.099991 2.099990 2.099989 2.099988 2.099986 2.099984 2.099982 2.099980 2.099977 2.099975 2.099971 2.099967 2.099963 2.099958 2.099953 2.099947 2.099940 2.099932 2.099922 2.099912 2.099900 2.099886 2.099871 2.099853 2.099832 2.099809 2.099782 2.099750 2.099714 2.099673 2.099625 2.099569 2.099505 2.099430 2.099343 2.099242 2.099124 2.098987 2.098826 2.098637 2.098416 2.098157 2.097853 2.097496 2.097078 2.096590 2.096018 2.095349 2.094565 2.093646 2.092568 2.091300 2.089813 2.088069 2.086021 2.083379 2.080497 2.077112 2.073126 2.068430 2.062886 2.056351 2.048656 2.039573 2.028814 2.016102 2.001115 1.983373 1.962328 1.937442 1.908037 1.873176 1.831949 1.783016 1.727821 1.668947 1.608303 1.547049 1.486481 1.427177 1.369568 1.313610 1.260053 1.208692 1.159434 1.105285 1.060006 1.016858 0.975652 0.936329 0.898759 0.863068 0.828865 0.796180 0.764948 0.735118 0.706663 0.679389 0.653384 0.628436 0.604462 0.581466 0.559347 0.538175 0.517666 0.497675 0.478067 0.459536 0.441596 0.424354 0.407469 0.391028 0.375148 0.359861 0.345049 0.330644 0.316589 0.303180 0.290734 0.279039 0.268105 0.257899 0.248380 0.239440 0.231107 0.223264 0.215933 0.209046 0.202591 0.196579 0.190886 0.185586 0.180601 0.175934 0.171560 0.167444 0.163594 0.159949 0.156529 0.153296 0.150266 0.147418 0.144743 0.142249 0.139900 0.137705 0.135625 0.133672 0.131825 0.130073 0.128420 0.126849 0.125374 0.123982 0.122676 0.121451 0.120300 0.119227 0.118213 0.116715 0.115838 0.115005 0.114214 0.113459 0.112747 0.112070 0.111435 0.110837 0.110277 0.109752 0.109258 0.108798 0.108362 0.107949 0.107557 0.107182 0.106824 0.106482 0.106159 0.105850 0.105558 0.105284 0.105027 0.104787 0.104558 0.104346 0.104147 0.103962 0.103784 0.103614 0.103452 0.103294 0.103145 0.103012 0.102877 0.102748 0.102626 0.102511 0.102402 0.102302 0.102207 0.102115 0.102028 0.101949 0.101876 0.101804 0.101737 0.101673 0.101610 0.101550 0.101489 0.101435 0.101384 0.101334 0.101286 0.101241 0.101199 0.101160 0.101127 0.101094 0.101058 0.101028 0.100999 0.100971 0.100946 0.100918 0.100896 0.100875 0.100856 0.100838 0.100821 0.100802 0.100758 0.100737 0.100722 0.100708 0.100701 0.100681 0.100669 0.100659 0.100648 0.100631 0.100617 0.100612 0.100606 0.100591 0.100586 0.100578 0.100566 0.100558 0.100558 0.100548 0.100541 0.100540 0.100529 0.100524 0.100522 0.100513 0.100508 0.100505 0.100497 0.100494 0.100495 0.100488 0.100485 0.100479 0.100473 0.100468 0.100470 0.100470 0.100467 0.100460 0.100456 0.100460 0.100455 0.100453 0.100448 0.100443 0.100447 0.100442 0.100439 0.100437 0.100431 0.100431 0.100431 0.100429 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Helium/Alpha = 351 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099998 2.099998 2.099998 2.099998 2.099998 2.099997 2.099997 2.099997 2.099996 2.099996 2.099995 2.099995 2.099994 2.099993 2.099992 2.099991 2.099990 2.099989 2.099988 2.099986 2.099985 2.099983 2.099981 2.099978 2.099976 2.099973 2.099969 2.099966 2.099961 2.099956 2.099951 2.099945 2.099938 2.099930 2.099921 2.099912 2.099900 2.099888 2.099873 2.099857 2.099839 2.099818 2.099794 2.099767 2.099736 2.099701 2.099661 2.099616 2.099564 2.099504 2.099436 2.099358 2.099268 2.099165 2.099046 2.098909 2.098751 2.098569 2.098358 2.098113 2.097829 2.097499 2.097114 2.096666 2.096143 2.095469 2.094742 2.093889 2.092884 2.091701 2.090302 2.088659 2.086724 2.084442 2.081739 2.078548 2.074783 2.070327 2.065042 2.058795 2.051407 2.042639 2.032238 2.019928 2.005378 1.988143 1.967699 1.943406 1.914691 1.880708 1.840708 1.795709 1.747785 1.698216 1.648008 1.594079 1.543880 1.494619 1.446443 1.399559 1.354048 1.310208 1.267772 1.226830 1.187374 1.149391 1.112824 1.077512 1.043593 1.010940 0.979521 0.949343 0.920233 0.892287 0.865230 0.838921 0.813263 0.788969 0.765547 0.743136 0.721357 0.700300 0.680051 0.660620 0.641898 0.623806 0.606257 0.589313 0.573021 0.557108 0.541624 0.526581 0.511962 0.497683 0.483806 0.470201 0.456929 0.443930 0.431230 0.418900 0.406749 0.394953 0.383384 0.372065 0.360977 0.350087 0.339455 0.328956 0.318666 0.308514 0.298586 0.288849 0.279305 0.270013 0.260873 0.251994 0.243398 0.235322 0.227764 0.220659 0.213988 0.207661 0.201733 0.196138 0.190889 0.185967 0.181345 0.177031 0.172954 0.166920 0.163392 0.160047 0.156871 0.153844 0.150991 0.148282 0.145739 0.143348 0.141108 0.139012 0.137039 0.135198 0.133452 0.131801 0.130235 0.128740 0.127310 0.125939 0.124647 0.123411 0.122244 0.121147 0.120120 0.119158 0.118244 0.117395 0.116598 0.115854 0.115144 0.114465 0.113815 0.113185 0.112588 0.112022 0.111484 0.110966 0.110472 0.110012 0.109581 0.109181 0.108798 0.108434 0.108095 0.107780 0.107488 0.107210 0.106944 0.106685 0.106435 0.106194 0.105964 0.105751 0.105547 0.105352 0.105164 0.104984 0.104813 0.104652 0.104503 0.104366 0.104238 0.104114 0.103993 0.103878 0.103771 0.103672 0.103580 0.103495 0.103416 0.103341 0.103270 0.103201 0.103033 0.102973 0.102911 0.102854 0.102798 0.102738 0.102687 0.102644 0.102595 0.102553 0.102508 0.102471 0.102433 0.102395 0.102364 0.102326 0.102297 0.102267 0.102240 0.102217 0.102187 0.102162 0.102137 0.102121 0.102095 0.102079 0.102054 0.102035 0.102017 0.102004 0.101984 0.101973 0.101950 0.101936 0.101923 0.101908 0.101901 0.101892 0.101872 0.101859 0.101853 0.101842 0.101835 0.101819 0.101809 0.101802 0.101792 0.101787 0.101773 0.101765 0.101758 0.101750 0.101745 0.101733 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Lithium/Alpha = 351 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099998 2.099998 2.099998 2.099998 2.099997 2.099997 2.099997 2.099996 2.099996 2.099995 2.099995 2.099994 2.099993 2.099992 2.099992 2.099990 2.099989 2.099988 2.099986 2.099985 2.099983 2.099980 2.099978 2.099975 2.099972 2.099968 2.099964 2.099959 2.099954 2.099948 2.099941 2.099933 2.099924 2.099914 2.099902 2.099889 2.099873 2.099855 2.099835 2.099812 2.099785 2.099754 2.099718 2.099677 2.099630 2.099575 2.099512 2.099439 2.099355 2.099257 2.099145 2.099014 2.098863 2.098688 2.098486 2.098250 2.097977 2.097660 2.097253 2.096819 2.096314 2.095727 2.095043 2.094247 2.093318 2.092236 2.090970 2.089493 2.087770 2.085758 2.083407 2.080647 2.077423 2.073653 2.069233 2.064041 2.057947 2.050817 2.042427 2.032558 2.020967 2.007337 1.991298 1.972412 1.950115 1.923845 1.892912 1.856987 1.815316 1.772836 1.729010 1.684374 1.639418 1.594748 1.550729 1.507403 1.464929 1.423504 1.383237 1.344087 1.306046 1.269062 1.233299 1.198686 1.165186 1.132668 1.101338 1.070980 1.041664 1.013177 0.985628 0.958973 0.933115 0.908071 0.883788 0.860352 0.837646 0.815660 0.794391 0.773763 0.753808 0.734401 0.715644 0.697443 0.679798 0.662714 0.646127 0.630099 0.614491 0.599373 0.584686 0.570419 0.556583 0.543100 0.530048 0.517311 0.504941 0.492875 0.481076 0.469523 0.458143 0.447024 0.436064 0.425334 0.414814 0.404501 0.394431 0.384545 0.374969 0.365523 0.356247 0.347087 0.338053 0.329176 0.320357 0.311706 0.303164 0.294786 0.286568 0.278470 0.270552 0.262764 0.250842 0.243386 0.236039 0.228879 0.221957 0.215427 0.209245 0.203465 0.197992 0.192826 0.187980 0.183412 0.179163 0.175169 0.171452 0.167945 0.164611 0.161453 0.158439 0.155569 0.152810 0.150196 0.147714 0.145349 0.143123 0.141032 0.139091 0.137245 0.135507 0.133880 0.132360 0.130938 0.129582 0.128306 0.127073 0.125886 0.124759 0.123678 0.122645 0.121640 0.120691 0.119800 0.118971 0.118202 0.117471 0.116779 0.116129 0.115526 0.114961 0.114433 0.113926 0.113433 0.112959 0.112498 0.112058 0.111645 0.111260 0.110892 0.110536 0.110195 0.109868 0.109557 0.109262 0.108986 0.108734 0.108501 0.108282 0.108068 0.107860 0.107662 0.107475 0.107303 0.107142 0.106781 0.106650 0.106527 0.106407 0.106287 0.106171 0.106057 0.105948 0.105843 0.105743 0.105646 0.105555 0.105468 0.105385 0.105308 0.105235 0.105167 0.105102 0.105040 0.104981 0.104925 0.104871 0.104820 0.104771 0.104724 0.104679 0.104636 0.104595 0.104556 0.104519 0.104483 0.104449 0.104416 0.104384 0.104354 0.104325 0.104297 0.104270 0.104244 0.104219 0.104194 0.104170 0.104147 0.104125 0.104103 0.104082 0.104061 0.104041 0.104022 0.104003 0.103984 0.103966 0.103948 0.103930 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Beryllium/Alpha = 351 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099998 2.099998 2.099998 2.099998 2.099997 2.099997 2.099997 2.099996 2.099996 2.099995 2.099995 2.099994 2.099993 2.099992 2.099991 2.099990 2.099989 2.099988 2.099986 2.099985 2.099983 2.099981 2.099978 2.099975 2.099972 2.099969 2.099965 2.099960 2.099955 2.099949 2.099943 2.099935 2.099927 2.099917 2.099906 2.099893 2.099879 2.099862 2.099843 2.099822 2.099797 2.099768 2.099735 2.099697 2.099653 2.099603 2.099545 2.099479 2.099402 2.099314 2.099211 2.099093 2.098957 2.098799 2.098617 2.098406 2.098135 2.097848 2.097515 2.097130 2.096682 2.096163 2.095560 2.094859 2.094045 2.093097 2.091995 2.090708 2.089211 2.087470 2.085439 2.083062 2.080293 2.077061 2.073284 2.068869 2.063693 2.057631 2.050557 2.042245 2.032496 2.021062 2.007652 1.991902 1.973389 1.951590 1.924760 1.894495 1.859939 1.822224 1.782582 1.741736 1.700284 1.658559 1.617036 1.575993 1.535578 1.495965 1.456989 1.419073 1.382162 1.346225 1.311218 1.277211 1.244310 1.212321 1.181288 1.151141 1.121931 1.093575 1.066001 1.039271 1.013303 0.988137 0.963668 0.939917 0.916862 0.894422 0.872640 0.851417 0.830848 0.810846 0.791413 0.772567 0.754239 0.736489 0.719180 0.702380 0.686030 0.670122 0.654673 0.639607 0.625007 0.610760 0.596930 0.583466 0.570346 0.557565 0.545069 0.532970 0.521174 0.509743 0.498638 0.487836 0.477333 0.467039 0.457055 0.447189 0.437464 0.427853 0.418325 0.408929 0.399600 0.390477 0.381525 0.372799 0.364278 0.355917 0.347770 0.339763 0.327983 0.320182 0.312449 0.304786 0.297182 0.289726 0.282342 0.275128 0.268020 0.261049 0.254248 0.247590 0.241115 0.234730 0.228461 0.222265 0.216136 0.210158 0.204431 0.199096 0.194065 0.189353 0.184928 0.180776 0.176897 0.173234 0.169828 0.166608 0.163574 0.160681 0.157933 0.155326 0.152818 0.150448 0.148188 0.146037 0.143986 0.142014 0.140158 0.138411 0.136793 0.135255 0.133795 0.132438 0.131174 0.130000 0.128885 0.127818 0.126788 0.125796 0.124834 0.123912 0.123051 0.122231 0.121450 0.120700 0.119986 0.119307 0.118664 0.118066 0.117510 0.116993 0.116496 0.116015 0.115558 0.115130 0.114734 0.114363 0.114019 0.113697 0.113396 0.113112 0.112836 0.112164 0.111907 0.111659 0.111420 0.111185 0.110963 0.110755 0.110562 0.110378 0.110203 0.110035 0.109874 0.109722 0.109577 0.109440 0.109310 0.109186 0.109067 0.108954 0.108846 0.108743 0.108645 0.108551 0.108461 0.108376 0.108293 0.108214 0.108138 0.108066 0.107996 0.107929 0.107865 0.107803 0.107744 0.107688 0.107633 0.107582 0.107532 0.107484 0.107438 0.107394 0.107351 0.107310 0.107270 0.107231 0.107194 0.107158 0.107122 0.107088 0.107054 0.107021 0.106989 0.106958 0.106927 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Boron/Alpha = 351 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099998 2.099998 2.099998 2.099998 2.099997 2.099997 2.099997 2.099996 2.099996 2.099995 2.099995 2.099994 2.099993 2.099993 2.099992 2.099991 2.099990 2.099988 2.099987 2.099985 2.099984 2.099982 2.099979 2.099977 2.099974 2.099971 2.099967 2.099963 2.099958 2.099953 2.099947 2.099940 2.099932 2.099924 2.099914 2.099902 2.099889 2.099874 2.099858 2.099838 2.099816 2.099791 2.099762 2.099728 2.099690 2.099646 2.099595 2.099537 2.099470 2.099393 2.099305 2.099202 2.099084 2.098948 2.098791 2.098590 2.098377 2.098131 2.097847 2.097518 2.097137 2.096696 2.096185 2.095593 2.094905 2.094108 2.093182 2.092107 2.090854 2.089398 2.087710 2.085741 2.083451 2.080774 2.077659 2.074032 2.069797 2.064836 2.059050 2.052295 2.044393 2.035152 2.024289 2.011585 1.996721 1.978457 1.957799 1.933588 1.905201 1.872825 1.837592 1.800259 1.761792 1.722635 1.683225 1.643800 1.604707 1.566185 1.528290 1.491025 1.454573 1.419094 1.384439 1.350628 1.317746 1.285824 1.254734 1.224483 1.195091 1.166509 1.138746 1.111728 1.085462 1.059945 1.035133 1.011007 0.987478 0.964628 0.942326 0.920646 0.899493 0.878898 0.858861 0.839324 0.820352 0.801839 0.783851 0.766326 0.749255 0.732660 0.716458 0.700733 0.685368 0.670434 0.655871 0.641654 0.627781 0.614201 0.601023 0.588160 0.575680 0.563549 0.551742 0.540266 0.529039 0.518177 0.507500 0.497059 0.486852 0.476874 0.467174 0.457672 0.448458 0.439456 0.430673 0.422050 0.413505 0.405050 0.396607 0.384459 0.376159 0.368019 0.360068 0.352294 0.344759 0.337376 0.330203 0.323132 0.316132 0.309218 0.302301 0.295428 0.288583 0.281867 0.275232 0.268694 0.262315 0.256048 0.249963 0.243989 0.238198 0.232541 0.226972 0.221484 0.216021 0.210594 0.205187 0.199989 0.195078 0.190463 0.186179 0.182181 0.178476 0.174989 0.171699 0.168634 0.165737 0.163011 0.160414 0.157945 0.155573 0.153299 0.151116 0.149039 0.147093 0.145231 0.143444 0.141719 0.140084 0.138545 0.137104 0.135777 0.134528 0.133339 0.132204 0.131148 0.130164 0.129235 0.128365 0.127540 0.126751 0.125989 0.125259 0.124563 0.123890 0.123246 0.122635 0.122064 0.121523 0.121003 0.120508 0.120034 0.118919 0.118505 0.118112 0.117732 0.117361 0.117012 0.116685 0.116385 0.116106 0.115845 0.115596 0.115356 0.115123 0.114896 0.114679 0.114472 0.114276 0.114091 0.113916 0.113752 0.113596 0.113450 0.113310 0.113177 0.113051 0.112930 0.112816 0.112707 0.112603 0.112503 0.112408 0.112317 0.112231 0.112148 0.112068 0.111992 0.111919 0.111848 0.111779 0.111713 0.111649 0.111586 0.111526 0.111467 0.111411 0.111355 0.111301 0.111248 0.111196 0.111145 0.111096 0.111047 0.110999 0.110952 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Carbon/Alpha = 351 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099998 2.099998 2.099998 2.099998 2.099997 2.099997 2.099997 2.099996 2.099996 2.099995 2.099995 2.099994 2.099993 2.099993 2.099992 2.099991 2.099990 2.099988 2.099987 2.099985 2.099984 2.099982 2.099980 2.099977 2.099974 2.099971 2.099968 2.099964 2.099959 2.099954 2.099948 2.099942 2.099934 2.099926 2.099917 2.099906 2.099894 2.099880 2.099864 2.099845 2.099825 2.099801 2.099774 2.099743 2.099707 2.099666 2.099619 2.099566 2.099504 2.099433 2.099351 2.099257 2.099148 2.099023 2.098863 2.098695 2.098500 2.098276 2.098017 2.097718 2.097372 2.096972 2.096509 2.095973 2.095352 2.094634 2.093801 2.092835 2.091715 2.090410 2.088899 2.087145 2.085107 2.082727 2.079965 2.076752 2.073009 2.068648 2.063553 2.057605 2.050685 2.042586 2.033137 2.022045 2.008479 1.993262 1.975429 1.954487 1.929928 1.901383 1.869398 1.834978 1.798952 1.761948 1.724402 1.686718 1.648930 1.611574 1.574747 1.538486 1.502819 1.467879 1.433874 1.400604 1.368101 1.336449 1.305643 1.275637 1.246373 1.217924 1.190214 1.163278 1.137052 1.111485 1.086628 1.062397 1.038827 1.015788 0.993389 0.971534 0.950222 0.929443 0.909158 0.889417 0.870105 0.851280 0.832903 0.814971 0.797506 0.780433 0.763844 0.747626 0.731846 0.716447 0.701410 0.686729 0.672347 0.658375 0.644721 0.631454 0.618536 0.605941 0.593677 0.581657 0.570012 0.558553 0.547333 0.536354 0.525613 0.515167 0.504935 0.495018 0.485348 0.475952 0.466795 0.457824 0.449083 0.440512 0.428798 0.420620 0.412601 0.404718 0.396902 0.389169 0.381431 0.373783 0.366199 0.358730 0.351412 0.344228 0.337250 0.330432 0.323829 0.317375 0.311022 0.304748 0.298485 0.292239 0.285979 0.279811 0.273718 0.267691 0.261793 0.256012 0.250402 0.244865 0.239451 0.234190 0.229093 0.224162 0.219294 0.214532 0.209760 0.204988 0.200334 0.195795 0.191442 0.187267 0.183394 0.179780 0.176402 0.173254 0.170292 0.167522 0.164929 0.162510 0.160224 0.158067 0.156007 0.154009 0.152104 0.150265 0.148514 0.146857 0.145298 0.143806 0.142359 0.140980 0.139661 0.138413 0.137228 0.136117 0.135091 0.134140 0.133253 0.132396 0.131571 0.130784 0.130046 0.129364 0.128724 0.127257 0.126716 0.126205 0.125715 0.125228 0.124756 0.124300 0.123868 0.123455 0.123060 0.122681 0.122319 0.121976 0.121649 0.121342 0.121051 0.120776 0.120514 0.120265 0.120028 0.119799 0.119581 0.119373 0.119172 0.118980 0.118796 0.118622 0.118455 0.118297 0.118145 0.118000 0.117861 0.117728 0.117600 0.117477 0.117359 0.117246 0.117137 0.117031 0.116930 0.116831 0.116735 0.116643 0.116553 0.116466 0.116382 0.116299 0.116219 0.116141 0.116064 0.115989 0.115916 0.115844 0.115774 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Nitrogen/Alpha = 351 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099998 2.099998 2.099998 2.099998 2.099998 2.099997 2.099997 2.099997 2.099996 2.099996 2.099995 2.099995 2.099994 2.099993 2.099992 2.099992 2.099991 2.099989 2.099988 2.099987 2.099985 2.099983 2.099981 2.099979 2.099977 2.099974 2.099971 2.099967 2.099963 2.099959 2.099954 2.099948 2.099942 2.099934 2.099926 2.099917 2.099906 2.099894 2.099880 2.099865 2.099847 2.099827 2.099804 2.099778 2.099748 2.099713 2.099674 2.099628 2.099577 2.099517 2.099449 2.099370 2.099280 2.099176 2.099043 2.098903 2.098743 2.098557 2.098344 2.098098 2.097814 2.097486 2.097107 2.096669 2.096162 2.095577 2.094900 2.094116 2.093208 2.092157 2.090935 2.089520 2.087880 2.085977 2.083768 2.081192 2.078204 2.074735 2.070695 2.065977 2.060491 2.054104 2.046654 2.037965 2.027311 2.015366 2.001439 1.985151 1.966116 1.943736 1.917615 1.887970 1.855698 1.821529 1.786149 1.750208 1.713868 1.677377 1.641142 1.605316 1.569969 1.535228 1.501025 1.467591 1.434977 1.403069 1.371878 1.341464 1.311840 1.282963 1.254752 1.227294 1.200571 1.174516 1.149095 1.124324 1.100217 1.076669 1.053728 1.031340 1.009499 0.988204 0.967388 0.947117 0.927279 0.907927 0.889003 0.870506 0.852450 0.834766 0.817536 0.800663 0.784219 0.768155 0.752453 0.737115 0.722083 0.707467 0.693172 0.679268 0.665721 0.652502 0.639621 0.626994 0.614747 0.602692 0.590879 0.579310 0.567977 0.556938 0.546108 0.535593 0.525325 0.515331 0.505580 0.496019 0.486698 0.477555 0.465263 0.456576 0.448107 0.439866 0.431821 0.424018 0.416364 0.408903 0.401548 0.394284 0.387081 0.379871 0.372698 0.365545 0.358517 0.351588 0.344778 0.338120 0.331588 0.325262 0.319085 0.313094 0.307237 0.301469 0.295768 0.290039 0.284312 0.278570 0.272910 0.267316 0.261790 0.256426 0.251202 0.246177 0.241252 0.236423 0.231763 0.227203 0.222777 0.218416 0.214119 0.209841 0.205561 0.201314 0.197175 0.193210 0.189358 0.185675 0.182195 0.178960 0.175944 0.173106 0.170462 0.167953 0.165597 0.163397 0.161350 0.159433 0.157614 0.155906 0.154267 0.152687 0.151159 0.149700 0.148307 0.146969 0.145713 0.144526 0.143406 0.142325 0.141287 0.140302 0.139364 0.137155 0.136334 0.135555 0.134806 0.134085 0.133413 0.132788 0.132211 0.131665 0.131143 0.130639 0.130150 0.129681 0.129232 0.128809 0.128410 0.128036 0.127685 0.127355 0.127043 0.126746 0.126466 0.126198 0.125945 0.125703 0.125472 0.125252 0.125042 0.124842 0.124650 0.124467 0.124292 0.124123 0.123961 0.123806 0.123655 0.123511 0.123372 0.123237 0.123106 0.122979 0.122856 0.122737 0.122620 0.122506 0.122395 0.122287 0.122181 0.122078 0.121977 0.121878 0.121782 0.121686 0.121593 /Gy
+dv:Sc/CellGeneric_abR2/HCP/Oxygen/Alpha = 351 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.100000 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099999 2.099998 2.099998 2.099998 2.099998 2.099997 2.099997 2.099997 2.099996 2.099996 2.099995 2.099995 2.099994 2.099994 2.099993 2.099992 2.099991 2.099990 2.099989 2.099988 2.099986 2.099985 2.099983 2.099981 2.099978 2.099976 2.099973 2.099970 2.099966 2.099962 2.099958 2.099953 2.099947 2.099940 2.099933 2.099925 2.099915 2.099905 2.099893 2.099879 2.099863 2.099846 2.099826 2.099803 2.099777 2.099747 2.099712 2.099673 2.099629 2.099577 2.099519 2.099451 2.099374 2.099285 2.099171 2.099052 2.098915 2.098758 2.098576 2.098367 2.098127 2.097849 2.097529 2.097160 2.096734 2.096241 2.095673 2.095016 2.094256 2.093378 2.092361 2.091180 2.089814 2.088233 2.086401 2.084278 2.081806 2.078937 2.075610 2.071743 2.067250 2.062001 2.055905 2.048818 2.040153 2.030434 2.019122 2.005932 1.990531 1.972537 1.951460 1.926802 1.898677 1.867859 1.835062 1.801062 1.766300 1.731137 1.695881 1.660580 1.625660 1.591225 1.557298 1.523875 1.491117 1.459132 1.427777 1.397132 1.367207 1.338041 1.309550 1.281800 1.254657 1.228238 1.202469 1.177325 1.152783 1.128825 1.105487 1.082689 1.060420 1.038700 1.017477 0.996789 0.976527 0.956754 0.937421 0.918516 0.900047 0.881949 0.864296 0.846980 0.830061 0.813499 0.797282 0.781415 0.765848 0.750694 0.735865 0.721425 0.707342 0.693592 0.680183 0.667029 0.654265 0.641699 0.629384 0.617320 0.605502 0.593982 0.582674 0.571683 0.560941 0.550474 0.540247 0.530209 0.520408 0.510785 0.497987 0.488814 0.479859 0.471135 0.462610 0.454333 0.446218 0.438327 0.430588 0.423025 0.415652 0.408438 0.401420 0.394527 0.387779 0.381092 0.374412 0.367727 0.361025 0.354423 0.347885 0.341473 0.335175 0.329012 0.323013 0.317154 0.311489 0.305933 0.300527 0.295222 0.289957 0.284714 0.279445 0.274272 0.269167 0.264130 0.259167 0.254247 0.249446 0.244738 0.240185 0.235715 0.231341 0.227133 0.223086 0.219224 0.215462 0.211753 0.208056 0.204376 0.200692 0.197054 0.193559 0.190150 0.186856 0.183689 0.180708 0.177921 0.175299 0.172858 0.170561 0.168418 0.166384 0.164452 0.162635 0.160937 0.159364 0.157881 0.156490 0.155177 0.153942 0.152775 0.151644 0.148913 0.147883 0.146899 0.145951 0.145024 0.144149 0.143326 0.142554 0.141814 0.141104 0.140416 0.139757 0.139130 0.138535 0.137975 0.137442 0.136937 0.136458 0.136001 0.135568 0.135155 0.134763 0.134387 0.134028 0.133685 0.133356 0.133042 0.132740 0.132451 0.132174 0.131907 0.131653 0.131408 0.131174 0.130949 0.130733 0.130527 0.130328 0.130137 0.129954 0.129777 0.129606 0.129441 0.129281 0.129126 0.128975 0.128828 0.128686 0.128547 0.128411 0.128279 0.128150 0.128023 0.128756 /Gy
+
+dv:Sc/CellGeneric_abR2/HCP/Proton/Beta = 351 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000001 0.000002 0.000003 0.000004 0.000005 0.000010 0.000013 0.000018 0.000024 0.000036 0.000053 0.000070 0.000094 0.000136 0.000194 0.000261 0.000355 0.000505 0.000701 0.000961 0.001322 0.001815 0.002559 0.003480 0.004615 0.005921 0.007413 0.008910 0.010418 0.011912 0.013497 0.014997 0.016439 0.017887 0.019594 0.021067 0.022482 0.023859 0.025211 0.026509 0.027739 0.028944 0.030124 0.031259 0.032336 0.033366 0.034361 0.035305 0.036195 0.037045 0.037860 0.038640 0.039378 0.040094 0.040798 0.041493 0.042151 0.042796 0.043438 0.044075 0.044705 0.045317 0.045913 0.046498 0.047073 0.047637 0.048136 0.048518 0.048799 0.049016 0.049188 0.049327 0.049444 0.049542 0.049629 0.049704 0.049769 0.049826 0.049873 0.049909 0.049934 0.049950 0.049961 0.049965 0.049969 0.049974 0.049980 0.049988 0.049997 0.050005 0.050011 0.050013 0.050011 0.050005 0.049999 0.049994 0.049989 0.049987 0.049987 0.049990 0.049996 0.050001 0.050007 0.050010 0.050012 0.050010 0.050007 0.050001 0.049994 0.049991 0.049990 0.049990 0.049993 0.049997 0.050001 0.050005 0.050007 0.050008 0.050008 0.050006 0.050003 0.049999 0.049996 0.049994 0.049993 0.049993 0.049995 0.049997 0.049999 0.050002 0.050004 0.050005 0.050005 0.050005 0.050004 0.050002 0.050000 0.049997 0.049996 0.049996 0.049996 0.049997 0.049994 0.049995 0.049996 0.049997 0.049998 0.049998 0.049998 0.049999 0.049999 0.049999 0.049998 0.049997 0.049996 0.049995 0.049994 0.049994 0.049995 0.049997 0.049997 0.049997 0.049998 0.049999 0.049999 0.050000 0.050000 0.049999 0.049999 0.050000 0.050000 0.049999 0.049999 0.049998 0.049999 0.049998 0.049998 0.049997 0.049996 0.049996 0.049996 0.049996 0.049997 0.049998 0.049997 0.049995 0.049997 0.049997 0.049997 0.049997 0.049998 0.049999 0.049998 0.049997 0.049999 0.049998 0.049998 0.049999 0.049999 0.049997 0.049998 0.049998 0.049997 0.049999 0.049998 0.049997 0.049999 0.049999 0.049999 0.050000 0.049999 0.049997 0.049999 0.049998 0.050000 0.050000 0.050001 0.050000 0.049998 0.049999 0.050000 0.050000 0.049998 0.049999 0.049998 0.050000 0.050000 0.049998 0.049999 0.049999 0.050000 0.050000 0.050000 0.049999 0.049999 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Helium/Beta = 351 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000002 0.000003 0.000004 0.000005 0.000008 0.000013 0.000016 0.000021 0.000034 0.000047 0.000063 0.000085 0.000123 0.000171 0.000234 0.000321 0.000439 0.000635 0.000876 0.001208 0.001658 0.002315 0.003081 0.003950 0.004909 0.006072 0.007180 0.008290 0.009427 0.010610 0.011795 0.012928 0.014063 0.015213 0.016347 0.017437 0.018515 0.019601 0.020668 0.021703 0.022729 0.023751 0.024762 0.025743 0.026716 0.027683 0.028638 0.029542 0.030415 0.031252 0.032061 0.032833 0.033568 0.034265 0.034927 0.035554 0.036151 0.036715 0.037244 0.037749 0.038231 0.038692 0.039136 0.039571 0.039999 0.040430 0.040862 0.041299 0.041734 0.042162 0.042582 0.042987 0.043383 0.043783 0.044179 0.044580 0.044981 0.045392 0.045810 0.046234 0.046653 0.047067 0.047469 0.047851 0.048222 0.048574 0.048886 0.049117 0.049281 0.049407 0.049514 0.049612 0.049698 0.049771 0.049831 0.049874 0.049902 0.049915 0.049919 0.049925 0.049926 0.049932 0.049943 0.049959 0.049979 0.049997 0.050014 0.050026 0.050030 0.050028 0.050021 0.050009 0.049995 0.049984 0.049974 0.049970 0.049971 0.049978 0.049986 0.049996 0.050006 0.050014 0.050018 0.050019 0.050019 0.050016 0.050008 0.049998 0.049990 0.049984 0.049982 0.049983 0.049986 0.049988 0.049992 0.049997 0.050003 0.050008 0.050010 0.050010 0.050011 0.050011 0.050010 0.050006 0.050001 0.049996 0.049992 0.049990 0.049989 0.049991 0.049992 0.049993 0.049993 0.049995 0.049997 0.049999 0.050002 0.050005 0.050006 0.050006 0.050005 0.050005 0.050006 0.050007 0.050007 0.050006 0.050005 0.050003 0.050001 0.049999 0.049997 0.049995 0.049993 0.049992 0.049992 0.049992 0.049992 0.049994 0.049994 0.049992 0.049993 0.049993 0.049994 0.049993 0.049993 0.049994 0.049993 0.049995 0.049994 0.049995 0.049994 0.049993 0.049995 0.049995 0.049996 0.049994 0.049997 0.049995 0.049997 0.049997 0.049998 0.049996 0.049998 0.049996 0.050000 0.049999 0.049999 0.050001 0.049999 0.049997 0.050001 0.050001 0.049999 0.050000 0.049998 0.050001 0.050001 0.050000 0.050001 0.049999 0.050001 0.050001 0.050001 0.050001 0.049999 0.050001 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Lithium/Beta = 351 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000001 0.000003 0.000004 0.000004 0.000005 0.000010 0.000014 0.000018 0.000022 0.000034 0.000048 0.000063 0.000085 0.000121 0.000165 0.000222 0.000300 0.000405 0.000568 0.000792 0.001077 0.001467 0.002017 0.002665 0.003374 0.004158 0.005048 0.005951 0.006862 0.007793 0.008765 0.009732 0.010684 0.011637 0.012579 0.013565 0.014505 0.015436 0.016357 0.017304 0.018216 0.019122 0.020016 0.020927 0.021818 0.022698 0.023573 0.024452 0.025316 0.026163 0.026997 0.027822 0.028623 0.029404 0.030164 0.030909 0.031626 0.032322 0.032993 0.033640 0.034262 0.034855 0.035425 0.035968 0.036484 0.036975 0.037438 0.037877 0.038288 0.038675 0.039038 0.039382 0.039713 0.040035 0.040358 0.040686 0.041028 0.041382 0.041745 0.042112 0.042471 0.042819 0.043146 0.043458 0.043755 0.044059 0.044363 0.044677 0.045010 0.045357 0.045721 0.046089 0.046452 0.046807 0.047148 0.047474 0.047984 0.048270 0.048558 0.048833 0.049079 0.049278 0.049432 0.049555 0.049657 0.049744 0.049814 0.049869 0.049904 0.049921 0.049922 0.049914 0.049906 0.049901 0.049900 0.049909 0.049927 0.049948 0.049969 0.049994 0.050016 0.050031 0.050037 0.050038 0.050035 0.050026 0.050009 0.049991 0.049976 0.049966 0.049962 0.049965 0.049970 0.049975 0.049982 0.049993 0.050005 0.050014 0.050020 0.050020 0.050021 0.050022 0.050021 0.050014 0.050005 0.049995 0.049985 0.049980 0.049978 0.049980 0.049983 0.049985 0.049986 0.049987 0.049989 0.049993 0.049997 0.050001 0.050006 0.050010 0.050012 0.050011 0.050009 0.050009 0.050011 0.050013 0.050013 0.050012 0.050011 0.050004 0.050000 0.049996 0.049992 0.049989 0.049988 0.049987 0.049987 0.049988 0.049988 0.049989 0.049990 0.049991 0.049992 0.049992 0.049992 0.049992 0.049992 0.049992 0.049992 0.049993 0.049993 0.049993 0.049994 0.049994 0.049994 0.049994 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049995 0.049996 0.049996 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Beryllium/Beta = 351 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000001 0.000003 0.000003 0.000004 0.000008 0.000011 0.000014 0.000018 0.000023 0.000034 0.000049 0.000063 0.000085 0.000119 0.000163 0.000219 0.000296 0.000399 0.000559 0.000783 0.001058 0.001426 0.001929 0.002505 0.003144 0.003842 0.004613 0.005407 0.006209 0.007026 0.007845 0.008736 0.009588 0.010423 0.011265 0.012130 0.012990 0.013820 0.014652 0.015493 0.016328 0.017142 0.017954 0.018771 0.019581 0.020379 0.021174 0.021971 0.022764 0.023549 0.024332 0.025112 0.025888 0.026647 0.027396 0.028132 0.028850 0.029552 0.030235 0.030905 0.031559 0.032197 0.032820 0.033425 0.034014 0.034582 0.035133 0.035663 0.036172 0.036660 0.037127 0.037572 0.037990 0.038384 0.038753 0.039097 0.039417 0.039714 0.039993 0.040254 0.040508 0.040761 0.041024 0.041308 0.041614 0.041941 0.042276 0.042608 0.042928 0.043230 0.043514 0.043774 0.044016 0.044407 0.044660 0.044937 0.045239 0.045562 0.045895 0.046231 0.046559 0.046879 0.047185 0.047470 0.047734 0.047973 0.048204 0.048437 0.048673 0.048919 0.049159 0.049364 0.049518 0.049637 0.049732 0.049808 0.049867 0.049906 0.049931 0.049938 0.049926 0.049908 0.049895 0.049885 0.049885 0.049897 0.049915 0.049933 0.049953 0.049978 0.050004 0.050025 0.050038 0.050040 0.050040 0.050041 0.050034 0.050020 0.050003 0.049984 0.049970 0.049960 0.049957 0.049961 0.049967 0.049969 0.049972 0.049978 0.049986 0.049994 0.050005 0.050014 0.050020 0.050021 0.050019 0.050019 0.050022 0.050024 0.050024 0.050021 0.050017 0.050011 0.050005 0.049996 0.049987 0.049980 0.049975 0.049975 0.049977 0.049980 0.049982 0.049984 0.049985 0.049985 0.049985 0.049985 0.049987 0.049988 0.049988 0.049989 0.049989 0.049990 0.049990 0.049991 0.049992 0.049992 0.049993 0.049994 0.049996 0.049997 0.049998 0.050000 0.050001 0.050003 0.050004 0.050006 0.050007 0.050009 0.050009 0.050011 0.050012 0.050013 0.050014 0.050014 0.050015 0.050015 0.050016 0.050016 0.050016 0.050017 0.050017 0.050017 0.050017 0.050017 0.050017 0.050017 0.050017 0.050016 0.050016 0.050016 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Boron/Beta = 351 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000001 0.000003 0.000003 0.000004 0.000005 0.000010 0.000014 0.000017 0.000021 0.000033 0.000045 0.000059 0.000078 0.000101 0.000150 0.000204 0.000271 0.000367 0.000517 0.000707 0.000956 0.001286 0.001688 0.002233 0.002803 0.003425 0.004085 0.004812 0.005562 0.006292 0.007040 0.007829 0.008621 0.009387 0.010160 0.010961 0.011744 0.012515 0.013285 0.014063 0.014830 0.015588 0.016341 0.017086 0.017843 0.018582 0.019316 0.020044 0.020781 0.021505 0.022231 0.022953 0.023681 0.024403 0.025121 0.025834 0.026541 0.027237 0.027920 0.028591 0.029251 0.029895 0.030527 0.031144 0.031752 0.032343 0.032922 0.033489 0.034044 0.034587 0.035114 0.035625 0.036118 0.036592 0.037048 0.037483 0.037901 0.038294 0.038669 0.039024 0.039356 0.039666 0.039951 0.040215 0.040456 0.040679 0.040889 0.041095 0.041309 0.041545 0.041811 0.042275 0.042590 0.042900 0.043199 0.043481 0.043739 0.043972 0.044181 0.044379 0.044586 0.044800 0.045045 0.045321 0.045619 0.045925 0.046234 0.046540 0.046834 0.047115 0.047378 0.047624 0.047842 0.048035 0.048224 0.048418 0.048623 0.048855 0.049098 0.049316 0.049490 0.049625 0.049728 0.049805 0.049865 0.049904 0.049933 0.049949 0.049945 0.049927 0.049903 0.049885 0.049875 0.049876 0.049889 0.049904 0.049918 0.049933 0.049954 0.049978 0.050002 0.050024 0.050039 0.050042 0.050040 0.050042 0.050045 0.050040 0.050030 0.050017 0.050002 0.049984 0.049970 0.049961 0.049956 0.049955 0.049957 0.049962 0.049965 0.049966 0.049967 0.049971 0.049975 0.049979 0.049989 0.049994 0.050001 0.050007 0.050014 0.050020 0.050023 0.050023 0.050021 0.050018 0.050017 0.050016 0.050017 0.050019 0.050021 0.050023 0.050025 0.050025 0.050025 0.050024 0.050023 0.050022 0.050021 0.050020 0.050019 0.050018 0.050018 0.050017 0.050017 0.050016 0.050016 0.050015 0.050014 0.050013 0.050012 0.050011 0.050010 0.050009 0.050008 0.050006 0.050005 0.050004 0.050003 0.050002 0.050001 0.050000 0.049999 0.049998 0.049997 0.049996 0.049995 0.049994 0.049993 0.049992 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Carbon/Beta = 351 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000002 0.000003 0.000003 0.000004 0.000008 0.000011 0.000014 0.000018 0.000023 0.000034 0.000048 0.000062 0.000083 0.000108 0.000160 0.000218 0.000288 0.000385 0.000534 0.000737 0.000987 0.001321 0.001749 0.002243 0.002784 0.003369 0.003979 0.004682 0.005364 0.006041 0.006738 0.007474 0.008213 0.008927 0.009648 0.010396 0.011129 0.011851 0.012572 0.013302 0.014022 0.014734 0.015440 0.016140 0.016850 0.017543 0.018233 0.018915 0.019604 0.020281 0.020957 0.021630 0.022306 0.022979 0.023650 0.024323 0.024996 0.025666 0.026331 0.026987 0.027637 0.028273 0.028901 0.029516 0.030121 0.030714 0.031298 0.031873 0.032435 0.032985 0.033521 0.034044 0.034556 0.035055 0.035544 0.036016 0.036479 0.036930 0.037366 0.037788 0.038190 0.038575 0.038939 0.039282 0.039602 0.039901 0.040179 0.040433 0.040666 0.040965 0.041151 0.041327 0.041507 0.041704 0.041931 0.042192 0.042475 0.042768 0.043060 0.043344 0.043615 0.043863 0.044087 0.044281 0.044454 0.044622 0.044797 0.044992 0.045225 0.045489 0.045765 0.046048 0.046336 0.046618 0.046888 0.047141 0.047383 0.047612 0.047813 0.047984 0.048137 0.048296 0.048461 0.048652 0.048871 0.049090 0.049291 0.049465 0.049613 0.049721 0.049800 0.049861 0.049901 0.049931 0.049955 0.049960 0.049948 0.049927 0.049902 0.049880 0.049870 0.049869 0.049877 0.049892 0.049904 0.049912 0.049924 0.049941 0.049959 0.049976 0.049997 0.050017 0.050034 0.050043 0.050042 0.050039 0.050039 0.050044 0.050048 0.050048 0.050042 0.050034 0.050010 0.049999 0.049986 0.049974 0.049964 0.049958 0.049954 0.049953 0.049953 0.049954 0.049956 0.049959 0.049961 0.049963 0.049963 0.049963 0.049964 0.049964 0.049965 0.049966 0.049967 0.049968 0.049970 0.049972 0.049974 0.049976 0.049977 0.049979 0.049979 0.049980 0.049980 0.049981 0.049981 0.049982 0.049982 0.049982 0.049982 0.049982 0.049982 0.049982 0.049983 0.049983 0.049983 0.049983 0.049983 0.049983 0.049983 0.049983 0.049983 0.049983 0.049983 0.049983 0.049984 0.049984 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Nitrogen/Beta = 351 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000001 0.000002 0.000003 0.000004 0.000005 0.000010 0.000013 0.000016 0.000020 0.000031 0.000042 0.000055 0.000072 0.000095 0.000135 0.000187 0.000249 0.000332 0.000440 0.000622 0.000843 0.001117 0.001465 0.001910 0.002418 0.002942 0.003506 0.004140 0.004776 0.005415 0.006068 0.006722 0.007438 0.008124 0.008798 0.009480 0.010182 0.010882 0.011562 0.012243 0.012937 0.013624 0.014296 0.014965 0.015642 0.016308 0.016965 0.017619 0.018273 0.018920 0.019562 0.020199 0.020837 0.021470 0.022102 0.022732 0.023365 0.023998 0.024631 0.025265 0.025897 0.026527 0.027148 0.027762 0.028369 0.028966 0.029556 0.030133 0.030703 0.031259 0.031805 0.032340 0.032864 0.033379 0.033880 0.034375 0.034862 0.035340 0.035809 0.036265 0.036712 0.037143 0.037561 0.037963 0.038349 0.038722 0.039077 0.039416 0.039881 0.040172 0.040441 0.040687 0.040910 0.041109 0.041287 0.041446 0.041596 0.041747 0.041917 0.042120 0.042359 0.042625 0.042900 0.043175 0.043447 0.043706 0.043949 0.044167 0.044359 0.044524 0.044667 0.044810 0.044964 0.045149 0.045374 0.045623 0.045879 0.046142 0.046413 0.046672 0.046920 0.047147 0.047363 0.047574 0.047761 0.047919 0.048053 0.048179 0.048313 0.048463 0.048641 0.048844 0.049042 0.049225 0.049398 0.049550 0.049671 0.049761 0.049829 0.049878 0.049909 0.049938 0.049962 0.049971 0.049963 0.049946 0.049926 0.049901 0.049877 0.049866 0.049864 0.049868 0.049877 0.049888 0.049896 0.049901 0.049906 0.049916 0.049929 0.049941 0.049951 0.049984 0.049998 0.050013 0.050027 0.050039 0.050044 0.050044 0.050041 0.050038 0.050036 0.050037 0.050040 0.050045 0.050049 0.050051 0.050052 0.050050 0.050047 0.050043 0.050039 0.050035 0.050031 0.050028 0.050026 0.050023 0.050021 0.050019 0.050017 0.050014 0.050012 0.050009 0.050006 0.050003 0.050000 0.049997 0.049994 0.049991 0.049988 0.049985 0.049983 0.049980 0.049977 0.049975 0.049973 0.049971 0.049969 0.049967 0.049965 0.049963 0.049962 0.049960 0.049959 0.049957 0.049955 /Gy2
+dv:Sc/CellGeneric_abR2/HCP/Oxygen/Beta = 351 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000001 0.000002 0.000003 0.000004 0.000004 0.000008 0.000012 0.000015 0.000019 0.000024 0.000039 0.000052 0.000066 0.000088 0.000126 0.000171 0.000227 0.000302 0.000401 0.000554 0.000762 0.001007 0.001324 0.001738 0.002197 0.002690 0.003219 0.003766 0.004399 0.005011 0.005618 0.006243 0.006904 0.007568 0.008210 0.008866 0.009535 0.010198 0.010851 0.011505 0.012152 0.012822 0.013469 0.014113 0.014752 0.015400 0.016036 0.016663 0.017288 0.017913 0.018533 0.019146 0.019753 0.020362 0.020966 0.021566 0.022164 0.022763 0.023362 0.023959 0.024559 0.025161 0.025764 0.026365 0.026963 0.027558 0.028143 0.028721 0.029288 0.029845 0.030393 0.030930 0.031461 0.031979 0.032493 0.032999 0.033496 0.033986 0.034465 0.034937 0.035396 0.035845 0.036283 0.036710 0.037129 0.037537 0.037934 0.038497 0.038863 0.039213 0.039545 0.039861 0.040157 0.040436 0.040693 0.040931 0.041146 0.041339 0.041508 0.041655 0.041788 0.041914 0.042050 0.042217 0.042422 0.042663 0.042917 0.043178 0.043438 0.043693 0.043935 0.044158 0.044359 0.044532 0.044680 0.044804 0.044924 0.045058 0.045223 0.045429 0.045653 0.045880 0.046115 0.046359 0.046602 0.046833 0.047055 0.047257 0.047454 0.047648 0.047813 0.047948 0.048060 0.048157 0.048264 0.048383 0.048525 0.048694 0.048871 0.049035 0.049195 0.049355 0.049498 0.049615 0.049711 0.049787 0.049844 0.049883 0.049909 0.049933 0.049959 0.049977 0.049981 0.049973 0.049960 0.049945 0.049928 0.049905 0.049883 0.049866 0.049862 0.049867 0.049875 0.049883 0.049891 0.049896 0.049897 0.049898 0.049900 0.049904 0.049913 0.049922 0.049931 0.049938 0.049943 0.049947 0.049951 0.049954 0.049958 0.049962 0.049966 0.049970 0.049975 0.049980 0.049986 0.049991 0.049997 0.050002 0.050007 0.050012 0.050017 0.050022 0.050026 0.050030 0.050033 0.050036 0.050039 0.050041 0.050043 0.050045 0.050046 0.050047 0.050048 0.050048 0.050049 0.050049 0.050049 0.050049 0.050049 0.050049 0.050049 0.050048 0.050048 0.050042 /Gy2
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseToMedium.txt.in b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseToMedium.txt.in
new file mode 100644
index 000000000..5cbd25532
--- /dev/null
+++ b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseToMedium.txt.in
@@ -0,0 +1,8 @@
+#-- physical DoseToMedium scorer
+s:Sc/Patient/Tally_DoseToMedium/Quantity = "DoseToMedium"
+s:Sc/Patient/Tally_DoseToMedium/OutputType = Sim/DoseScorerOutputType # "csv" "binary" "Root" "Xml" or "DICOM"
+s:Sc/Patient/Tally_DoseToMedium/Component = "Patient"
+sv:Sc/Patient/Tally_DoseToMedium/Report = Sim/DoseScorerReport
+s:Sc/Patient/Tally_DoseToMedium/IfOutputFileAlreadyExists = "Overwrite" # "Exit" "Overwrite" or "Increment"
+b:Sc/Patient/Tally_DoseToMedium/OutputToConsole = "False"
+s:Sc/Patient/Tally_DoseToMedium/OutputFile = Sim/ScoreLabel + "_physicalDose"
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseToWater.txt.in b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseToWater.txt.in
new file mode 100644
index 000000000..b4f827118
--- /dev/null
+++ b/matRad/doseCalc/topas/scorer/TOPAS_scorer_doseToWater.txt.in
@@ -0,0 +1,8 @@
+#-- shared physDose sub-scorer
+s:Sc/Tally_DoseToWater/Quantity = "DoseToWater"
+s:Sc/Tally_DoseToWater/OutputType = Sim/DoseScorerOutputType
+s:Sc/Tally_DoseToWater/Component = "Patient"
+b:Sc/Tally_DoseToWater/PreCalculateStoppingPowerRatios = "True"
+s:Sc/Tally_DoseToWater/IfOutputFileAlreadyExists = "Overwrite"
+b:Sc/Tally_DoseToWater/OutputToConsole = "False"
+s:Sc/Tally_DoseToWater/OutputFile = Sim/ScoreLabel + "_doseToWater"
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/scorer/TOPAS_scorer_surfaceIC.txt.in b/matRad/doseCalc/topas/scorer/TOPAS_scorer_surfaceIC.txt.in
new file mode 100644
index 000000000..7404a2a2d
--- /dev/null
+++ b/matRad/doseCalc/topas/scorer/TOPAS_scorer_surfaceIC.txt.in
@@ -0,0 +1,9 @@
+# -- surface track count scorer
+s:Sc/IC/Quantity = "SurfaceTrackCount"
+s:Sc/IC/OutputType = "csv" # "csv" "binary" "Root" "Xml" or "DICOM"
+s:Sc/IC/Surface = "IC/ZPlusSurface"
+sv:Sc/IC/Report = 1 "Sum"
+s:Sc/IC/IfOutputFileAlreadyExists = "Overwrite" # "Exit" "Overwrite" or "Increment"
+s:Sc/IC/OutputFile = Sim/ScoreLabel + "_IC"
+s:Sc/IC/OnlyIncludeParticlesOfGeneration = "Primary"
+s:Sc/IC/OnlyIncludeParticlesGoing = "In"
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/scorer/TOPAS_subscorer_LET.txt.in b/matRad/doseCalc/topas/scorer/TOPAS_subscorer_LET.txt.in
new file mode 100644
index 000000000..8b6da0cac
--- /dev/null
+++ b/matRad/doseCalc/topas/scorer/TOPAS_subscorer_LET.txt.in
@@ -0,0 +1,6 @@
+#-- shared LET sub-scorer
+s:Sc/ProtonLET/Quantity = "ProtonLET"
+s:Sc/ProtonLET/Component = "Patient"
+s:Sc/ProtonLET/IfOutputFileAlreadyExists = "Overwrite"
+s:Sc/ProtonLET/OutputType = Sim/DoseScorerOutputType
+s:Sc/ProtonLET/OutputFile = Sim/ScoreLabel + "_LET"
\ No newline at end of file
diff --git a/matRad/doseCalc/topas/world/TOPAS_matRad_geometry.txt.in b/matRad/doseCalc/topas/world/TOPAS_matRad_geometry.txt.in
new file mode 100644
index 000000000..4eb3c522e
--- /dev/null
+++ b/matRad/doseCalc/topas/world/TOPAS_matRad_geometry.txt.in
@@ -0,0 +1,29 @@
+s:Ge/World/Material = Sim/WorldMaterial
+d:Ge/World/HLX=200.0 cm
+d:Ge/World/HLY=200.0 cm
+d:Ge/World/HLZ=200.0 cm
+b:Ge/World/Invisible = "TRUE"
+
+s:Ge/Nozzle/Parent = "Isocenter"
+s:Ge/Nozzle/Type = "Group"
+
+s:Ge/Isocenter/Parent = "Gantry"
+s:Ge/Isocenter/Type = "Group"
+d:Ge/Isocenter/RotX = +90. deg
+d:Ge/Isocenter/RotY = 0. deg
+d:Ge/Isocenter/RotZ = +90. deg
+
+s:Ge/Gantry/Parent = "Couch"
+s:Ge/Gantry/Type = "Group"
+d:Ge/Gantry/RotX = 0. deg
+d:Ge/Gantry/RotY = 0. deg
+d:Ge/Gantry/RotZ = -1 * Sim/GantryAngle deg
+
+s:Ge/Couch/Parent = "World"
+s:Ge/Couch/Type = "Group"
+d:Ge/Couch/TransX = 0. cm
+d:Ge/Couch/TransY = 0. cm
+d:Ge/Couch/TransZ = 0. cm
+d:Ge/Couch/RotX = 0. deg
+d:Ge/Couch/RotY = -1 * Sim/CouchAngle deg
+d:Ge/Couch/RotZ = 0. deg
\ No newline at end of file
diff --git a/matRad_addMargin.m b/matRad/geometry/matRad_addMargin.m
similarity index 97%
rename from matRad_addMargin.m
rename to matRad/geometry/matRad_addMargin.m
index 2cbdc71e9..b2ee6b374 100644
--- a/matRad_addMargin.m
+++ b/matRad/geometry/matRad_addMargin.m
@@ -24,7 +24,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/geometry/matRad_cubeCoords2worldCoords.m b/matRad/geometry/matRad_cubeCoords2worldCoords.m
new file mode 100644
index 000000000..d39743a84
--- /dev/null
+++ b/matRad/geometry/matRad_cubeCoords2worldCoords.m
@@ -0,0 +1,55 @@
+function coord = matRad_cubeCoords2worldCoords(cCoord, gridStruct, allowOutside)
+% matRad function to convert cube coordinates to world coordinates
+%
+% call
+% coord = matRad_worldToCubeCoordinates(vCoord, gridStruct)
+%
+% inputs
+% cCoord: cube coordinates [vx vy vz] (Nx3 in mm)
+% gridStruct: matRad ct struct or dij.doseGrid/ctGrid struct
+% allowOutside: indices not within the image bounds will be calculated
+% optional, default is true
+%
+% outputs
+% coord: worldCoordinates [x y z] (Nx3 in mm)
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if nargin < 3
+ allowOutside = true;
+end
+
+gridStruct = matRad_getWorldAxes(gridStruct);
+
+firstVoxWorld = [min(gridStruct.x) min(gridStruct.y) min(gridStruct.z)];
+firstVoxCube = [gridStruct.resolution.x gridStruct.resolution.y gridStruct.resolution.z];
+translation = firstVoxWorld - firstVoxCube;
+
+% If we don't allow outside coordinates, we check here
+if (~allowOutside)
+ minBoundsCube = firstVoxCube./2;
+ maxBoundsCube = firstVoxCube./2 + gridStruct.dimensions([2 1 3]).*[gridStruct.resolution.x gridStruct.resolution.y gridStruct.resolution.z];
+ violateBounds = any(any(cCoord < minBoundsCube | cCoord > maxBoundsCube));
+
+ if violateBounds
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Queried world coordinate is outside the ct');
+ end
+end
+
+% find the closest world coord index in gridStruct.x/y/z
+% calc absolute differences and locate smallest difference
+coord = cCoord + translation;
\ No newline at end of file
diff --git a/matRad/geometry/matRad_cubeIndex2worldCoords.m b/matRad/geometry/matRad_cubeIndex2worldCoords.m
new file mode 100644
index 000000000..99bb7432a
--- /dev/null
+++ b/matRad/geometry/matRad_cubeIndex2worldCoords.m
@@ -0,0 +1,51 @@
+function coord = matRad_cubeIndex2worldCoords(cubeIx, gridStruct)
+% matRad function to convert cube indices to world coordinates
+%
+% call
+% coord = matRad_cubeIndex2worldCoords(vCoord, gridStruct)
+%
+% inputs
+% cCoord: cube indices [i j k] (Nx3) or [linIx] (Nx1)
+% gridStruct: matRad ct struct or dij.doseGrid/ctGrid struct
+% allowOutside: indices not within the image bounds will be calculated
+% optional, default is true
+%
+% outputs
+% coord: worldCoordinates [x y z] (Nx3 in mm)
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%Sanitize grid / cube dimensions
+if isfield(gridStruct,'cubeDim')
+ gridStruct.dimensions = gridStruct.cubeDim;
+end
+
+%Check if we have linear indices
+if size(cubeIx,2) == 1
+ [cubeIx(:,1),cubeIx(:,2),cubeIx(:,3)] = ind2sub(gridStruct.dimensions,cubeIx);
+end
+
+%Check if we have the right dimensions
+if size(cubeIx,2) ~= 3
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('voxel coordinates must be Nx3 (subscript indices) or Nx1 (linear indices)!');
+end
+
+%First create cube coordinates
+coord = cubeIx(:,[2 1 3]) .* [gridStruct.resolution.x gridStruct.resolution.y gridStruct.resolution.z];
+coord = matRad_cubeCoords2worldCoords(coord,gridStruct,false);
+
+end
\ No newline at end of file
diff --git a/matRad_getIsoCenter.m b/matRad/geometry/matRad_getIsoCenter.m
similarity index 77%
rename from matRad_getIsoCenter.m
rename to matRad/geometry/matRad_getIsoCenter.m
index 249f79f88..16a0670b7 100644
--- a/matRad_getIsoCenter.m
+++ b/matRad/geometry/matRad_getIsoCenter.m
@@ -23,7 +23,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -39,13 +39,17 @@
V = [];
%Check if any constraints/Objectives have been defined yet
-noObjOrConst = all(cellfun(@isempty,cst(:,6)));
+if size(cst,2) >= 6
+ noObjOrConst = all(cellfun(@isempty,cst(:,6)));
+else
+ noObjOrConst = true;
+end
% Save target indices in V variable.
for i = 1:size(cst,1)
% We only let a target contribute if it has an objective/constraint or
% if we do not have specified objectives/constraints at all so far
- if isequal(cst{i,3},'TARGET') && (~isempty(cst{i,6}) || noObjOrConst)
+ if isequal(cst{i,3},'TARGET') && (noObjOrConst || ~isempty(cst{i,6}))
V = [V; cst{i,4}{1}];
end
end
@@ -56,19 +60,17 @@
% throw error message if no target is found
if isempty(V)
- error('Could not find target');
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Could not find target!');
end
-% Transform subcripts from linear indices
-[yCoordsV, xCoordsV, zCoordsV] = ind2sub(ct.cubeDim,V);
-
% Transform to [mm]
-xCoordsV = xCoordsV * ct.resolution.x;
-yCoordsV = yCoordsV * ct.resolution.y;
-zCoordsV = zCoordsV * ct.resolution.z;
+coord = matRad_cubeIndex2worldCoords(V, ct); %idx2worldcoord
+
% Calculated isocenter.
-isoCenter = mean([xCoordsV yCoordsV zCoordsV]);
+isoCenter = mean(coord);
+
% Visualization
if visBool
@@ -77,7 +79,7 @@
hold on
% Plot target
- plot3(yCoordsV,xCoordsV,zCoordsV,'kx')
+ plot3(coord(:,2), coord(:,1), coord(:,3),'kx')
% Show isocenter: red point
plot3(isoCenter(2),isoCenter(1),isoCenter(3),'r.','MarkerSize',30)
diff --git a/matRad_getRotationMatrix.m b/matRad/geometry/matRad_getRotationMatrix.m
similarity index 97%
rename from matRad_getRotationMatrix.m
rename to matRad/geometry/matRad_getRotationMatrix.m
index 9dcf3c8b0..e47a0ad68 100644
--- a/matRad_getRotationMatrix.m
+++ b/matRad/geometry/matRad_getRotationMatrix.m
@@ -31,7 +31,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/geometry/matRad_getWorldAxes.m b/matRad/geometry/matRad_getWorldAxes.m
new file mode 100644
index 000000000..a285f6a1c
--- /dev/null
+++ b/matRad/geometry/matRad_getWorldAxes.m
@@ -0,0 +1,56 @@
+function gridStruct = matRad_getWorldAxes(gridStruct)
+% matRad function to compute and store world coordinates into ct.x
+%
+% call
+% gridStruct = matRad_getWorldAxes(gridStruct)
+%
+% gridStruct: can be ct, dij.doseGrid,dij.ctGrid
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+update = false;
+if nargin>0
+ if ~ (isfield(gridStruct,{'x','y','z'}))
+ update = true;
+ else
+ if isempty(gridStruct.x)||isempty(gridStruct.y)||isempty(gridStruct.z)
+ update = true;
+ end
+ end
+
+ if update
+ %
+ if isfield(gridStruct,'cubeDim')
+ gridStruct.dimensions = gridStruct.cubeDim;
+ end
+ % check if dicominfo exists
+ if isfield(gridStruct, 'dicomInfo') && isfield(gridStruct.dicomInfo,'ImagePositionPatient')
+ firstVox = gridStruct.dicomInfo.ImagePositionPatient;
+ else
+ firstVox = - (gridStruct.dimensions([2 1 3])./2).*[gridStruct.resolution.x gridStruct.resolution.y gridStruct.resolution.z] ;
+ end
+
+ gridStruct.x = firstVox(1) + gridStruct.resolution.x*(0:gridStruct.dimensions(2)-1);
+ gridStruct.y = firstVox(2) + gridStruct.resolution.y*(0:gridStruct.dimensions(1)-1);
+ gridStruct.z = firstVox(3) + gridStruct.resolution.z*(0:gridStruct.dimensions(3)-1);
+ end
+else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Cannot compute world coordinates without matRad ct or grid structure');
+end
+
+end
diff --git a/matRad_resizeCstToGrid.m b/matRad/geometry/matRad_resizeCstToGrid.m
similarity index 77%
rename from matRad_resizeCstToGrid.m
rename to matRad/geometry/matRad_resizeCstToGrid.m
index 011e0e1dc..5206ca2fc 100644
--- a/matRad_resizeCstToGrid.m
+++ b/matRad/geometry/matRad_resizeCstToGrid.m
@@ -1,5 +1,5 @@
function cst = matRad_resizeCstToGrid(cst,vXgridOld,vYgridOld,vZgridOld,vXgridNew,vYgridNew,vZgridNew)
-% matRad function to resize the ct to a given resolution
+% matRad function to resize the cst to a given resolution
%
% call
% cst = matRad_resizeCstToGrid(cst,vXgridOld,vYgridOld,vZgridOld,vXgridNew,vYgridNew,vZgridNew)
@@ -25,16 +25,24 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispInfo('Resampling structure set... ');
-for i = 1:size(cst,1)
- for j = 1:numel(cst{i,4})
+if isequal(vXgridOld,vXgridNew) && isequal(vYgridOld,vYgridNew) && isequal(vZgridOld,vZgridNew)
+ matRad_cfg.dispInfo('nothing to be done, grids are the same!\n');
+ return;
+end
+
+
+for i = 1:size(cst,1) % loop over all structures
+ for j = 1:numel(cst{i,4}) % loop over all scenarios
tmpCube = zeros([numel(vYgridOld) numel(vXgridOld) numel(vZgridOld)]);
tmpCube(cst{i,4}{j}) = 1;
cst{i,4}{j} = find(matRad_interp3(vXgridOld,vYgridOld,vZgridOld, ...
@@ -45,4 +53,6 @@
matRad_cfg.dispWarning('Resizing the cst to the dose grid created an empty structure %s in scenario %d (cst{%d,4}{%d})!',cst{i,2},j,i,j);
end
end
-end
\ No newline at end of file
+end
+
+matRad_cfg.dispInfo('Done!\n');
diff --git a/matRad/geometry/matRad_selectVoxelsFromCst.m b/matRad/geometry/matRad_selectVoxelsFromCst.m
new file mode 100644
index 000000000..fb8c57d51
--- /dev/null
+++ b/matRad/geometry/matRad_selectVoxelsFromCst.m
@@ -0,0 +1,133 @@
+function [includeMask] = matRad_selectVoxelsFromCst(cstOnDoseGrid, doseGrid, selectionMode)
+% matRad function to get mask of the voxels (on dose grid) that are
+% included in cst structures specified by selectionMode.
+%
+% call
+% includeMask = matRad_getVoxelsOnCstStructs(cst,doseGrid,VdoseGrid,selectionMode)
+%
+% input
+% cstOnDoseGrid: cstOnDoseGrid (voxel indexes included in cst{:,4} are referred to a cube of dimensions doseGrid.dimensions)
+% doseGrid: doseGrid struct containing field doseGrid.dimensions
+% selectionMode: define wich method to apply to select the cst
+% structures to include. Choices are:
+% all all voxels will be included
+% targetOnly only includes the voxels in structures labeld as target
+% oarsOnly only includes the voxels in structures labeld as oars
+% objectivesOnly only includes the voxels in structures with at least one objective/constraint
+% robustnessOnly only includes the voxels in structures with robustness objectives/constraints
+% [indexes] only includes the voxels in structures specified by index array (i.e. [1,2,3] includes first three structures)
+%
+%
+% output
+%
+% includeMask: logical array #voxels in dose grid
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2023 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ matRad_cfg = MatRad_Config.instance();
+ cstOnDoseGrid = matRad_setOverlapPriorities(cstOnDoseGrid);
+
+ selectedCstStructs = [];
+
+ includeMask = cell(size(cstOnDoseGrid{1,4},2),1);
+ includeMask(:) = {false(prod(doseGrid.dimensions),1)};
+
+ if isequal(selectionMode , 'all')
+ for ctScenIdx=1:size(includeMask,2)
+ includeMask{ctScenIdx}(:) = true;
+ end
+ else
+
+ if ischar(selectionMode)
+
+ switch selectionMode
+ case 'targetOnly'
+ selectedCstStructs = find(cellfun(@(x) strcmp(x,'TARGET'), [cstOnDoseGrid(:,3)]));
+ case 'objectivesOnly'
+ for i=1:size(cstOnDoseGrid,1)
+ if numel(cstOnDoseGrid{i,6})>0
+ selectedCstStructs = [selectedCstStructs, i];
+ end
+ end
+ case 'oarsOnly'
+ selectedCstStructs = find(cellfun(@(x) strcmp(x,'OAR'), [cstOnDoseGrid(:,3)]));
+ case 'robustnessOnly'
+ for i=1:size(cstOnDoseGrid,1)
+ for j = 1:numel(cstOnDoseGrid{i,6})
+ if isfield(cstOnDoseGrid{i,6}{j}, 'robustness') && ~isequal(cstOnDoseGrid{i,6}{j}.robustness, 'none')
+ selectedCstStructs = [selectedCstStructs,i];
+ end
+ end
+ end
+ otherwise
+ matRad_cfg.dispError('Unrecognized voxel selection mode: %s', selectionMode);
+ end
+ elseif isnumeric(selectionMode)
+
+ selectedCstStructs = unique(intersect(selectionMode, [cstOnDoseGrid{:,1}]+1));
+ if ~isequal(selectedCstStructs, unique(selectionMode))
+ matRad_cfg.dispWarning('Specified structures are not compatible with cst structures. Only performing calculation on stuctures: %s',num2str(selectedCstStructs));
+ end
+ end
+
+ %loop over all cst sturctures
+ for i=1:size(cstOnDoseGrid,1)
+
+ if ~isempty(cstOnDoseGrid{i,4}{1})
+
+ if numel(cstOnDoseGrid{i,6}) > 0
+ %loop over obj/constraint functions
+ for j = 1:numel(cstOnDoseGrid{i,6})
+
+
+ obj = cstOnDoseGrid{i,6}{j};
+
+ if ~isa(obj,'matRad_DoseOptimizationFunction')
+ try
+ obj = matRad_DoseOptimizationFunction.createInstanceFromStruct(obj);
+ catch
+ matRad_cfg.dispError('cst{%d,6}{%d} is not a valid Objective/constraint! Remove or Replace and try again!',i,j);
+ end
+ end
+
+ robustness = obj.robustness;
+
+ if any(intersect(i, selectedCstStructs))
+ for ctIdx=1:size(cstOnDoseGrid{i,4},2)
+ includeMask{ctIdx}(cstOnDoseGrid{i,4}{ctIdx}) = 1;
+ end
+
+ if isequal(robustness, 'none')
+ matRad_cfg.dispWarning('Including cst structure %s even though this structure has no robustness.', cstOnDoseGrid{i,2});
+ end
+ else
+ matRad_cfg.dispWarning('Excluding cst structure %s even though this structure has an objective or constratint.', cstOnDoseGrid{i,2});
+
+ if ~isequal(robustness, 'none')
+ matRad_cfg.dispWarning('Excluding cst structure %s even though this structure has robustness.', cstOnDoseGrid{i,2});
+ end
+ end
+ end
+
+ else %numel(cst{i,6}) <= 0
+ if any(intersect(i, selectedCstStructs))
+ matRad_cfg.dispWarning('Including cst structure %s even though this structure does not have any objective or constraint', cstOnDoseGrid{i,2}');
+ end
+ end %numel(cst{i,6}) > 0
+ end %if cst{i,4} not empty
+
+ end %for loop over cst
+
+ end
+end
\ No newline at end of file
diff --git a/matRad_setOverlapPriorities.m b/matRad/geometry/matRad_setOverlapPriorities.m
similarity index 84%
rename from matRad_setOverlapPriorities.m
rename to matRad/geometry/matRad_setOverlapPriorities.m
index b7162f23f..c1d3c7cea 100644
--- a/matRad_setOverlapPriorities.m
+++ b/matRad/geometry/matRad_setOverlapPriorities.m
@@ -25,17 +25,22 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+
+matRad_cfg.dispInfo('Adjusting structures for overlap... ');
+
numOfCtScenarios = unique(cellfun(@(x)numel(x),cst(:,4)));
+%Sanity check
if numel(numOfCtScenarios) > 1
- error('Inconsistent number of segmentations in cst struct.');
+ matRad_cfg.dispError('\nInconsistent number of segmentations in cst struct.');
end
for i = 1:numOfCtScenarios
@@ -56,7 +61,7 @@
cst{j,4}{i} = idx;
if isempty(cst{j,4}{i}) && ~isempty(cst{j,6})
- error([cst{j,2} ': Objective(s) and/or constraints for inverse planning defined ' ...
+ matRad_cfg.dispWarning(['\n' cst{j,2} ': Objective(s) and/or constraints for inverse planning defined ' ...
'but structure overlapped by structure with higher overlap priority.' ...
'Objective(s) will not be considered during optimization']);
end
@@ -71,7 +76,8 @@
overlapPriorityCube(cst{i,4}{1}) = cst{i,5}.Priority;
end
end
-
+
+matRad_cfg.dispInfo('Done!\n');
end
diff --git a/matRad/geometry/matRad_world2cubeCoords.m b/matRad/geometry/matRad_world2cubeCoords.m
new file mode 100644
index 000000000..02bcf04d1
--- /dev/null
+++ b/matRad/geometry/matRad_world2cubeCoords.m
@@ -0,0 +1,58 @@
+function coord = matRad_world2cubeCoords(wCoord, gridStruct , allowOutside)
+% matRad function to convert world coordinates to cube coordinates
+%
+% call
+% coord = world2cubeCoords(wCoord, ct)
+%
+%
+% wCoord: world coordinates array Nx3 (x,y,z) [mm]
+% gridStruct: can be matRad ct, dij.doseGrid, or the ctGrid
+% required fields x,y,x,dimensions,resolution
+% allowOutside: indices not within the image bounds will be calculated
+% optional, default is true
+%
+% coord : cube coordinates (x,y,z) - Nx3 in mm
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if nargin < 3
+ allowOutside = true;
+end
+
+gridStruct = matRad_getWorldAxes(gridStruct);
+
+firstVoxWorld = [min(gridStruct.x) min(gridStruct.y) min(gridStruct.z)];
+firstVoxCube = [gridStruct.resolution.x gridStruct.resolution.y gridStruct.resolution.z];
+translation = firstVoxCube - firstVoxWorld;
+
+% If we don't allow outside coordinates, we check here
+if (~allowOutside)
+ % world bounds
+ minBoundsWorld = [min(gridStruct.x)-gridStruct.resolution.x/2 min(gridStruct.y)-gridStruct.resolution.y/2 min(gridStruct.z)-gridStruct.resolution.z/2];
+ maxBoundsWorld = [max(gridStruct.x)+gridStruct.resolution.x/2 max(gridStruct.y)+gridStruct.resolution.y/2 max(gridStruct.z)+gridStruct.resolution.z/2];
+ violateBounds = any(any(wCoord < minBoundsWorld | wCoord > maxBoundsWorld));
+
+ if violateBounds
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Queried world coordinate is outside the ct');
+ end
+end
+
+% find the closest world coord index in gridStruct.x/y/z
+% calc absolute differences and locate smallest difference
+coord = wCoord + translation;
+
+end
diff --git a/matRad/geometry/matRad_world2cubeIndex.m b/matRad/geometry/matRad_world2cubeIndex.m
new file mode 100644
index 000000000..3c047b1bf
--- /dev/null
+++ b/matRad/geometry/matRad_world2cubeIndex.m
@@ -0,0 +1,50 @@
+function indices = matRad_world2cubeIndex(wCoord, gridStruct,allowOutside)
+% matRad function to convert world coordinates to cube indices
+%
+% call
+% coord = world2cubeCoords(wCoord, ct)
+%
+%
+% wCoord: world coordinates array Nx3 (x,y,z) [mm]
+% gridStruct: can be matRad ct, dij.doseGrid, or the ctGrid
+% required fields x,y,x,dimensions,resolution
+% allowOutside: If coordinates outside are allowed. False default.
+%
+% index: cube index (i,j,k) honoring Matlab permuatation of i,j
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if nargin < 3
+ allowOutside = false;
+end
+
+if size(wCoord,2) ~= 3
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('world coordinates must be Nx3');
+end
+
+gridStruct = matRad_getWorldAxes(gridStruct);
+
+%First obtain coordinates in multiples of the resolution
+coords = matRad_world2cubeCoords(wCoord,gridStruct,allowOutside);
+
+%Now get the indices
+coords = round(coords ./ [gridStruct.resolution.x gridStruct.resolution.y gridStruct.resolution.z]);
+
+%Do the permutation
+indices = coords(:,[2 1 3]);
+
+end
diff --git a/matRad/gfx/dkfz_logo_blue.png b/matRad/gfx/dkfz_logo_blue.png
new file mode 100644
index 000000000..a56d2f1b2
Binary files /dev/null and b/matRad/gfx/dkfz_logo_blue.png differ
diff --git a/matRad/gfx/dkfz_logo_grey.png b/matRad/gfx/dkfz_logo_grey.png
new file mode 100644
index 000000000..38d93c26e
Binary files /dev/null and b/matRad/gfx/dkfz_logo_grey.png differ
diff --git a/matRad/gfx/dkfz_logo_white.png b/matRad/gfx/dkfz_logo_white.png
new file mode 100644
index 000000000..f2a75cf03
Binary files /dev/null and b/matRad/gfx/dkfz_logo_white.png differ
diff --git a/matRad/gfx/matrad_logo.png b/matRad/gfx/matrad_logo.png
new file mode 100644
index 000000000..cb4bc43f7
Binary files /dev/null and b/matRad/gfx/matrad_logo.png differ
diff --git a/matRad/gui/icons8-gamma-16.png b/matRad/gui/icons8-gamma-16.png
new file mode 100644
index 000000000..2e8aa6d0f
Binary files /dev/null and b/matRad/gui/icons8-gamma-16.png differ
diff --git a/matRad/gui/matRad_InfoWidget_uiwrapper.m b/matRad/gui/matRad_InfoWidget_uiwrapper.m
new file mode 100644
index 000000000..6da0f18dc
--- /dev/null
+++ b/matRad/gui/matRad_InfoWidget_uiwrapper.m
@@ -0,0 +1,132 @@
+classdef matRad_InfoWidget_uiwrapper < matRad_Widget
+% Class that defines and deploys the infoWidget to display matRad information
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+
+ end
+
+ methods
+ function this = matRad_InfoWidget_uiwrapper(handleParent)
+ if nargin < 1
+ handleParent = matRad_uiwrapper('uifigure',...
+ 'Units','pixels',...%'characters',...
+ 'Position',[150 450 190 130],...%[100 45 25 7],...
+ ...%'Visible',1,...%'on',...
+ 'Color',[0.501960784313725 0.501960784313725 0.501960784313725],...
+ ...%'IntegerHandle',0,...%'off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+...% 'MenuBar','none',...
+ 'Name','MatRad Info',...
+ ...%'NumberTitle',0,...%'off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1');
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this)
+ h94 = this.widgetHandle;
+
+ h95 = matRad_uiwrapper('uibutton',...
+ 'Parent',h94,...
+ 'Position',[60 2 70 30],...%[0.238095238095238 0.134831460674157 0.563492063492063 0.280898876404494],...
+ ...%'Units','pixels',...
+ 'Tag','btnAbout',...
+ 'FontSize',7,...
+ 'text','About',...
+ 'ButtonPushedFcn',@(hObject,eventdata) btnAbout_Callback(this,hObject,eventdata),...
+ 'BackgroundColor',[0.501960784313725 0.501960784313725 0.501960784313725],...
+ 'FontWeight','bold');
+
+ h96 = matRad_uiwrapper('uilabel',...
+ 'Parent',h94,...
+ ...%'Units','normalized',...
+ ...%'String','v3.0.0',...
+ 'Position',[20 80 150 40],...%[0.227106227106227 0.752808988764045 0.523809523809524 0.191011235955056],...
+ 'BackgroundColor',[0.501960784313725 0.501960784313725 0.501960784313725],...
+ 'Tag','text15',...
+ 'FontSize',8,...
+ 'FontWeight','bold');
+
+ h97 = matRad_uiwrapper('uilabel',...
+ 'Parent',h94,...
+ ...%'Units','normalized',...
+ ...%'String','github.com/e0404/matRad',...
+ 'Position',[20 40 150 20],...%[0.0384615384615385 0.528089887640449 0.942307692307693 0.168539325842697],...
+ 'BackgroundColor',[0.501960784313725 0.501960784313725 0.501960784313725],...
+ 'Tag','text31',...
+ 'FontSize',8,...
+ 'FontWeight','bold' );
+
+ this.createHandles();
+ handles=this.handles;
+ %Alter matRad Version string positioning
+ vString = matRad_version();
+ vPos = get(handles.text15,'Position');
+ urlPos = get(handles.text31,'Position');
+ btnPos = get(handles.btnAbout,'Position');
+
+ %vPos([1 3]) = urlPos([1 3]);
+ %vPos([1 3]) = [0 1];
+% vPos(4) = vPos(4)*1.25;
+% btnPos(2) = 0.05;
+% urlPos(2) = btnPos(2)+btnPos(4)+0.05;
+% vPos(2) = urlPos(2) + urlPos(4) + 0.05;
+% vPos(4) = 0.98 - vPos(2);
+
+ set(handles.btnAbout,'Position',btnPos);
+ set(handles.text31,'String','www.matRad.org','Position',urlPos,'Enable','inactive','ButtonDownFcn', @(~,~) web('www.matrad.org','-browser'));
+ set(handles.text15,'String',vString,'Position',vPos);
+
+ this.handles=handles;
+ end
+ end
+
+ methods (Access = protected)
+
+ function btnAbout_Callback(this, hObject, event)
+ handles = this.handles;
+ %msgbox({'https://github.com/e0404/matRad/' 'email: matrad@dkfz.de'},'About');
+
+ matRad_cfg = MatRad_Config.instance();
+ [~,matRadVer] = matRad_version;
+
+ msg{1} = ['matRad ''' matRadVer.name '''']; %Name
+ if matRad_cfg.eduMode
+ msg{1} = [msg{1} ' Educational'];
+ end
+ msg{end+1} = sprintf('v%d.%d.%d',matRadVer.major,matRadVer.minor,matRadVer.patch); %Version Number
+ if isdeployed
+ msg{end+1} = 'Standalone Version';
+ elseif ~isempty(matRadVer.branch) && ~isempty(matRadVer.commitID)
+ msg{end+1} = sprintf('Git: Branch %s, commit %s',matRadVer.branch,matRadVer.commitID(1:8));
+ end
+
+ [env,envver] = matRad_getEnvironment();
+ msg{end+1} = sprintf('Environment: %s v%s %s',env,envver,version('-release'));
+
+ msg{end+1} = 'Web: www.matrad.org';
+ msg{end+1} = 'E-Mail: contact@matrad.org';
+
+ msgbox(msg,'About matRad');
+
+
+ this.handles = handles;
+ end
+ end
+end
diff --git a/matRad/gui/matRad_MainGUI.m b/matRad/gui/matRad_MainGUI.m
new file mode 100644
index 000000000..bd4a0eb86
--- /dev/null
+++ b/matRad/gui/matRad_MainGUI.m
@@ -0,0 +1,619 @@
+classdef matRad_MainGUI < handle
+ % matRad_MainGUI Graphical User Interface configuration class
+ % This Class is used to create the main GUI interface where all the widgets
+ % are called and created.
+ %
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2019 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+ guiHandle
+ lastStoragePath = []
+ end
+
+ properties (Access = private)
+ LogoWidget
+ WorkflowWidget
+ PlanWidget
+ OptimizationWidget
+ VisualizationWidget
+ ViewerOptionsWidget
+ StructureVisibilityWidget
+ InfoWidget
+ ViewingWidget
+ DVHStatsWidget
+ eventListeners
+ GammaWidget
+
+ forceClose = false;
+ end
+
+
+
+ methods(Access = protected)
+ function this = createMenuBar(this)
+ h1 = this.guiHandle;
+ folder = fileparts(mfilename('fullpath'));
+ loadIcons = load(fullfile(folder,'matRad_iconsGUI.mat'),'icons');
+
+ icons = loadIcons.icons;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ h60 = uitoolbar(...
+ 'Parent',h1,...
+ 'Tag','uitoolbar1');
+
+ h61 = uipushtool(...
+ 'Parent',h60,...
+ 'Children',[],...
+ 'BusyAction','cancel',...
+ 'Interruptible','off',...
+ 'Tag','toolbarLoad',...
+ 'CData',icons{1},...
+ 'ClickedCallback',@(hObject,eventdata) toolbarLoad_ClickedCallback(this,hObject,eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Open File' );
+
+ h62 = uipushtool(...
+ 'Parent',h60,...
+ 'BusyAction','cancel',...
+ 'Interruptible','off',...
+ 'Tag','toolbarSave',...
+ 'CData',icons{2},...
+ 'ClickedCallback',@(hObject,eventdata) toolbarSave_ClickedCallback(this,hObject,eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Save Figure');
+
+
+ h63 = uipushtool(...
+ 'Parent',h60,...
+ 'Tag','uipushtool_screenshot',...
+ 'CData',icons{3},...
+ 'ClickedCallback',@(hObject,eventdata)uipushtool_screenshot_ClickedCallback(this, hObject, eventdata),...
+ 'TooltipString','Take a screenshot of the current dose or profile plot' );
+
+ h64 = uitoggletool(...
+ 'Parent',h60,...
+ 'Tag','toolbarZoomIn',...
+ 'CData',icons{4},...
+ 'ClickedCallback',@(hObject, eventdata)toolbarZoomIn_ClickedCallback(this, hObject, eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Zoom In');
+
+ h65 = uitoggletool(...
+ 'Parent',h60,...
+ 'Children',[],...
+ 'Tag','toolbarZoomOut',...
+ 'CData',icons{5},...
+ 'ClickedCallback',@(hObject, eventdata)toolbarZoomOut_ClickedCallback(this, hObject, eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Zoom Out');
+
+ h66 = uitoggletool(...
+ 'Parent',h60,...
+ 'Tag','toolbarPan',...
+ 'CData',icons{6},...
+ 'ClickedCallback',@(hObject, eventdata)toolbarPan_ClickedCallback(this, hObject, eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Pan' );
+
+ h67 = uitoggletool(...
+ 'Parent',h60,...
+ 'Tag','toolbarCursor',...
+ 'CData',icons{7},...
+ 'ClickedCallback',@(hObject, eventdata)toolbarCursor_ClickedCallback(this, hObject, eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Data Cursor' );
+
+ h68 = uitoggletool(...
+ 'Parent',h60,...
+ 'Tag','toolbarLegend',...
+ 'CData',icons{8},...
+ 'ClickedCallback',@(hObject, eventdata)toolbarLegend_ClickedCallback(this, hObject, eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Insert Legend');
+
+ h69 = uitoggletool(...
+ 'Parent',h60,...
+ 'Tag','uitoggletool8',...
+ 'CData',icons{9},...
+ 'ClickedCallback',@(hObject, eventdata)uitoggletool8_ClickedCallback(this, hObject, eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Insert Colorbar' );
+
+
+ % TODO: future dose comparison tool
+ % h70 = uipushtool(...
+ % 'Parent',h60,...
+ % 'Tag','uipushtool_GammaIndex',...
+ % 'CData',icons{10},...
+ % 'ClickedCallback',@(hObject,eventdata)gammaIndex_ClickedCallback(this, hObject, eventdata),...
+ % 'TooltipString','Calculate Gamma Index' );
+
+ h70 = uitoggletool(...
+ 'Parent',h60,...
+ 'Tag','uitoggletool_theme',...
+ 'CData',icons{11},...
+ 'ClickedCallback',@(hObject, eventdata)theme_ClickedCallback(this, hObject, eventdata),...
+ 'Separator','on',...
+ 'TooltipString','Toogle Dark Mode');
+
+
+ % Adapt background color
+ if matRad_isUifigure(h1)
+ h60.BackgroundColor = matRad_cfg.gui.backgroundColor;
+ elseif matRad_cfg.isOctave
+ %Do nothing
+ else
+ try
+ drawnow();
+ jToolbar = get(get(h60,'JavaContainer'),'ComponentPeer');
+
+ cellColor = num2cell(matRad_cfg.gui.backgroundColor);
+ color = java.awt.Color(cellColor{:});
+ jToolbar.setBackground(color);
+ jToolbar.getParent.getParent.setBackground(color);
+ jToolbar.setBorderPainted(false);
+
+ toolbarComponents = jToolbar.getComponents();
+ for c=1:length(toolbarComponents)
+ %toolbarComponents(c).setOpaque(false);
+ toolbarComponents(c).setBackground(color);
+ for subIx = 1 : length(toolbarComponents(c).getComponents())
+ toolbarComponents(c).getComponent(subIx-1).setBackground(color);
+ end
+ end
+ catch ME
+ matRad_cfg.dispDeprecationWarning('Setting the Background Color seems to be deprecated now: %s',ME.message);
+ end
+ end
+ end
+ end
+
+ methods
+ function obj = matRad_MainGUI(varargin)
+ %Panel for Main Widget
+
+ matRad_cfg = MatRad_Config.instance();
+
+ figArgs = {...
+ 'Units','normalized',...
+ 'Position',[0.005 0.04 0.99 0.9],... %approximate fullscreen position
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','matRad (NOT FOR CLINICAL USE!)',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1',...
+ 'CloseRequestFcn',@(src,hEvent) figure1_CloseRequestFcn(obj,src,hEvent)};
+
+ if ~matRad_cfg.isOctave
+ figArgs(end+1:end+2) = {'AutoResizeChildren','Off'};
+ end
+
+ obj.guiHandle = figure(figArgs{:});
+
+ %WindowState not available in all versions
+ if matRad_ispropCompat(obj.guiHandle,'WindowState')
+ set(obj.guiHandle,'WindowState','maximized');
+ end
+
+ if matRad_cfg.isOctave
+ commonPanelProperties = {'Parent',obj.guiHandle,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.highlightColor,...
+ 'Clipping','off',...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Units','normalized', ...
+ 'HighlightColor',matRad_cfg.gui.elementColor};
+
+ else
+ commonPanelProperties = {'Parent',obj.guiHandle,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.highlightColor,...
+ 'Clipping','off',...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'AutoResizeChildren','Off',...
+ 'Units','normalized', ...
+ 'HighlightColor',matRad_cfg.gui.elementColor};
+ end
+
+
+
+ pWorkflow = uipanel(commonPanelProperties{:},...
+ 'Title','Workflow',...
+ 'Tag','uipanel4',...
+ 'Position',[0.005 0.8 0.425 0.2]);
+
+ pPlan = uipanel(commonPanelProperties{:},...
+ 'Title','Plan',...
+ 'Tag','uipanel1',...
+ 'Position',[0.005 0.55 0.425 0.25]);
+
+ pOptimization = uipanel(commonPanelProperties{:},...
+ 'Title',strtrim(strjoin({ 'Objectives & constraints'; ' '; ' ' })),...
+ 'Tag','uipanel3',...
+ 'Position',[0.005 0.21 0.425 0.34]);
+
+ pVisualization = uipanel(commonPanelProperties{:},...
+ 'Title',strtrim(strjoin({ 'Visualization'; ' ' })),...
+ 'Tag','uipanel2',...
+ 'Position',[0.005 0.005 0.425 0.205]);
+
+ pLogo = uipanel(commonPanelProperties{:},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'Tag','uipanel13',...
+ 'Position',[0.44 0.89 0.55 0.1],...
+ 'HighLightColor',[0.501960784313725 0.501960784313725 0.501960784313725],...
+ 'BorderType','none');
+
+ pViewing = uipanel(commonPanelProperties{:},...
+ 'Title','Slice Viewer',...
+ 'Tag','uipanel11',...
+ 'Position',[0.435 0.005 0.455 0.885]);
+
+ pViewerOptions = uipanel(commonPanelProperties{:},...
+ 'Title','Viewer Options',...
+ 'Tag','uipanel_colormapOptions',...
+ 'Position',[0.895 0.445 0.1 0.445]);
+
+ pStructureVisibility = uipanel(commonPanelProperties{:},...
+ 'Title','Structure Visibilty',...
+ 'Tag','uipanel10',...
+ 'Position',[0.895 0.11 0.1 0.33]);
+
+ pInfo = uipanel(commonPanelProperties{:},...
+ 'Title','Info',...
+ 'Tag','uipanel12',...
+ 'Position',[0.895 0.005 0.1 0.1]);
+
+ % Initialize Widgets
+ obj.WorkflowWidget = matRad_WorkflowWidget(pWorkflow);
+ obj.PlanWidget = matRad_PlanWidget(pPlan);
+ obj.OptimizationWidget = matRad_OptimizationWidget(pOptimization);
+ obj.ViewingWidget = matRad_ViewingWidget(pViewing);
+ obj.ViewingWidget.scrollHandle = obj.guiHandle;
+ obj.VisualizationWidget = matRad_VisualizationWidget(pVisualization);
+ obj.VisualizationWidget.viewingWidgetHandle = obj.ViewingWidget;
+ obj.ViewerOptionsWidget = matRad_ViewerOptionsWidget(pViewerOptions);
+ obj.ViewerOptionsWidget.viewingWidgetHandle = obj.ViewingWidget;
+ obj.StructureVisibilityWidget = matRad_StructureVisibilityWidget(pStructureVisibility);
+ obj.InfoWidget = matRad_InfoWidget(pInfo); % does not need a listener
+ obj.LogoWidget = matRad_LogoWidget(pLogo); % does not need a listener
+
+ %Initialize event Listeners
+ switch matRad_cfg.env
+ case 'MATLAB'
+ %Event listeners triggered on events
+ obj.eventListeners.workflow = addlistener(obj.WorkflowWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.plan = addlistener(obj.PlanWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.optimization = addlistener(obj.OptimizationWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.viewing = addlistener(obj.ViewingWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.plot = addlistener(obj.ViewingWidget,'plotUpdated',@(src,hEvent) updateButtons(obj));
+ obj.eventListeners.visualization = addlistener(obj.VisualizationWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.viewerOptions = addlistener(obj.ViewerOptionsWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.structureVisibility = addlistener(obj.StructureVisibilityWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+
+ % only available in MATLAB
+ obj.ViewingWidget.dcmHandle = datacursormode(obj.guiHandle);
+ obj.ViewingWidget.panHandle = pan(obj.guiHandle);
+ obj.ViewingWidget.zoomHandle = zoom(obj.guiHandle);
+
+ case 'OCTAVE'
+ % addlistener is not yet available in octave
+ obj.eventListeners.workflow = matRad_addListenerOctave(obj.WorkflowWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.plan = matRad_addListenerOctave(obj.PlanWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.optimization = matRad_addListenerOctave(obj.OptimizationWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.viewing = matRad_addListenerOctave(obj.ViewingWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.plot = matRad_addListenerOctave(obj.ViewingWidget,'plotUpdated',@(src,hEvent) updateButtons(obj));
+ obj.eventListeners.visualization = matRad_addListenerOctave(obj.VisualizationWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.viewerOptions = matRad_addListenerOctave(obj.ViewerOptionsWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+ obj.eventListeners.structureVisibility = matRad_addListenerOctave(obj.StructureVisibilityWidget,'workspaceChanged',@(src,hEvent) updateWidgets(obj,hEvent));
+
+ end
+
+ obj.createMenuBar();
+
+ % update button states
+ obj.updateButtons();
+ end
+
+ function delete(this)
+ this.forceClose = true;
+ try
+ close(this.guiHandle);
+ catch
+ delete@handle(this);
+ end
+ end
+
+ function close(this)
+ this.delete();
+ end
+
+ function update(this,evt)
+ if nargin < 2
+ this.updateWidgets();
+ this.updateButtons();
+ else
+ this.updateWidgets(evt);
+ this.updateButtons();
+ end
+ end
+
+
+ function updateWidgets(this,evt)
+ matRad_cfg = MatRad_Config.instance();
+
+ args = {};
+ if nargin < 2 || ~isa(evt,'matRad_WorkspaceChangedEvent')
+ changed = {};
+ else
+ changed = evt.changedVariables;
+ args = {evt};
+ end
+
+ if matRad_cfg.logLevel > 3
+ matRad_cfg.dispDebug('GUI Workspace Changed at %s. ',datestr(now,'HH:MM:SS.FFF'));
+ if ~isempty(changed)
+ matRad_cfg.dispDebug('Specific Variables: %s.\n',strjoin(changed,'|'));
+ else
+ matRad_cfg.dispDebug('\n');
+ end
+ end
+
+
+ %%Debug
+ this.PlanWidget.update(args{:});
+ this.WorkflowWidget.update(args{:});
+ this.OptimizationWidget.update(args{:});
+ this.ViewingWidget.updateLock = false;
+ this.ViewingWidget.update(args{:});
+ this.StructureVisibilityWidget.update(args{:});
+
+ this.ViewingWidget.updateLock = true;
+ this.ViewerOptionsWidget.update(args{:});
+ this.VisualizationWidget.update(args{:});
+ this.ViewingWidget.updateLock = false;
+ end
+
+ function updateButtons(this)
+ %disp(['Plot Changed ' datestr(now,'HH:MM:SS.FFF')]); %Debug
+ % update the visualization and viewer options widgets
+
+ matRad_cfg = MatRad_Config.instance();
+
+ if strcmp(matRad_cfg.env,'OCTAVE')
+ return
+ end
+
+ set(findobj(this.guiHandle,'tag','toolbarPan'),'State',get(this.ViewingWidget.panHandle,'Enable'));
+ set(findobj(this.guiHandle,'tag','toolbarCursor'),'State',get(this.ViewingWidget.dcmHandle,'Enable'));
+ if ~isempty(strfind(lower(matRad_cfg.gui.name),'light'))
+ enable = 'on';
+ state = 'off';
+ elseif ~isempty(strfind(lower(matRad_cfg.gui.name),'dark'))
+ enable = 'on';
+ state = 'on';
+ else
+ enable = 'off';
+ state = 'off';
+ end
+ set(findobj(this.guiHandle,'tag','uitoggletool_theme'),'State',state,'Enable',enable);
+
+ if this.ViewingWidget.plotColorBar && ~isempty(this.ViewingWidget.cBarHandle) && isvalid(this.ViewingWidget.cBarHandle)
+ set(findobj(this.guiHandle,'tag','uitoggletool8'),'State','on')
+ else
+ set(findobj(this.guiHandle,'tag','uitoggletool8'),'State','off')
+ end
+ if this.ViewingWidget.plotLegend && ~isempty(this.ViewingWidget.legendHandle) && isvalid(this.ViewingWidget.legendHandle)
+ set(findobj(this.guiHandle,'tag','toolbarLegend'),'State',get(this.ViewingWidget.legendHandle,'visible'));
+ else
+ set(findobj(this.guiHandle,'tag','toolbarLegend'),'State','off');
+ end
+ if strcmp(get(this.ViewingWidget.zoomHandle,'Enable'),'on')
+ if strcmp(get(this.ViewingWidget.zoomHandle,'Direction'),'in')
+ set(findobj(this.guiHandle,'tag','toolbarZoomOut'),'State','off');
+ set(findobj(this.guiHandle,'tag','toolbarZoomIn'),'State','on');
+ else
+ set(findobj(this.guiHandle,'tag','toolbarZoomOut'),'State','on');
+ set(findobj(this.guiHandle,'tag','toolbarZoomIn'),'State','off');
+ end
+ else
+ set(findobj(this.guiHandle,'tag','toolbarZoomOut'),'State','off');
+ set(findobj(this.guiHandle,'tag','toolbarZoomIn'),'State','off');
+ end
+
+ this.ViewerOptionsWidget.update();
+ this.VisualizationWidget.update();
+
+ end
+
+ %% Callbacks
+ % toolbar load button
+ function toolbarLoad_ClickedCallback(this,hObject, eventdata)
+ this.WorkflowWidget.btnLoadMat_Callback(hObject, eventdata);
+ end
+ % toolbar save button
+ function toolbarSave_ClickedCallback(this,hObject, eventdata)
+ %handles=this.handles;
+
+
+ answer = questdlg('Do you wish to save the full workspace or only matRad variables?','Save','Full workspace', 'matRad variables', 'matRad variables');
+
+
+ switch answer
+ case 'Full workspace'
+ uisave;
+ case 'matRad variables'
+ variables = {'cst','ct','pln','stf','dij','resultGUI'};
+ vExists=false(size(variables));
+ for i= 1:numel(variables)
+ var= char(variables(i));
+ vExists(i) = evalin('base',['exist(''' var ''',''var'')']);
+ if vExists(i)
+ eval([var '=evalin(''base'',''' var ''');'])
+ end
+ end
+ uisave(variables(vExists));
+ end
+
+ end
+ %Toolbar screenshot of the Viewing widget
+ function uipushtool_screenshot_ClickedCallback(this,hObject, eventdata)
+ % hObject handle to uipushtool_screenshot (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ matRad_cfg = MatRad_Config.instance();
+ tmpFig = figure('position',[100 100 700 600],'Visible','off','name','Current View','Color',matRad_cfg.gui.backgroundColor);
+ cBarHandle = this.ViewingWidget.cBarHandle; %findobj(handles.figure1,'Type','colorbar');
+ if ~isempty(cBarHandle) && ishghandle(cBarHandle)
+ new_handle = copyobj([this.ViewingWidget.handles.axesFig cBarHandle],tmpFig);
+ else
+ new_handle = copyobj(this.ViewingWidget.handles.axesFig,tmpFig);
+ end
+
+ oldPos = get(this.ViewingWidget.handles.axesFig,'Position');
+ set(new_handle(1),'units','normalized', 'Position',oldPos);
+
+ if (ischar(this.lastStoragePath) || isstring(this.lastStoragePath)) && isfolder(this.lastStoragePath)
+ this.lastStoragePath = [];
+ end
+
+ [filename, pathname] = uiputfile({'*.jpg;*.tif;*.png;*.gif','All Image Files'; '*.fig','MATLAB figure file'},'Save current view',[this.lastStoragePath 'screenshot.png']);
+
+ if pathname ~= 0
+ this.lastStoragePath = pathname;
+ end
+
+ if ~isequal(filename,0) && ~isequal(pathname,0)
+ set(gcf, 'pointer', 'watch');
+ saveas(tmpFig,fullfile(pathname,filename));
+ set(gcf, 'pointer', 'arrow');
+ close(tmpFig);
+ uiwait(msgbox('Current view has been succesfully saved!'));
+ else
+ uiwait(msgbox('Aborted saving, showing figure instead!'));
+ set(tmpFig,'Visible','on');
+ end
+
+ end
+
+ % Toggle color bar callback
+ function uitoggletool8_ClickedCallback(this,hObject, eventdata)
+ % hObject handle to uitoggletool8 (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ this.ViewingWidget.plotColorBar = strcmp(get(hObject,'State'),'on');
+
+ end
+ %Toggle Legend Callback
+ function toolbarLegend_ClickedCallback(this,hObject, eventdata)
+
+ this.ViewingWidget.plotLegend = strcmp(get(hObject,'State'),'on');
+ end
+ %Toggle Zoom in Callback
+ function toolbarZoomIn_ClickedCallback(this,hObject, eventdata)
+ set(this.ViewingWidget.zoomHandle,'Enable',char(get(hObject,'State')));
+ set(this.ViewingWidget.zoomHandle,'Direction','in');
+ set(findobj('tag','toolbarZoomOut'),'State','off');
+ end
+ %Toggle Zoom out Callback
+ function toolbarZoomOut_ClickedCallback(this,hObject, eventdata)
+ set(this.ViewingWidget.zoomHandle,'Enable',char(get(hObject,'State')));
+ set(this.ViewingWidget.zoomHandle,'Direction','out');
+ set(findobj('tag','toolbarZoomIn'),'State','off');
+ end
+ %Toggle Pan Callback
+ function toolbarPan_ClickedCallback(this,hObject, eventdata)
+ set(this.ViewingWidget.panHandle,'Enable',char(get(hObject,'State')));
+ end
+ %Toggle cursor Callback
+ function toolbarCursor_ClickedCallback(this,hObject, eventdata)
+ set(this.ViewingWidget.dcmHandle,'Enable',get(hObject,'State'));
+ end
+
+ %Toggle cursor Callback
+ function gammaIndex_ClickedCallback(this,hObject, eventdata)
+ this.GammaWidget = matRad_GammaWidget();
+ end
+
+ function theme_ClickedCallback(this,hObject,eventdata)
+ if strcmp(get(hObject,'State'),'on')
+ theme = matRad_ThemeDark;
+ else
+ theme = matRad_ThemeLight;
+ end
+ delete(this);
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.gui = struct(theme);
+
+ this = matRad_MainGUI;
+ end
+
+
+ % button: close
+ function figure1_CloseRequestFcn(this,hObject,~)
+ matRad_cfg = MatRad_Config.instance();
+
+ if this.forceClose
+ selection = 'Yes';
+ else
+ %Get default colors
+ bgColor = get(0,'DefaultUicontrolBackgroundColor');
+ fgColor = get(0,'DefaultUIcontrolForegroundColor');
+ figColor = get(0,'DefaultFigureColor');
+ txtColor = get(0,'DefaultTextColor');
+
+ %Make sure we use the matRad color scheme
+ set(0,'DefaultUicontrolBackgroundColor',matRad_cfg.gui.elementColor);
+ set(0,'DefaultUIcontrolForegroundColor',matRad_cfg.gui.textColor);
+ set(0,'DefaultFigureColor',matRad_cfg.gui.backgroundColor);
+ set(0,'DefaultTextColor',matRad_cfg.gui.textColor);
+
+ selection = questdlg('Do you really want to close matRad?',...
+ 'Close matRad',...
+ 'Yes','No','Yes');
+
+ %restore original colors
+ set(0,'DefaultUicontrolBackgroundColor',bgColor);
+ set(0,'DefaultUIcontrolForegroundColor',fgColor);
+ set(0,'DefaultFigureColor',figColor);
+ set(0,'DefaultTextColor',txtColor);
+ end
+
+ %close if requested
+ switch selection
+ case 'Yes'
+ delete(hObject);
+ delete(this);
+ otherwise
+ %Do nothing
+ end
+
+ end
+
+ end
+
+
+end
+
diff --git a/matRad/gui/matRad_Theme.m b/matRad/gui/matRad_Theme.m
new file mode 100644
index 000000000..3586e0cb0
--- /dev/null
+++ b/matRad/gui/matRad_Theme.m
@@ -0,0 +1,55 @@
+classdef (Abstract) matRad_Theme
+ properties (Constant, Abstract)
+ name; %Name of the Theme
+ backgroundColor; %background color in RGB [0,1]
+ elementColor; %element color in RGB [0,1]
+ textColor; %element color in RGB [0,1]
+ highlightColor; %element color in RGB [0,1]
+ fontSize; %font size
+ fontWeight; %font Weight
+ fontName; %font name
+ author; %credit for theme
+ description; %theme description
+ end
+
+ methods
+ function obj = matRad_Theme()
+ %As MATLAB / Octave sets the abstract properties before the constructor is called, we can validate them here
+ obj.validate();
+ end
+
+ function validate(this)
+ %Do not use matRad_cfg here!
+
+ validateattributes(this.name,{'char'},{'nonempty','row'},mfilename('class'),'name');
+ validateattributes(this.backgroundColor,{'double'},{'size',[1 3],'<=',1,'>=',0},mfilename('class'),'backgroundColor');
+ validateattributes(this.elementColor,{'double'},{'size',[1 3],'<=',1,'>=',0},mfilename('class'),'elementColor');
+ validateattributes(this.textColor,{'double'},{'size',[1 3],'<=',1,'>=',0},mfilename('class'),'textColor');
+ validateattributes(this.highlightColor,{'double'},{'size',[1 3],'<=',1,'>=',0},mfilename('class'),'highlightColor');
+ validateattributes(this.fontSize,{'double'},{'scalar','positive'},mfilename('class'),'fontSize');
+ validateattributes(this.fontWeight,{'char'},{'nonempty','row'},mfilename('class'),'fontWeight');
+ validateattributes(this.fontName,{'char'},{'nonempty','row'},mfilename('class'),'fontName');
+ validateattributes(this.author,{'char'},{'nonempty','row'},mfilename('class'),'author');
+ validateattributes(this.description,{'char'},{'nonempty','row'},mfilename('class'),'description');
+
+ %Further checks on font weight
+ if ~any(strcmp(this.fontWeight,{'normal','bold'}))
+ error('matRad:matRad_Theme:invalidType','Invalid font weight. Must be one of ''normal'' or ''bold''');
+ end
+ end
+
+ function s = struct(this)
+ s.name = this.name;
+ s.backgroundColor = this.backgroundColor;
+ s.elementColor = this.elementColor;
+ s.textColor = this.textColor;
+ s.highlightColor = this.highlightColor;
+ s.fontSize = this.fontSize;
+ s.fontWeight = this.fontWeight;
+ s.fontName = this.fontName;
+ s.author = this.author;
+ s.description = this.description;
+ end
+ end
+end
+
diff --git a/matRad/gui/matRad_Widget.m b/matRad/gui/matRad_Widget.m
new file mode 100644
index 000000000..c08d26d40
--- /dev/null
+++ b/matRad/gui/matRad_Widget.m
@@ -0,0 +1,298 @@
+classdef matRad_Widget < handle
+
+ % matRad_Widget Main Class for GUI widget generation
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ properties (GetAccess = public , SetAccess = protected)
+ widgetHandle %Holds parent widget handle
+ handles = struct([]); %Holds all handles in parent handle
+ isInUifigure;
+ end
+
+ properties
+ updateLock = false; %Property to lock updating of the widget
+ end
+
+ events
+ %If the widget changes the workspace, this event should be emitted
+ %with notify(...). Other widgets can add listeners to update when
+ %this event is emitted
+ workspaceChanged
+ end
+
+ methods
+ %CONSTRUCTOR
+ function this = matRad_Widget(handleParent)
+ this.widgetHandle = handleParent;
+
+ %Create the layout
+ try
+ this.createLayout();
+ catch ME
+ showError(this,'Widget could not be created!',ME);
+ end
+
+ %Initialize the widget
+ try
+ this.initialize();
+ catch ME
+ showWarning(this,'Widget could not be initialized!',ME);
+ end
+
+ matRad_cfg = MatRad_Config.instance();
+
+ % only enable in matlab
+ %strcmp(env,'MATLAB') &&
+ if matRad_cfg.isMatlab && strcmp(get(handleParent,'type'),'figure')
+ set(this.widgetHandle,'ButtonDownFcn',@(src,hEvent) update(this));
+ set(this.widgetHandle,'KeyPressFcn',@(src,hEvent) update(this));
+ end
+ end
+
+ function set.handles(obj,handles)
+ obj.handles = handles;
+ end
+
+ %INITIALIZE FUNCTION
+ function initialize(this)
+ this.update();
+ end
+
+ function changedWorkspace(this,varargin)
+ matRad_cfg = MatRad_Config.instance();
+ % handle environment
+ switch matRad_cfg.env
+ case 'MATLAB'
+ %the PlanWidget only changes the pln
+ evt = matRad_WorkspaceChangedEventData(varargin{:});
+ notify(this, 'workspaceChanged',evt);
+ case 'OCTAVE'
+ evt = matRad_WorkspaceChangedEvent(varargin{:});
+ matRad_notifyOctave(this, 'workspaceChanged',evt);
+ end
+ end
+
+ function handles = showError(this,Message,ME)
+ matRad_cfg = MatRad_Config.instance();
+ handles = this.handles;
+ if nargin == 3
+ %Add exception message
+ if isfield(handles,'devMode') && handles.devMode
+ meType = 'extended';
+ else
+ meType = 'basic';
+ end
+ Message = [Message,ME.message];%{Message,ME.getReport(meType,'hyperlinks','off')};
+ end
+
+ if isfield(handles,'ErrorDlg')
+ if ishandle(handles.ErrorDlg)
+ close(handles.ErrorDlg);
+ end
+ end
+ if ~matRad_cfg.disableGUI
+ h = errordlg(Message);
+ matRad_applyThemeToDlg(h);
+ end
+ matRad_cfg.dispError(Message);
+ this.handles = handles;
+ end
+
+ function showWarning(this,Message,ME)
+ matRad_cfg = MatRad_Config.instance();
+
+ handles = this.handles;
+ if nargin == 3
+ %Add exception message
+ if isfield(handles,'devMode') && handles.devMode
+ meType = 'extended';
+ else
+ meType = 'basic';
+ end
+ Message = [Message,ME.message];
+ % Future error hyperlinks {Message,ME.getReport(meType,'hyperlinks','off')};
+ end
+ if ~matRad_cfg.disableGUI
+ h = warndlg(Message);
+ matRad_applyThemeToDlg(h);
+ end
+ matRad_cfg.dispWarning(Message);
+ this.handles = handles;
+ end
+
+ function showMessage(this,message,varargin)
+ matRad_cfg = MatRad_Config.instance();
+ if ~matRad_cfg.disableGUI
+ h = msgbox(message,varargin{:});
+ matRad_applyThemeToDlg(h);
+ end
+ end
+
+ %function notifyUpdate(this,workSpaceVariables)
+ % notify(this,'workspaceChanged',workSpaceVariables);
+ %end
+
+ function enableWindowCallback(this,enable)
+
+ if strcmp(get(this.widgetHandle,'type'),'figure') && enable
+ set(this.widgetHandle,'ButtonDownFcn',@(src,hEvent) update(this));
+ set(this.widgetHandle,'KeyPressFcn',@(src,hEvent) update(this));
+ else
+ set(this.widgetHandle,'ButtonDownFcn','');
+ set(this.widgetHandle,'KeyPressFcn','');
+ end
+ end
+
+ function isInUi = get.isInUifigure(this)
+ hFig = ancestor(this.widgetHandle,'Figure');
+ isInUi = matRad_isUifigure(hFig);
+ end
+
+ function delete(this)
+ if ishandle(this.widgetHandle)
+ this.destroyLayout();
+ end
+ end
+
+ function close(this)
+ if ishandle(this.widgetHandle)
+ this.destroyLayout();
+ end
+ end
+ end
+
+ methods (Sealed)
+
+ %Update function to be called when the widget should be updated
+ %(i.e. by listener to the workspaceChanged event or in
+ %initialization)
+ %Implement the protected doUpdate function in a subclass to define
+ %the update process
+ function update(this,evt)
+ try
+ if nargin == 2
+ this.doUpdate(evt);
+ else
+ this.doUpdate();
+ end
+ catch ME
+ %Error Handling
+ this.showWarning('Update failed',ME); %We only show a warning to not halt execution.
+ end
+ end
+ end
+
+ methods (Access = protected)
+
+ function this = doUpdate(this,~)
+
+ end
+
+ %CREATE LAYOUT FUNCTION
+ function this = createLayout(this, handleParent)
+ end
+
+ function destroyLayout(this)
+ fNames = fieldnames(this.handles);
+ for i = 1:numel(fNames)
+ h = this.handles.(fNames{i});
+ if isscalar(h) && isgraphics(h)
+ delete(h);
+ end
+ end
+ end
+
+ %Helper function to create handles structure from tags (similar to
+ %guihandles)
+ function this = createHandles(this)
+ %Iterate through all objects
+ all_h = findall(this.widgetHandle);
+
+ for i = 1:numel(all_h)
+ this_h = all_h(i);
+ if matRad_ispropCompat(this_h, 'Tag')
+ tag = get(this_h, 'Tag');
+ if ~isempty(tag) && isvarname(tag) % can it be used as a fieldname?
+
+ % if a field of this name already exists, get its contents
+ if isfield(this.handles, tag)
+ prev_h = this.handles.(tag);
+ else
+ prev_h = [];
+ end
+
+ % append our handle to whatever was there before. If nothing
+ % was there before.
+ if isappdata(this_h, 'Control')
+ % if this uicontrol is a proxy for external controls, replace it with
+ % that control
+ control = getappdata(this_h, 'Control');
+ this.handles(1).(tag) = [prev_h control.Instance];
+ else
+ this.handles(1).(tag) = [prev_h this_h];
+ end
+
+ end % if legal tag
+ end
+ end % loop
+ end
+
+ %Helper function to check if update is necessary when an event is
+ %passed
+ function [doUpdate,whichVars] = checkUpdateNecessary(this,updateVars,evt)
+ if isa(evt,'matRad_WorkspaceChangedEvent')
+ changed = evt.changedVariables;
+ else
+ changed = {};
+ end
+
+
+ %If changed is empty, we need an update, if not, we crosscheck
+ doUpdate = true;
+ whichVars = [];
+
+ if ~isempty(changed)
+ cmp = cellfun(@(str) any(strcmp(str,changed)),updateVars);
+ doUpdate = any(cmp);
+ if nargout == 2
+ whichVars = find(cmp); %indices of variables
+ end
+ end
+ end
+
+ %Helper function to position GUI elements on the Main Widget
+ function pos = computeGridPos(this,gridPos,buttonGridSize,buttonRelSize)
+ if nargin < 4
+ buttonRelSize = [0.9 0.75];
+ end
+
+ gridElementSize = 1./buttonGridSize;
+ buttonRelSize = buttonRelSize ./ buttonGridSize;
+
+ buttonGridPos = (gridPos-1) ./ buttonGridSize;
+ buttonGridPos(2) = 1 - gridElementSize(2) - buttonGridPos(2);
+ offset = (gridElementSize - buttonRelSize)./2;
+
+ pos = [buttonGridPos+offset buttonRelSize];
+ end
+ end
+
+end
+
diff --git a/matRad/gui/matRad_WorkspaceChangedEvent.m b/matRad/gui/matRad_WorkspaceChangedEvent.m
new file mode 100644
index 000000000..1fa7ba7c2
--- /dev/null
+++ b/matRad/gui/matRad_WorkspaceChangedEvent.m
@@ -0,0 +1,47 @@
+classdef matRad_WorkspaceChangedEvent < handle
+% matRad_WorkspaceChangedEvent class
+% Base class to store event data (no subclass yet for compatability)
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ changedVariables %Cell array with changed variable names
+ end
+
+ methods
+ function obj = matRad_WorkspaceChangedEvent(varargin)
+ %matRad_WorkspaceChangedEvent Construct the event
+ % varargin is the cell array of arguments (strings)
+ % containing the variable names
+
+ %obj = obj@event.EventData();
+
+ obj.changedVariables = varargin;
+
+
+ %Debug:
+ %{
+ if isempty(varargin)
+ changed = 'all';
+ else
+ changed = strjoin(varargin,'|');
+ end
+ fprintf('Changed variables: %s\n',changed);
+ %}
+
+ end
+
+ end
+end
+
diff --git a/matRad/gui/matRad_WorkspaceChangedEventData.m b/matRad/gui/matRad_WorkspaceChangedEventData.m
new file mode 100644
index 000000000..700c5a57d
--- /dev/null
+++ b/matRad/gui/matRad_WorkspaceChangedEventData.m
@@ -0,0 +1,42 @@
+classdef matRad_WorkspaceChangedEventData < matRad_WorkspaceChangedEvent & event.EventData
+% Class matRad_WorkspaceChangedEventData
+% EventData subclass to store changed variables when widget changes
+% the workspace
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ methods
+ function obj = matRad_WorkspaceChangedEventData(varargin)
+ %matRad_WorkspaceChangedEvent Construct the event
+ % varargin is the cell array of arguments (strings)
+ % containing the variable names
+
+ obj = obj@event.EventData();
+ obj = obj@matRad_WorkspaceChangedEvent(varargin{:});
+
+
+ %Debug:
+ %{
+ if isempty(varargin)
+ changed = 'all';
+ else
+ changed = strjoin(varargin,'|');
+ end
+ fprintf('Changed variables: %s\n',changed);
+ %}
+ end
+
+ end
+end
+
diff --git a/matRad/gui/matRad_addListenerOctave.m b/matRad/gui/matRad_addListenerOctave.m
new file mode 100644
index 000000000..4793d99d8
--- /dev/null
+++ b/matRad/gui/matRad_addListenerOctave.m
@@ -0,0 +1,33 @@
+function retval = matRad_addListenerOctave (hSource,eventName,callback)
+% matRad_addListenerOctave is a function that creates and accumulates new
+% Listeners in eventMap
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ global eventMap;
+
+ persistent warningprinted;
+ if isempty(warningprinted)
+ hSource.showWarning('You are using an experimental implementation of ''addlistener'' concept for Octave');
+ warningprinted=2;
+ end
+
+ newListener = struct('src',hSource,'event',eventName,'callback',callback);
+ if isempty(eventMap)
+ eventMap = newListener;
+ else
+ eventMap(end+1) = newListener;
+ end
+ retval=newListener;
+
+end
diff --git a/matRad/gui/matRad_applyThemeToDlg.m b/matRad/gui/matRad_applyThemeToDlg.m
new file mode 100644
index 000000000..ffdf0d674
--- /dev/null
+++ b/matRad/gui/matRad_applyThemeToDlg.m
@@ -0,0 +1,35 @@
+function matRad_applyThemeToDlg(hDlgBox)
+% matRad_applyThemeToDlg is a helper function to apply a theme tho dialog
+% boxes
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+try
+ if matRad_cfg.isOctave
+ txtObj = findall(hDlgBox,'type','text');
+ txtObj = txtObj(1);
+ okBtn = findall(hDlgBox,'type','uicontrol','style','pushbutton');
+ set(findall(hDlgBox,'type','uipanel'),'BackgroundColor',matRad_cfg.gui.backgroundColor);
+ else
+ txtObj = findall(hDlgBox,'tag','MessageBox');
+ okBtn = findall(hDlgBox,'tag','OKButton');
+ hDlgBox.Color = matRad_cfg.gui.backgroundColor;
+ end
+
+ set(txtObj,'Color',matRad_cfg.gui.textColor);
+ set(okBtn,'BackgroundColor',matRad_cfg.gui.elementColor,'ForegroundColor',matRad_cfg.gui.textColor);
+catch
+ matRad_cfg.dispWarning('Theme could not be applied to dialog!');
+end
\ No newline at end of file
diff --git a/matRad/gui/matRad_applyThemeToWaitbar.m b/matRad/gui/matRad_applyThemeToWaitbar.m
new file mode 100644
index 000000000..3c43266de
--- /dev/null
+++ b/matRad/gui/matRad_applyThemeToWaitbar.m
@@ -0,0 +1,50 @@
+function matRad_applyThemeToWaitbar(hWaitbar)
+% matRad_applyThemeToDlg is a helper function to apply a theme to a waitbar
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+try
+ set(hWaitbar,'Color',matRad_cfg.gui.backgroundColor);
+ txtObj = findall(hWaitbar,'type','text');
+ txtObj = txtObj(1);
+ if matRad_cfg.isOctave
+ patchObj = findobj(hWaitbar,'type','patch');
+ set(patchObj,'FaceColor',matRad_cfg.gui.highlightColor);
+ set(patchObj,'EdgeColor',matRad_cfg.gui.textColor);
+ axesObj = findobj(hWaitbar,'type','axes');
+ set(axesObj,'Color',matRad_cfg.gui.elementColor);
+ else
+ %no longer a patch object, we need java
+ %jcolor = [int32(rgb * 255) 0];
+ hJava = findall(hWaitbar,'type','hgjavacomponent');
+ hIndic = findall(hWaitbar,'type','uiprogressindicator');
+ if ~isempty(hJava)
+ jcolorbg = num2cell(matRad_cfg.gui.elementColor);
+ jcolorfg = num2cell(matRad_cfg.gui.highlightColor);
+
+ hJava.JavaPeer.setForeground(java.awt.Color(jcolorfg{:}))
+ hJava.JavaPeer.setBackground(java.awt.Color(jcolorbg{:}));
+ elseif ~isempty(hIndic)
+ hWaitbar.Children.Color = matRad_cfg.gui.elementColor;
+ hIndic.ProgressColor = matRad_cfg.gui.highlightColor;
+ else
+ %Do nothing
+ end
+ end
+
+ set(txtObj,'Color',matRad_cfg.gui.textColor,'Interpreter','none');
+catch
+ matRad_cfg.dispWarning('Theme could not be applied to dialog!');
+end
diff --git a/matRad/gui/matRad_getLogo.m b/matRad/gui/matRad_getLogo.m
new file mode 100644
index 000000000..fc7830982
--- /dev/null
+++ b/matRad/gui/matRad_getLogo.m
@@ -0,0 +1,65 @@
+function [im,alpha] = matRad_getLogo(scale)
+% matRad function to obtain matRad logo image data adhering to theme
+%
+% call
+% matRad_getLogoDKFZ()
+% matRad_getLogoDKFZ(scale)
+%
+% input:
+% scale: (optional, default 1): either scalar resizing factor or new
+% image size that should be used in pixels
+%
+% output:
+% im: RGB image
+% alpha: alpha channel
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+if nargin < 1
+ scale = 1;
+end
+
+matRad_cfg = MatRad_Config.instance();
+filepath = matRad_cfg.matRadSrcRoot;
+
+%Read Logo
+[im,~,alpha] = imread(fullfile(filepath,'gfx','matrad_logo.png'));
+
+%Recolor Logo
+if matRad_cfg.isOctave
+ rgbUint = uint8(matRad_cfg.gui.backgroundColor * 255);
+ alphaMap = alpha == 0;
+ for i = 1:3
+ tmp = squeeze(im(:,:,i));
+ tmp(alphaMap) = rgbUint(i);
+ im(:,:,i) = tmp;
+ end
+ alpha(:) = uint8(0);
+end
+
+%Resize
+if (isscalar(scale) && scale ~=1) || ~isequal(scale,size(alpha))
+ if matRad_cfg.isOctave
+ pkg load image;
+ end
+ im = imresize(im,scale);
+ alpha = imresize(alpha,scale);
+end
+
+end
+
diff --git a/matRad/gui/matRad_getLogoDKFZ.m b/matRad/gui/matRad_getLogoDKFZ.m
new file mode 100644
index 000000000..e3483cd03
--- /dev/null
+++ b/matRad/gui/matRad_getLogoDKFZ.m
@@ -0,0 +1,91 @@
+function [im,alpha] = matRad_getLogoDKFZ(scale)
+% matRad function to obtain DKFZ logo image data adhering to theme
+%
+% call
+% matRad_getLogoDKFZ()
+% matRad_getLogoDKFZ(scale)
+%
+% input:
+% scale: (optional, default 1): either scalar resizing factor or new
+% image size that should be used in pixels
+%
+% output:
+% im: RGB image
+% alpha: alpha channel
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+if nargin < 1
+ scale = 1;
+end
+
+matRad_cfg = MatRad_Config.instance();
+filepath = matRad_cfg.matRadSrcRoot;
+
+%Choose if in darkmode to use white logo
+bgHsv = rgb2hsv(matRad_cfg.gui.backgroundColor);
+dkfzBlueHsv = [0.569 1 0.718];
+
+colorWhiteLogo = bgHsv(3) < 0.25;
+greyNormThreshold = 0.5;
+
+%Choose Logo based on conditions
+if colorWhiteLogo
+ dkfzLogo = 'dkfz_logo_white.png';
+elseif norm(dkfzBlueHsv - bgHsv) < greyNormThreshold
+ dkfzLogo = 'dkfz_logo_grey.png';
+else
+ dkfzLogo = 'dkfz_logo_blue.png';
+end
+
+%Read Logo
+[im,~,alpha] = imread(fullfile(filepath,'gfx',dkfzLogo));
+
+%Recolor Logo
+% if colorWhiteLogo
+% rgbUint = uint8(matRad_cfg.gui.highlightColor * 255);
+% for i = 1:3
+% tmp = squeeze(im(:,:,i));
+% tmp(tmp == 255) = rgbUint(i);
+% im(:,:,i) = tmp;
+% end
+% end
+
+%In case of Octave with no transparancy we recolor the background
+if matRad_cfg.isOctave
+ rgbUint = uint8(matRad_cfg.gui.backgroundColor * 255);
+ alphaMap = alpha == 0;
+ for i = 1:3
+ tmp = squeeze(im(:,:,i));
+ tmp(alphaMap) = rgbUint(i);
+ im(:,:,i) = tmp;
+ end
+ alpha(:) = uint8(0);
+end
+
+%Resize
+if (isscalar(scale) && scale ~=1) || ~isequal(scale,size(alpha))
+ if matRad_cfg.isOctave
+ pkg load image;
+ end
+ im = imresize(im,scale);
+ alpha = imresize(alpha,scale);
+end
+
+end
+
diff --git a/matRad/gui/matRad_iconsGUI.mat b/matRad/gui/matRad_iconsGUI.mat
new file mode 100644
index 000000000..f8716ff48
Binary files /dev/null and b/matRad/gui/matRad_iconsGUI.mat differ
diff --git a/matRad/gui/matRad_isUifigure.m b/matRad/gui/matRad_isUifigure.m
new file mode 100644
index 000000000..41540bb77
--- /dev/null
+++ b/matRad/gui/matRad_isUifigure.m
@@ -0,0 +1,32 @@
+function tf = matRad_isUifigure(hFig)
+% Checks if the figure was created with uifigure
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+if matRad_cfg.isOctave
+ tf = false;
+else
+ if verLessThan('Matlab','9.0') %version < 2016a (release of uifigs)
+ tf = false;
+ elseif verLessThan('Matlab','9.5') % 16a <= version < 2018b
+ tf = ~isempty(matlab.ui.internal.dialog.DialogHelper.getFigureID(hFig));
+ else % version >= 2018b
+ tf = matlab.ui.internal.isUIFigure(hFig);
+ end
+end
+
+end
+
diff --git a/matRad/gui/matRad_notifyOctave.m b/matRad/gui/matRad_notifyOctave.m
new file mode 100644
index 000000000..eb58f492c
--- /dev/null
+++ b/matRad/gui/matRad_notifyOctave.m
@@ -0,0 +1,56 @@
+function matRad_notifyOctave(hObject,eventName,evt)
+% Experimental Function to notify on event for OCTAVE
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ global eventMap;
+ if isempty(eventMap)
+ return;
+ end
+
+ persistent warningprinted;
+ if isempty(warningprinted)
+ hObject.showWarning('You are using an experimental implementation of ''notify'' concept for Octave');
+ warningprinted=2;
+ end
+
+ objHandles = {eventMap(:).src};
+
+ %clean Handles
+ validH = cellfun(@isa,objHandles,repmat({'handle'},size(objHandles))); %cellfun(@ishandle,objHandles);
+ objHandles = objHandles(validH);
+ eventMap = eventMap(validH);
+
+ %Find the object
+ objIx = cellfun(@(h) isequal(hObject,h), objHandles);
+
+ objEvents = eventMap(objIx);
+
+ if isempty(objEvents)
+ return;
+ end
+
+ allNames = {objEvents(:).event};
+ eventIx = find(strcmp(eventName,allNames));
+
+ for runIx = 1:numel(eventIx)
+ runEventIx = eventIx(runIx);
+ runEvent = objEvents(runEventIx);
+ if nargin < 3
+ runEvent.callback(hObject);
+ else
+ runEvent.callback(hObject,evt);
+ end
+end
diff --git a/matRad/gui/themes/matRad_ThemeDark.m b/matRad/gui/themes/matRad_ThemeDark.m
new file mode 100644
index 000000000..7f4c164b9
--- /dev/null
+++ b/matRad/gui/themes/matRad_ThemeDark.m
@@ -0,0 +1,15 @@
+classdef matRad_ThemeDark < matRad_Theme
+ properties (Constant)
+ name = 'Dark';
+ backgroundColor = [0.129176470588235 0.137568627450980 0.143529411764706];
+ elementColor = [0.068176470588235 0.070274509803922 0.071764705882353];
+ textColor = [0.308588235294118 0.579235294117647 0.771470588235294];
+ highlightColor = [0.793725490196079 0.693647058823530 0.357176470588235];
+ fontSize = 8;
+ fontWeight = 'bold';
+ fontName = 'Helvetica';
+ author = 'Niklas Wahl (github.com/wahln)';
+ description = 'Default Dark theme for matRad';
+ end
+end
+
diff --git a/matRad/gui/themes/matRad_ThemeLight.m b/matRad/gui/themes/matRad_ThemeLight.m
new file mode 100644
index 000000000..d48c2dc27
--- /dev/null
+++ b/matRad/gui/themes/matRad_ThemeLight.m
@@ -0,0 +1,14 @@
+classdef matRad_ThemeLight < matRad_Theme
+ properties (Constant)
+ name = 'Light';
+ backgroundColor = [212, 211, 205]/255; %very light beige/yellow
+ elementColor = [229, 229, 229]/255; %neutral light grey
+ textColor = [ 54, 52, 46]/255; %dark yellowish-grey
+ highlightColor = [ 67, 97, 167]/255; %blue
+ fontSize = 8;
+ fontWeight = 'bold';
+ fontName = 'Helvetica';
+ author = 'Pia Stammer (github.com/PiaStammer)';
+ description = 'Default Light Theme for matRad';
+ end
+end
\ No newline at end of file
diff --git a/matRad/gui/widgets/matRad_3DWidget.m b/matRad/gui/widgets/matRad_3DWidget.m
new file mode 100644
index 000000000..d520d4a07
--- /dev/null
+++ b/matRad/gui/widgets/matRad_3DWidget.m
@@ -0,0 +1,216 @@
+classdef matRad_3DWidget < matRad_ViewingWidget
+
+ % matRad_3DWidget class to generate GUI widget for 3D plan visualization
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ viewingWidgetHandle;
+ end
+
+ events
+
+ end
+
+ methods
+ %Constructor
+ function this = matRad_3DWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 2
+ handleParent = figure(...
+ 'Units','normalized',...
+ 'Position',[0.3 0.2 0.4 0.6],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,... 'CloseRequestFcn',@(hObject,eventdata) figure1_CloseRequestFcn(this,hObject,eventdata),...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad 3D',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1');
+
+ end
+
+ this = this@matRad_ViewingWidget(handleParent);
+
+ this.update();
+
+ end
+
+ function this=initialize(this)
+
+ end
+ end
+
+ methods(Access = protected)
+ function this = createLayout(this)
+ h88 = this.widgetHandle;
+ createLayout@matRad_ViewingWidget(this);
+
+ set(this.handles.axesFig,'XTickMode','auto','XTickLabelMode','auto');
+ set(this.handles.axesFig,'YTickMode','auto','YTickLabelMode','auto');
+ set(this.handles.axesFig,'ZTickMode','auto','ZTickLabelMode','auto');
+
+ %this.createHandles();
+ end
+
+ function this=doUpdate(this,~)
+ if ~this.updateLock
+ if ~isempty(this.viewingWidgetHandle)
+ matRad_cfg = MatRad_Config.instance();
+
+ p = matRad_getPropsCompat(this.viewingWidgetHandle);
+
+ % copy all the properties of the viewingwidget except for the widgethandle
+ this.updateLock = true;
+ for k = 1:length(p)
+ if ~strcmp(p{k},'widgetHandle') && ~strcmp(p{k},'handles') && ~strcmp(p{k},'updateLock')
+ try
+ this.(p{k}) = this.viewingWidgetHandle.(p{k});
+ catch
+ %warning('failed to copy property: %s', p{k});
+ end
+ end
+ end
+ this.updateLock = false;
+ end
+ this.plot3D();
+ end
+ end
+ end
+
+ methods
+ function set.viewingWidgetHandle(this,hViewingWidget)
+ this.viewingWidgetHandle = hViewingWidget;
+ this.doUpdate();
+ end
+
+ function plot3D(this)
+
+ matRad_cfg = MatRad_Config.instance();
+ if evalin('base','exist(''pln'')') && ...
+ evalin('base','exist(''ct'')') && evalin('base','exist(''cst'')')
+
+ ct = evalin('base','ct');
+ cst = evalin('base','cst');
+ pln = evalin('base','pln');
+
+
+ if evalin('base','exist(''resultGUI'')')
+ Result = evalin('base','resultGUI');
+ end
+
+ if evalin('base','exist(''stf'')')
+ stf = evalin('base','stf');
+ else
+ stf = [];
+ end
+ else
+ return
+ end
+
+ axesFig3D=this.handles.axesFig;
+ view(axesFig3D,3);
+ oldView = get(axesFig3D,'View');
+
+ cla(axesFig3D);
+
+ defaultFontSize = matRad_cfg.gui.fontSize;
+
+ %Check if we need to precompute the surface data
+ if size(cst,2) < 8
+ cst = matRad_computeAllVoiSurfaces(ct,cst);
+ assignin('base','cst',cst);
+ end
+
+ set(this.widgetHandle,'Color',matRad_cfg.gui.backgroundColor);
+ set(axesFig3D,'Color',matRad_cfg.gui.elementColor);
+
+ %% Plot 3D structures
+ hold(axesFig3D,'on');
+ if this.plotContour && exist('cst','var') && exist('ct','var') %get(handles.radiobtnContour,'Value') && handles.State>0
+ voiPatches = matRad_plotVois3D(axesFig3D,ct,cst,this.VOIPlotFlag);
+ end
+
+ %% plot the CT slice
+ if this.plotCT %get(handles.radiobtnCT,'Value')
+ window = this.dispWindow{2,1}; %(2 for ct)
+ ctMap = matRad_getColormap(this.ctColorMap,this.cMapSize);
+ ctHandle = matRad_plotCtSlice3D(axesFig3D,ct,1,this.plane,this.slice,ctMap,window);
+ end
+
+ %% plot the dose slice
+ if exist('Result','var')
+ doseMap = matRad_getColormap(this.doseColorMap,this.cMapSize);
+ doseIx = 3;
+ % if the selected display option doesn't exist then simply display
+ % the first cube of the Result struct
+ if ~isfield(Result,this.SelectedDisplayOption)
+ CubeNames = fieldnames(Result);
+ this.updateLock=false;
+ this.SelectedDisplayOption = CubeNames{1,1};
+ this.updateLock=true;
+ end
+
+ dose = Result.(this.SelectedDisplayOption);
+
+ % dose colorwash
+ if ~isempty(dose) && ~isvector(dose)
+
+ % if isempty(this.dispWindow{doseIx,2})
+ % this.dispWindow{doseIx,2} = [min(dose(:)) max(dose(:))]; % set min and max dose values
+ % end
+
+ if this.plotDose %get(handles.radiobtnDose,'Value')
+ [doseHandle,~,~] = matRad_plotDoseSlice3D(axesFig3D,ct,dose,this.plane,this.slice,this.CutOffLevel,this.doseOpacity,doseMap,this.dispWindow{doseIx,1});
+ end
+ if this.plotIsoDoseLines %get(handles.radiobtnIsoDoseLines,'Value')
+ matRad_plotIsoDoseLines3D(axesFig3D,ct,dose,this.IsoDose_Contours,this.IsoDose_Levels,this.plane,this.slice,doseMap,this.dispWindow{doseIx,1},'LineWidth',1.5);
+ end
+ end
+ end
+
+ if this.plotPlan %get(handles.radiobtnPlan,'Value')
+ matRad_plotPlan3D(axesFig3D,pln,stf);
+ end
+
+ xlim(ct.resolution.x/2 + ct.resolution.x*[1 ct.cubeDim(2)]);
+ ylim(ct.resolution.y/2 + ct.resolution.y*[1 ct.cubeDim(1)]);
+ zlim(ct.resolution.z/2 + ct.resolution.z*[1 ct.cubeDim(3)]);
+
+ xlabel(axesFig3D,'x [mm]','FontSize',defaultFontSize,'Color',matRad_cfg.gui.textColor)
+ ylabel(axesFig3D,'y [mm]','FontSize',defaultFontSize,'Color',matRad_cfg.gui.textColor)
+ zlabel(axesFig3D,'z [mm]','FontSize',defaultFontSize,'Color',matRad_cfg.gui.textColor)
+ title(axesFig3D,'matRad 3D view','FontSize',defaultFontSize,'Color',matRad_cfg.gui.highlightColor);
+
+ % set axis ratio
+ ratios = [1 1 1]; %[1/ct.resolution.x 1/ct.resolution.y 1/ct.resolution.z];
+ ratios = ratios([2 1 3]);
+ set(axesFig3D,'DataAspectRatioMode','manual');
+ set(axesFig3D,'DataAspectRatio',ratios./max(ratios));
+
+ set(axesFig3D,'Ydir','reverse');
+
+ set(axesFig3D,'view',oldView);
+
+ %this.handles = handles;
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/matRad/gui/widgets/matRad_DVHStatsWidget.m b/matRad/gui/widgets/matRad_DVHStatsWidget.m
new file mode 100644
index 000000000..0f6646dab
--- /dev/null
+++ b/matRad/gui/widgets/matRad_DVHStatsWidget.m
@@ -0,0 +1,133 @@
+classdef matRad_DVHStatsWidget < matRad_Widget
+
+ % matRad_DVHStatsWidget class to generate GUI widget display DVH and
+ % stats
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+ selectedDisplayOption;
+ dvhWidgetHandle = [];
+ statWidgetHandle = [];
+ end
+
+ methods
+ function this = matRad_DVHStatsWidget(handleParent)
+
+
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+
+ handleParent = figure(...
+ 'Units','normalized',...
+ 'OuterPosition',[0 0.05 0.5 0.95],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,... 'CloseRequestFcn',@(hObject,eventdata) figure1_CloseRequestFcn(this,hObject,eventdata),...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','figure',...
+ 'ToolBar','figure',...
+ 'Name','MatRad Plan Analysis',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figDVHStat');
+
+ end
+ this = this@matRad_Widget(handleParent);
+ this.update();
+
+ end
+
+ function set.selectedDisplayOption(this,value)
+ this.selectedDisplayOption = value;
+ this.update();
+
+ end
+ function removeOverlap(this)
+ % Clear previous plotted objects
+ delete(this.dvhWidgetHandle.widgetHandle.Children(3));
+
+ end
+ end
+
+ methods(Access = protected)
+ function this = createLayout(this)
+ h88 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+ %DVH Panel
+ p1 = uipanel(...
+ 'Parent',h88,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.highlightColor,...
+ 'Tag','panelDVH',...
+ 'Clipping','off',...
+ 'Position',[0.005 0.505 0.99 0.495],...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Title','DVH');
+ % Statistics panel
+ p2 = uipanel(...
+ 'Parent',h88,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.highlightColor,...
+ 'Tag','panelStats',...
+ 'Clipping','off',...
+ 'Position',[0.005 0.005 0.99 0.495],...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Title','Statistics');
+
+ %Initiate DVH and Stats Widgets
+ this.dvhWidgetHandle = matRad_DVHWidget(p1);
+ this.statWidgetHandle = matRad_StatisticsWidget(p2);
+ this.createHandles();
+
+ end
+
+ function this=doUpdate(this,evt)
+ if ~this.updateLock && ~isempty(this.selectedDisplayOption)
+ if nargin == 2
+ doUpdate = this.checkUpdateNecessary({'resultGUI','cst','pln'},evt);
+
+ if doUpdate
+ this.dvhWidgetHandle.update(evt);
+ this.statWidgetHandle.update(evt);
+ end
+ else
+ %Check for change in the selected cube
+ if ~strcmp(this.dvhWidgetHandle.selectedCube, this.selectedDisplayOption)
+ this.dvhWidgetHandle.selectedCube = this.selectedDisplayOption;
+ this.statWidgetHandle.selectedCube = this.selectedDisplayOption;
+
+ else
+ this.dvhWidgetHandle = this.dvhWidgetHandle.doUpdate();
+ this.statWidgetHandle = this.statWidgetHandle.doUpdate();
+ %Clear previous DVH and stat
+ if numel(this.dvhWidgetHandle.widgetHandle.Children) > 2
+ this.removeOverlap();
+ end
+ end
+ end
+ end
+ end
+
+ end
+end
+
diff --git a/matRad/gui/widgets/matRad_DVHWidget.m b/matRad/gui/widgets/matRad_DVHWidget.m
new file mode 100644
index 000000000..d709e740c
--- /dev/null
+++ b/matRad/gui/widgets/matRad_DVHWidget.m
@@ -0,0 +1,132 @@
+classdef matRad_DVHWidget < matRad_Widget
+ % matRad_DVHWidget class to generate GUI widget to display DVH
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+ selectedCube = [];
+ end
+
+ properties (SetAccess = private)
+ dvhAx;
+ end
+
+ methods
+ function this = matRad_DVHWidget(handleParent)
+
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+ handleParent = figure(...
+ 'Units','normalized',...
+ 'Position',[0.005 0.5 0.495 0.45],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,... 'CloseRequestFcn',@(hObject,eventdata) figure1_CloseRequestFcn(this,hObject,eventdata),...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad DVH',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figDVH',...
+ 'PaperSize',[20.99999864 29.69999902]);
+
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+
+ function removeOverlap(this)
+ %Clear previous plotted objects from the figure
+ delete(this.widgetHandle.Children(3));
+ end
+
+ function initialize(this)
+ initialize@matRad_Widget(this);
+ end
+ end
+
+ methods(Access = protected)
+ function this = createLayout(this,handleParent)
+ this.createHandles();
+ end
+
+ function this = doUpdate(this,evt)
+ if ~this.updateLock && ~isempty(this.selectedCube)
+ doUpdate = true;
+ if nargin == 2
+ doUpdate = this.checkUpdateNecessary({'resultGUI','cst','pln'},evt);
+ end
+
+ if doUpdate && evalin('base','exist(''resultGUI'')') && evalin('base','exist(''cst'')')
+ this.showDVH();
+ ch = get(this.widgetHandle,'Children'); %Octave compatibility, no dot indexing
+ if numel(ch) > 2
+ this.removeOverlap();
+ end
+ end
+ end
+ end
+ end
+
+ methods
+
+ function set.selectedCube(this,value)
+ this.selectedCube=value;
+ this.update();
+ end
+
+ function showDVH(this)
+ if isempty(this.selectedCube)
+ return;
+ end
+ if isgraphics(this.dvhAx)
+ cla(this.dvhAx);
+ else
+ matRad_cfg = MatRad_Config.instance();
+ this.dvhAx = axes(this.widgetHandle,...
+ 'Color',matRad_cfg.gui.elementColor,...
+ 'XColor',matRad_cfg.gui.textColor,...
+ 'YColor',matRad_cfg.gui.textColor,...
+ 'GridColor',matRad_cfg.gui.textColor,...
+ 'MinorGridColor',matRad_cfg.gui.backgroundColor);
+ end
+ resultGUI = evalin('base','resultGUI');
+ pln = evalin('base','pln');
+ cst = evalin('base','cst');
+ % Calculate and show DVH
+ doseCube = resultGUI.(this.selectedCube);
+ dvh = matRad_calcDVH(cst,doseCube,'cum');
+
+ matRad_showDVH(dvh,cst,pln,'axesHandle',this.dvhAx);
+
+ %check scenarios
+ if pln.multScen.totNumScen > 1
+ for i = 1:pln.multScen.totNumScen
+ scenFieldName = sprintf('%s_scen%d',this.selectedCube,i);
+ if isfield(resultGUI,scenFieldName)
+ tmpDvh = matRad_calcDVH(cst,resultGUI.(scenFieldName),'cum'); % Calculate cumulative scenario DVH
+ matRad_showDVH(tmpDvh,cst,pln,'axesHandle',this.dvhAx,'plotLegend',false,'LineWidth',0.5,'LineStyle','--'); % Show DVH plot
+ end
+ end
+ end
+
+ %No dot indexing for Octave compatibility
+ hTitle = get(this.dvhAx,'Title');
+ set(hTitle,'String', strrep(this.selectedCube, '_',' '), 'Color',matRad_cfg.gui.highlightColor);
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/matRad/gui/widgets/matRad_GammaWidget.m b/matRad/gui/widgets/matRad_GammaWidget.m
new file mode 100644
index 000000000..f352636f7
--- /dev/null
+++ b/matRad/gui/widgets/matRad_GammaWidget.m
@@ -0,0 +1,542 @@
+classdef matRad_GammaWidget < matRad_Widget
+ % matRad_GammaWidget : GUI widget for gamma index based comparisons of
+ % dose cubes stored within resultGUI struct.
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ properties
+ SelectedDisplayOption1 = 'physicalDose' ;
+ SelectedDisplayOption2 = 'physicalDose' ;
+ SelectedDisplayAllOptions = {};
+ criteria = [3 3];
+ n = 0;
+ localglobal = 'global';
+ resolution;
+ lockUpdate = false;
+ maxSlice;
+ slice;
+ gammaCube;
+ gammaPassRateCell;
+ updated = false;
+
+ end
+ properties (Constant)
+ normalization = {'local','global'} ;
+ end
+
+ events
+
+ end
+
+ methods
+ function this = matRad_GammaWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 2
+ handleParent = figure(...
+ 'Units','normalized',...
+ 'Position',[0.1 0.1 0.7 0.7],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,... 'CloseRequestFcn',@(hObject,eventdata) figure1_CloseRequestFcn(this,hObject,eventdata),...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad Gamma Analysis',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','GammaWidget');
+
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+
+ function this = initialize(this)
+ if evalin( 'base', 'exist(''resultGUI'')' )
+ resultGUI = evalin('base','resultGUI');
+ resultnames = fieldnames(resultGUI) ;
+ j = 1;
+ for i = 1:numel(resultnames)
+ if ndims(resultGUI.(resultnames{i}))==3
+ this.SelectedDisplayAllOptions{j} = resultnames{i};
+ j=j+1;
+ end
+ end
+ % get and set display options
+ %this.SelectedDisplayAllOptions = pad(this.SelectedDisplayAllOptions);
+ set(this.handles.popupSelectedDisplayOption1,'String',this.SelectedDisplayAllOptions);
+ set(this.handles.popupSelectedDisplayOption2,'String',this.SelectedDisplayAllOptions);
+ this.maxSlice = size(resultGUI.physicalDose,3);
+ this.slice = round(this.maxSlice/2);
+ end
+ if evalin( 'base', 'exist(''ct'')' )
+ ct = evalin('base','ct');
+ this.resolution = [ct.resolution.x, ct.resolution.y, ct.resolution.z];
+ set(this.handles.editResolution,'String', regexprep(num2str(this.resolution),'\s+',' '));
+ end
+
+ set(this.handles.editGammaCrit,'String', regexprep(num2str(this.criteria),'\s+',' '));
+
+ this.update();
+ end
+
+ % METHOD FOR WHEN WORKSPACE IS CHANGED
+
+ function set.SelectedDisplayOption1(this, value)
+ this.SelectedDisplayOption1 = value;
+ this.lockUpdate = true;
+ this.update();
+ end
+
+ function set.SelectedDisplayOption2(this, value)
+ this.SelectedDisplayOption2 = value;
+ this.lockUpdate = true;
+ this.update();
+ end
+
+ function set.resolution(this,value)
+ this.resolution = value;
+ this.lockUpdate = true;
+ this.update();
+ end
+
+ function set.criteria(this,value)
+ this.criteria = value;
+ this.lockUpdate = true;
+ this.update();
+ end
+
+ function set.localglobal(this,value)
+ this.localglobal = value;
+ this.lockUpdate = true;
+ this.update();
+ end
+ function set.n(this,value)
+ this.n = value;
+ this.lockUpdate = true;
+ this.update();
+ end
+
+
+ end
+
+ methods (Access = protected)
+
+ function this = createLayout(this,handleParent)
+ h20 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Create Main Grid layout
+ gridSize = [6 20];
+ elSize = [0.9 0.6];
+ [i,j] = ndgrid(1:gridSize(1),1:gridSize(2));
+ gridPos = arrayfun(@(i,j) computeGridPos(this,[i j],gridSize,elSize),i,j,'UniformOutput',false);
+
+ %Text for cube 1
+ txt = sprintf('Choose Reference Cube from ResultGUI');
+ h21 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Reference cube 1:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{3,1},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtCube1',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+ %Popup menu for cube 1
+ h22 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Please select ...',...
+ 'TooltipString',txt,...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{4,1},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)popupSelectedDisplayOption1_Callback(this,hObject,eventdata),...
+ 'Tag','popupSelectedDisplayOption1',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text for Cube 2
+ txt = sprintf('Choose Reference Cube from ResultGUI');
+ h23 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Reference cube 2:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{5,1},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtCube2',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Popup menu for cube 2
+ h24 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Please select ...',...
+ 'TooltipString',txt,...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{6,1},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)popupSelectedDisplayOption2_Callback(this,hObject,eventdata),...
+ 'Tag','popupSelectedDisplayOption2',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text for Gamma Criteria
+ txt = sprintf('Gamma Criteria [mm %%]');
+ h25 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Gamma Criteria [mm %]:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,3},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtGammaCrit',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Edit gamma Criteria
+ h26 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','0 0',...
+ 'TooltipString',txt,...
+ 'Style','edit',...
+ 'Position',gridPos{1,4},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)editGammaCrit_Callback(this,hObject,eventdata),...
+ 'Tag','editGammaCrit',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text for resolution
+ txt = sprintf('Resolution of cube [mm/voxel]');
+ h27 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Resolution of cube [mm/voxel]:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,5},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtResolution',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+ % Edit Resolution
+ h28 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','0 0 0',...
+ 'TooltipString',txt,...
+ 'Style','edit',...
+ 'Position',gridPos{1,6},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)editResolution_Callback(this,hObject,eventdata),...
+ 'Tag','editResolution',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text for Interpolation
+ txt = sprintf('Number of interpolations, max suggested value is 3');
+ h29 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Number of interpolations n:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,7},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtInterpolations',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Edit Interpolations
+ h30 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','0',...
+ 'TooltipString',txt,...
+ 'Style','edit',...
+ 'Position',gridPos{1,8},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)editInterpolations_Callback(this,hObject,eventdata),...
+ 'Tag','editInterpolations',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text Normalizations
+ txt = sprintf('local and global normalizations');
+ h31 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Type of normalization:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,9},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtInterpolations',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Normalization
+ h32 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String',this.normalization,...
+ 'TooltipString',txt,...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{1,10},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)popupNormalization_Callback(this,hObject,eventdata),...
+ 'Tag','popupNormalization',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text Slices
+ txt = sprintf('Choose which slice should be displayed in intensity plots');
+ h33 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Slice',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,11},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtInterpolations',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Slice Slider
+ h34 = uicontrol(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'String','Slider',...
+ 'TooltipString','Choose which slice should be displayed in intensity plots',...
+ 'Style','slider',...
+ 'Callback',@(hObject,eventdata) sliderSlice_Callback(this,hObject,eventdata),...
+ 'BusyAction','cancel',...
+ 'Interruptible','off',...
+ 'Position',gridPos{1,12},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','sliderSlice');
+
+ p1 = uipanel(...
+ 'Parent',h20,...
+ 'Units','normalized',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'Tag','panelGammaIdx',...
+ 'Clipping','off',...
+ 'Position',[0.22 0.01 0.7 0.9],...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Title','Gamma Analysis');
+ % TODO Future: scroll control
+
+ this.createHandles();
+ end
+
+ function this = doUpdate(this,~)
+
+ if evalin( 'base', 'exist(''resultGUI'')' ) && this.lockUpdate
+ resultGUI = evalin('base','resultGUI');
+ resultnames = fieldnames(resultGUI) ;
+ j = 1;
+ this.SelectedDisplayAllOptions = {};
+ for i = 1:numel(resultnames)
+ if ndims(resultGUI.(resultnames{i}))==3
+ this.SelectedDisplayAllOptions{j} = resultnames{i};
+ j=j+1;
+ end
+ end
+ % get and set display options
+ set(this.handles.popupSelectedDisplayOption1,'String',this.SelectedDisplayAllOptions);
+ set(this.handles.popupSelectedDisplayOption2,'String',this.SelectedDisplayAllOptions);
+
+ %slider options %CAN ALSO SET MIN AND MAX TO NONZERO SLICES
+
+ if size(resultGUI.(this.SelectedDisplayOption1),3) == size(resultGUI.(this.SelectedDisplayOption2),3)
+ this.maxSlice = size(resultGUI.(this.SelectedDisplayOption1),3);
+ this.slice = round(this.maxSlice/2);
+ set(this.handles.sliderSlice,'Min',1,'Max',this.maxSlice,...
+ 'Value', this.slice, ...
+ 'SliderStep',[1 1]);
+ else
+ this.showWarning('Mismatch in dimensions of selected cubes')
+ end
+
+
+ this.calcGamma();
+ this.plotGamma();
+ this.lockUpdate = false;
+ end
+
+ end
+ end
+ methods (Access = private)
+ %CALLBACKS
+ function popupNormalization_Callback(this, hObject, eventdata)
+ contents = cellstr(get(hObject,'String'));
+ this.localglobal = contents{get(hObject,'Value')};
+
+ end
+ function popupSelectedDisplayOption1_Callback(this,hObject,eventdata)
+ contents = cellstr(get(hObject,'String'));
+ this.SelectedDisplayOption1 = strtrim(contents{get(hObject,'Value')});
+
+ end
+ function popupSelectedDisplayOption2_Callback(this,hObject,eventdata)
+ contents = cellstr(get(hObject,'String'));
+ this.SelectedDisplayOption2 = strtrim(contents{get(hObject,'Value')});
+
+ end
+
+ function editResolution_Callback(this, hObject, ~)
+ t = sscanf (get(hObject,'String'), '%f');
+ if numel(t) ~=3
+ this.showWarning('Resolution value error')
+ else
+ this.resolution = t;
+ end
+ end
+
+ function editGammaCrit_Callback(this, hObject, ~)
+ t = sscanf (get(hObject,'String'), '%f');
+ if numel(t) ~=2
+ this.showWarning('Gamma Criterion value error')
+ else
+ this.criteria = t;
+ end
+ end
+
+ function editInterpolations_Callback(this, hObject, ~)
+ t = str2double(get(hObject,'String'));
+ if ~isnumeric(t)
+ this.showWarning('Number of Interpolations value error')
+ else
+ this.n = t;
+ end
+ end
+
+ function sliderSlice_Callback(this,hObject, ~)
+ % hObject handle to sliderSlice (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'Value') returns position of slider
+ % get(hObject,'Min') and get(hObject,'Max') to determine range of slider
+ %UpdatePlot(handles)
+
+ this.slice = round(get(hObject,'Value'));
+ this.plotGamma();
+ end
+
+
+ end
+
+
+ methods
+
+ %Calculate Gamma
+ function calcGamma(this)
+ if evalin( 'base', 'exist(''resultGUI'')' )
+ resultGUI = evalin('base','resultGUI');
+ else
+ % no result cube
+ end
+ if evalin( 'base', 'exist(''cst'')' )
+ cst = evalin('base','cst');
+ else
+ %no cst
+ end
+
+ [this.gammaCube,this.gammaPassRateCell] = matRad_gammaIndex(resultGUI.(this.SelectedDisplayOption1) ,resultGUI.(this.SelectedDisplayOption2),...
+ this.resolution,this.criteria,[],this.n,this.localglobal,cst);
+ end
+
+ %Show Gamma
+ function plotGamma(this)
+ this.widgetHandle;
+ % visualize if applicable
+
+ if ~isempty(this.slice) && ~isempty(this.gammaCube)
+ if isempty(this.handles.panelGammaIdx(1).Children)
+ ax = axes('Parent',this.handles.panelGammaIdx);
+ else
+ % overwrite previous plot in the panel
+ ax = this.handles.panelGammaIdx(1).Children(2);
+
+ end
+ % ax = figure('Parent',this.handles.panelGammaIdx);
+ % set(ax,'Color',[1 1 1]);
+ imagesc(ax, this.gammaCube(:,:,this.slice))
+ myColormap = matRad_getColormap('gammaIndex');
+
+ set(ax,'colormap',myColormap);
+
+ colorbar(ax);
+ titletxt = {[num2str(this.gammaPassRateCell{1,2},5) '% of points > ' num2str(this.criteria(1)) ...
+ '% pass gamma criterion (' num2str(this.criteria(1)) '% / ' ...
+ num2str(this.criteria(2)) 'mm)']; ['with ' num2str(2^this.n-1) ' interpolation points']};
+ title(ax, titletxt);
+ % set(this.handles.panelGammaIdx(1).Children(2),'Title',titletxt);
+ % this.createHandles();
+ end
+ end
+
+ end
+
+end
diff --git a/matRad/gui/widgets/matRad_InfoWidget.m b/matRad/gui/widgets/matRad_InfoWidget.m
new file mode 100644
index 000000000..18a9bc30b
--- /dev/null
+++ b/matRad/gui/widgets/matRad_InfoWidget.m
@@ -0,0 +1,164 @@
+classdef matRad_InfoWidget < matRad_Widget
+ % matRad_InfoWidget class to generate GUI widget to display system and
+ % version information
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+
+ end
+
+ methods
+ function this = matRad_InfoWidget(handleParent)
+ if nargin < 1
+ matRad_cfg = MatRad_Config.instance();
+ handleParent = figure(...
+ 'Units','normalized',...
+ 'Position',[0.45 0.45 0.1 0.1],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'IntegerHandle','off',...
+ 'MenuBar','none',...
+ 'Name','matRad Info',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1');
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this)
+ h94 = this.widgetHandle;
+ matRad_cfg = MatRad_Config.instance();
+ txt = sprintf('Info about\nsoftware environment & version\nmatRad version & branch');
+ %About button
+ h95 = uicontrol(...
+ 'Parent',h94,...
+ 'Units','normalized',...
+ 'String','About',...
+ 'TooltipString', txt,...
+ 'Position',[0.2 0.14 0.6 0.28],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) btnAbout_Callback(this,hObject,eventdata),...
+ 'Tag','btnAbout',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName);
+
+ %Position String
+ h96 = uicontrol(...
+ 'Parent',h94,...
+ 'Units','normalized',...
+ 'Style','text',...
+ 'Position',[0.1 0.75 0.8 0.2],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','text15',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName);
+
+ %URL Position String
+ h97 = uicontrol(...
+ 'Parent',h94,...
+ 'Units','normalized',...
+ 'Style','text',...
+ 'Position',[0.05 0.5 0.9 0.17],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.highlightColor,...
+ 'Tag','text31',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight','bold');
+
+ this.createHandles();
+ handles=this.handles;
+ %Alter matRad Version string positioning
+ vString = matRad_version();
+ vPos = get(handles.text15,'Position');
+ urlPos = get(handles.text31,'Position');
+ btnPos = get(handles.btnAbout,'Position');
+
+ %vPos([1 3]) = urlPos([1 3]);
+ vPos([1 3]) = [0 1];
+ vPos(4) = vPos(4)*1.25;
+ btnPos(2) = 0.05;
+ urlPos(2) = btnPos(2)+btnPos(4)+0.05;
+ vPos(2) = urlPos(2) + urlPos(4) + 0.05;
+ vPos(4) = 0.98 - vPos(2);
+
+ set(handles.btnAbout,'Position',btnPos);
+ set(handles.text31,'String','www.matRad.org','Position',urlPos,'Enable','inactive','ButtonDownFcn', @(~,~) web('www.matrad.org','-browser'));
+ set(handles.text15,'String',vString,'Position',vPos);
+ this.handles=handles;
+ end
+ end
+
+ methods (Access = protected)
+ function btnAbout_Callback(this, hObject, event)
+ handles = this.handles;
+ %msgbox({'https://github.com/e0404/matRad/' 'email: matrad@dkfz.de'},'About');
+
+ matRad_cfg = MatRad_Config.instance();
+ [~,matRadVer] = matRad_version;
+
+ if isfield(handles,'aboutBox') && ishghandle(handles.aboutBox)
+ delete(handles.aboutBox);
+ end
+
+ %Version Information
+ msg{1} = ['matRad ''' matRadVer.name '''']; %Name
+ if matRad_cfg.eduMode
+ msg{1} = [msg{1} ' Educational'];
+ end
+ msg{end+1} = sprintf('v%d.%d.%d',matRadVer.major,matRadVer.minor,matRadVer.patch); %Version Number
+ if isdeployed
+ msg{end+1} = 'Standalone Version';
+ elseif ~isempty(matRadVer.branch) && ~isempty(matRadVer.commitID)
+ msg{end+1} = sprintf('Git: Branch %s, commit %s',matRadVer.branch,matRadVer.commitID(1:8));
+ end
+
+ msg{end+1} = sprintf('Environment: %s v%s %s',matRad_cfg.env,matRad_cfg.envVersion,version('-release'));
+ msg{end+1} = newline;
+
+ %Contact Information
+ msg{end+1} = 'Web: www.matrad.org';
+ msg{end+1} = 'E-Mail: contact@matrad.org';
+ msg{end+1} = newline;
+
+ %Theme Information
+ msg{end+1} = 'GUI Themes:';
+ darkTheme = matRad_ThemeDark();
+ msg{end+1} = sprintf('%s - %s by %s',darkTheme.name,darkTheme.description,darkTheme.author);
+ lightTheme = matRad_ThemeLight();
+ msg{end+1} = sprintf('%s - %s by %s',lightTheme.name,lightTheme.description,lightTheme.author);
+ msg{end+1} = newline;
+
+
+ %Info Teext
+ msg{end+1} = matRad_info();
+
+ handles.aboutBox = msgbox(msg,'About matRad');
+ matRad_applyThemeToDlg(handles.aboutBox);
+
+ this.handles = handles;
+ end
+ end
+end
diff --git a/matRad/gui/widgets/matRad_LogoWidget.m b/matRad/gui/widgets/matRad_LogoWidget.m
new file mode 100644
index 000000000..9ed8aa5de
--- /dev/null
+++ b/matRad/gui/widgets/matRad_LogoWidget.m
@@ -0,0 +1,108 @@
+classdef matRad_LogoWidget < matRad_Widget
+ % matRad_InfoWidget class to display GUI logo widget
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ end
+
+ methods
+ function this = matRad_LogoWidget(handleParent)
+ if nargin < 1
+ matRad_cfg = MatRad_Config.instance();
+ handleParent = figure(...
+ 'MenuBar','none',...
+ 'Units','pixels',...
+ 'Position',[500 500 1000 125],...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'Name','MatRad Logo',...
+ 'IntegerHandle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure_importDialog',...
+ 'WindowStyle','normal',... 'PaperSize',[8.5 11],...
+ 'PaperType','usletter');
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+
+
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this)
+ %mfile = which(mfilename);
+ %[filepath] = fileparts(mfile);
+
+ matRad_cfg = MatRad_Config.instance();
+
+ filepath = matRad_cfg.matRadSrcRoot;
+
+ h1 = this.widgetHandle;
+
+ %matRad logo
+ h2 = axes(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'FontName','CMU Serif',...
+ 'Position',[0 0.0 0.5 0.875],...'Position',[- 0.304874274661509 -0.12225992317542 0.994397163120567 1.048719590268886],...
+ 'SortMethod','childorder',...
+ 'Tag','axesLogo');
+
+ [im,alpha] = matRad_getLogo();
+ f = image(im,'Parent',h2);
+ axis(h2,'image','off');
+ set(f, 'AlphaData', alpha);
+
+
+ %dkfz logo
+ h7 = axes(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'FontName','CMU Serif',...
+ 'Position',[0.5 0.0 0.5 0.85],...
+ 'SortMethod','childorder',...
+ 'Tag','axesDKFZ');
+
+ [im,alpha] = matRad_getLogoDKFZ();
+
+ f = image(im,'Parent',h7);
+ axis(h7,'image','off');
+ axis(h7,'tight');
+ set(f, 'AlphaData', alpha);
+
+ uicontrol('Parent',h1,...
+ 'Style','Text',...
+ 'Units','Normalized',...
+ 'Position',[0 0.85 0.5 0.15],...
+ 'String','matRad is not a medical product! DO NOT USE IT CLINICALLY!',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+
+ this.createHandles();
+
+ end
+ end
+
+ % NO CALLBACKS
+end
+
+
diff --git a/matRad/gui/widgets/matRad_OptimizationWidget.m b/matRad/gui/widgets/matRad_OptimizationWidget.m
new file mode 100644
index 000000000..753379277
--- /dev/null
+++ b/matRad/gui/widgets/matRad_OptimizationWidget.m
@@ -0,0 +1,682 @@
+classdef matRad_OptimizationWidget < matRad_Widget
+ % matRad_OptimizationWidget class to generate GUI widget to set
+ % optimization options
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ end
+
+ methods
+ function this = matRad_OptimizationWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+ handleParent = figure(...
+ 'Units','characters',...
+ 'Position',[60 20 150 20],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad Optimization',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1');
+ if matRad_cfg.isMatlab
+ handleParent.AutoResizeChildren = 'off';
+ end
+ end
+ this = this@matRad_Widget(handleParent);
+
+ this.initialize();
+ end
+
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this)
+ h1 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+ % handle environment
+ switch matRad_cfg.env
+ case 'MATLAB'
+ set(h1,'SizeChangedFcn',@(hObject,eventdata) widget_SizeChangedFcn(this,hObject,eventdata));
+ case 'OCTAVE'
+ % this creates an infinite loop in octave
+ %set(h1,'SizeChangedFcn',@(hObject,eventdata) widget_SizeChangedFcn(this,hObject,eventdata));
+ end
+
+ this.createHandles();
+
+ end
+
+ function this = doUpdate(this,evt)
+
+ doUpdate = true;
+ if nargin == 2
+ %At pln changes and at cst/cst (for Isocenter and new settings)
+ %we need to update
+ doUpdate = this.checkUpdateNecessary({'cst_obj'},evt);
+ end
+
+ if doUpdate
+ if evalin('base','exist(''ct'')') && evalin('base','exist(''cst'')')
+ generateCstTable(this, evalin('base','cst'));
+ else
+ delete(get(this.widgetHandle,'Children'));
+ end
+ end
+
+ end
+ end
+
+ methods(Access = private)
+
+ function cst = generateCstTable(this,cst)
+ matRad_cfg = MatRad_Config.instance();
+
+ %cst = updateStructureTable(this,cst);
+ handles = this.handles;
+ cstPanel = this.widgetHandle;
+
+ cstPanelPos = get(cstPanel,'Position');
+ cstPanelPosUnit = get(cstPanel,'Units');
+ set(cstPanel,'Units','pixels');
+ cstPanelPosPix = get(cstPanel,'Position');
+ set(cstPanel,'Units',cstPanelPosUnit);
+ aspectRatio = cstPanelPosPix(3) / cstPanelPosPix(4);
+
+ %Parameters for line height
+ objHeight = 0.095;% 22;
+ lineHeight = 0.1; %25; %Height of a table line
+ yTopSep = 0.12;%40; %Separation of the first line from the top
+ %tableViewHeight = cstPanelPos(4) - yTopSep; %Full height of the view
+ tableViewHeight = 1 - yTopSep;
+
+ %Widths of the fields
+ buttonW = objHeight / aspectRatio; % Make button squared
+ nameW = 3*buttonW;%60;
+ typeW = 3*buttonW;%70;
+ opW = buttonW;%objHeight;
+ robustW = 3*buttonW;
+ functionW = 5*buttonW;%120;
+ penaltyW = 1.5*buttonW;%40;
+ paramTitleW = 4*buttonW;%120;
+ paramW = 1*buttonW;%30;
+ fieldSep = 0.25*buttonW; %Separation between fields horizontally
+
+ %Scrollbar
+ cstPanelChildren = get(cstPanel,'Children');
+ cstVertTableScroll = findobj(cstPanelChildren,'Style','slider');
+ if isempty(cstVertTableScroll)
+ sliderPos = 0;
+ else
+ sliderPos = get(cstVertTableScroll,'Max') - get(cstVertTableScroll,'Value');
+ end
+ %disp(num2str(sliderPos));
+ ypos = @(c) tableViewHeight - c*lineHeight + sliderPos;
+
+ delete(cstPanelChildren);
+
+ %Creates a dummy axis to allow for the use of textboxes instead of uicontrol to be able to use the (la)tex interpreter
+ tmpAxes = axes('Parent',cstPanel,'units','normalized','position',[0 0 1 1],'visible','off', 'FontSize',8);
+
+ organTypes = {'OAR', 'TARGET','EXTERNAL','IGNORED'};
+
+ %Get all Classes & classNames
+ classNames = matRad_getObjectivesAndConstraints();
+
+ %Get robustness version
+ robustObj = {'none','STOCH','VWWC','VWWC_INV','COWC','OWC','PROB'};
+
+ numOfObjectives = 0;
+ for i = 1:size(cst,1)
+ if ~isempty(cst{i,6})
+ numOfObjectives = numOfObjectives + numel(cst{i,6});
+ end
+ end
+ %line
+ % step count
+ cnt = 0;
+
+ newline = '\n';
+
+ %Setup Headlines
+ xPos = 0.01; %5
+
+ h1 = uicontrol(cstPanel,'Style','text', ...
+ 'String','+/-', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) buttonW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'TooltipString','Remove or add Constraint or Objective', ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor);
+
+ tmp_pos = get(h1,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h2 = uicontrol(cstPanel,'Style','text', ...
+ 'String','VOI name', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) nameW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'TooltipString','Name of the structure with objective/constraint', ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor);
+
+ tmp_pos = get(h2,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h3 = uicontrol(cstPanel,'Style','text', ...
+ 'String','VOI type', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) typeW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'TooltipString','Segmentation Classification', ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor);
+
+ tmp_pos = get(h3,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ txt = sprintf('Overlap Priority\n(Smaller number overlaps higher number)');
+ h4 = uicontrol(cstPanel,'Style','text', ...
+ 'String','OP', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) opW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'TooltipString',txt, ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor);
+
+ tmp_pos = get(h4,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ txt = sprintf('Type of robustness objective');
+ h5 = uicontrol(cstPanel,'Style','text', ...
+ 'String','Robustness', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) robustW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'TooltipString',txt, ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor);
+
+ tmp_pos = get(h5,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h6 = uicontrol(cstPanel,'Style','text', ...
+ 'String','Function', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) functionW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'TooltipString','Objective/Constraint function type', ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor);
+
+ tmp_pos = get(h6,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h7 = uicontrol(cstPanel,'Style','text', ...
+ 'String','p', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) penaltyW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'TooltipString','Optimization penalty', ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor);
+
+ tmp_pos = get(h7,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h8 = uicontrol(cstPanel,'Style','text', ...
+ 'String','| Parameters', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) paramTitleW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'TooltipString','List of parameters', ...
+ 'HorizontalAlignment','left', ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor);
+
+ tmp_pos = get(h8,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ cnt = cnt + 1;
+
+ %Create Objectives / Constraints controls
+ for i = 1:size(cst,1)
+ if strcmp(cst(i,3),'IGNORED')~=1
+ %Compatibility Layer for old objective format
+ if isstruct(cst{i,6})
+ cst{i,6} = num2cell(arrayfun(@matRad_DoseOptimizationFunction.convertOldOptimizationStruct,cst{i,6}));
+ end
+ for j=1:numel(cst{i,6})
+
+ obj = cst{i,6}{j};
+
+ %Convert to class if not
+ if ~isa(obj,'matRad_DoseOptimizationFunction')
+ try
+ obj = matRad_DoseOptimizationFunction.createInstanceFromStruct(obj);
+ catch ME
+ this.showWarning('Objective/Constraint not valid!\n%s',ME.message)
+ continue;
+ end
+ end
+
+ xPos = 0.01;%5;
+
+ h = uicontrol(cstPanel,'Style','pushbutton', ...
+ 'String','-', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) buttonW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'TooltipString','Remove Objective/Constraint', ...
+ 'Callback',@(hObject,eventdata)btObjRemove_Callback(this,hObject,eventdata),...
+ 'UserData',[i,j]);
+
+ tmp_pos = get(h,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h = uicontrol(cstPanel','Style','edit', ...
+ 'String',cst{i,2}, ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) nameW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight,'BackgroundColor',matRad_cfg.gui.elementColor,'ForegroundColor',matRad_cfg.gui.textColor,'TooltipString','Name',...
+ 'Enable','inactive',... %Disable editing of name atm
+ 'UserData',[i,2], ...
+ 'Callback',@(hObject,eventdata)editCstParams_Callback(this,hObject,eventdata)); %Callback added, however, editing is disabled atm
+
+ tmp_pos = get(h,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h = uicontrol(cstPanel,'Style','popupmenu', ...
+ 'String',organTypes', ...
+ 'Value',find(strcmp(cst{i,3},organTypes)), ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) typeW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'TooltipString','Segmentation Classification',...
+ 'UserData',[i,3], ...
+ 'Callback',@(hObject,eventdata)editCstParams_Callback(this,hObject,eventdata));
+
+ tmp_pos = get(h,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ txt = sprintf('Overlap Priority\n(Smaller number overlaps higher number)');
+ h = uicontrol(cstPanel,'Style','edit', ...
+ 'String',num2str(cst{i,5}.Priority), ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) opW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'TooltipString',txt,...
+ 'UserData',[i,5], ...
+ 'Callback',@(hObject,eventdata)editCstParams_Callback(this,hObject,eventdata));
+
+
+ tmp_pos = get(h,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h = uicontrol(cstPanel,'Style','popupmenu', ...
+ 'String',robustObj, ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) robustW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'TooltipString','Select Objective/Constraint',...
+ 'UserData',{[i,j],classNames(1,:)}, ...
+ 'Callback',@(hObject,eventdata)changeRobustness_Callback(this,hObject,eventdata));
+
+ if isfield(cst{i,6}{j},'robustness')
+ set(h,'Value',find(strcmp(cst{i,6}{j}.robustness,robustObj)));
+ else
+ set(h, 'Value',find(strcmp('none',robustObj)));
+ end
+
+ tmp_pos = get(h,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h = uicontrol(cstPanel,'Style','popupmenu', ...
+ 'String',classNames(2,:)', ...
+ 'Value',find(strcmp(obj.name,classNames(2,:))), ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) functionW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'TooltipString','Select Objective/Constraint',...
+ 'UserData',{[i,j],classNames(1,:)}, ...
+ 'Callback',@(hObject,eventdata)changeObjFunction_Callback(this,hObject,eventdata));
+
+ tmp_pos = get(h,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+
+ %Check if we have an objective to display penalty
+ if isa(obj,'DoseObjectives.matRad_DoseObjective')
+ h = uicontrol(cstPanel,'Style','edit', ...
+ 'String',num2str(obj.penalty), ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) penaltyW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'TooltipString','Objective Penalty', ...
+ 'UserData',[i,j,0],...
+ 'Callback',@(hObject,eventdata)editObjParam_Callback(this,hObject,eventdata));
+ else
+ h = uicontrol(cstPanel,'Style','edit','String','----','Units','normalized','Position',[xPos ypos(cnt) penaltyW objHeight], 'FontSize',matRad_cfg.gui.fontSize,'FontName',matRad_cfg.gui.fontName,'FontWeight',matRad_cfg.gui.fontWeight,'BackgroundColor',matRad_cfg.gui.elementColor,'ForegroundColor',matRad_cfg.gui.textColor,'Enable','off');
+ end
+ tmp_pos = get(h,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+
+ for p = 1:numel(obj.parameterNames)
+% h = text('Parent',tmpAxes,'String',['| ' obj.parameterNames{p} ':'],'VerticalAlignment','middle','Units','normalized','Position',[xPos ypos(cnt)+lineHeight/2],'Interpreter','tex','FontWeight','normal',...
+% 'FontSize',get(cstPanel,'FontSize'),'FontName',get(cstPanel,'FontName'),'FontUnits',get(cstPanel,'FontUnits'),'FontWeight','normal');%[xPos ypos(cnt) 100 objHeight]);
+ % there is no fontsize for cstPanel
+ h = text('Parent',tmpAxes, ...
+ 'String',['| ' obj.parameterNames{p} ':'], ...
+ 'VerticalAlignment','middle', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt)+lineHeight/2], ...
+ 'Interpreter','tex', ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'Color',matRad_cfg.gui.textColor);%[xPos ypos(cnt) 100 objHeight]);
+
+ tmp_pos = get(h,'Extent');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ %h = annotation(cstPanel,'textbox','String',obj.parameters{1,p},'Units','pix','Position', [xPos ypos(cnt) 100 objHeight],'Interpreter','Tex');
+
+ %Check if we have a cell and therefore a parameter list
+ if iscell(obj.parameterTypes{p})
+ h = uicontrol(cstPanel,'Style','popupmenu', ...
+ 'String',obj.parameterTypes{p}', ...
+ 'Value',obj.parameters{p}, ...
+ 'TooltipString',obj.parameterNames{p}, ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) paramW*2 objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'UserData',[i,j,p],...
+ 'Callback',@(hObject,eventdata)editObjParam_Callback(this,hObject,eventdata));
+ else
+ h = uicontrol(cstPanel,'Style','edit', ...
+ 'String',num2str(obj.parameters{p}), ...
+ 'TooltipString',obj.parameterNames{p}, ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) paramW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'UserData',[i,j,p],...
+ 'Callback',@(hObject,eventdata)editObjParam_Callback(this,hObject,eventdata));
+ end
+
+ tmp_pos = get(h,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ end
+
+ cnt = cnt +1;
+ end
+ end
+ end
+ xPos = 0.01; %5
+ hAdd = uicontrol(cstPanel,'Style','pushbutton', ...
+ 'String','+', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) buttonW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'TooltipString','Add Objective/Constraint', ...
+ 'Callback',@(hObject,eventdata)btObjAdd_Callback(this,hObject,eventdata)); %{@btObjAdd_Callback,handles});
+ tmp_pos = get(hAdd,'Position');
+ xPos = xPos + tmp_pos(3) + fieldSep;
+ h = uicontrol(cstPanel,'Style','popupmenu', ...
+ 'String',cst(:,2)', ...
+ 'Units','normalized', ...
+ 'Position',[xPos ypos(cnt) nameW objHeight], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'TooltipString','String describing the VOI');
+ set(hAdd,'UserData',h);
+
+ %Calculate Scrollbar
+ lastPos = ypos(cnt);
+ firstPos = ypos(0);
+ tableHeight = abs(firstPos - lastPos);
+
+ exceedFac = tableHeight / tableViewHeight;
+ if exceedFac > 1
+ sliderFac = exceedFac - 1;
+ uicontrol(cstPanel,'Style','slider', ...
+ 'Units','normalized', ...
+ 'Position',[0.975 0 0.025 1], ...
+ 'FontSize',matRad_cfg.gui.fontSize, ...
+ 'FontName',matRad_cfg.gui.fontName, ...
+ 'FontWeight',matRad_cfg.gui.fontWeight, ...
+ 'BackgroundColor',matRad_cfg.gui.elementColor, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor, ...
+ 'Min',0,'Max',ceil(sliderFac)*tableViewHeight, ...
+ 'SliderStep',[lineHeight tableViewHeight] ./ (ceil(sliderFac)*tableViewHeight), ...
+ 'Value',ceil(sliderFac)*tableViewHeight - sliderPos, ...
+ 'Callback', @(hObject,eventdata)cstTableSlider_Callback(this,hObject,eventdata));
+ end
+
+ this.handles = handles;
+ end
+
+ % --- Executes when the widget is resized.
+ function widget_SizeChangedFcn(this,hObject, eventdata)
+ % hObject handle to h1 (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ try
+ generateCstTable(this,evalin('base','cst'));
+ catch
+ end
+ end
+
+ function btObjAdd_Callback(this,hObject, ~)
+ % hObject handle to btnuiTableAdd (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ handles=this.handles;
+
+ popupHandle = get(hObject,'UserData');
+ cstIndex = get(popupHandle,'Value');
+
+ cst = evalin('base','cst');
+ %Add Standard Objective
+ if strcmp(cst{cstIndex,3},'TARGET')
+ cst{cstIndex,6}{end+1} = struct(DoseObjectives.matRad_SquaredDeviation);
+ else
+ cst{cstIndex,6}{end+1} = struct(DoseObjectives.matRad_SquaredOverdosing);
+ end
+
+ assignin('base','cst',cst);
+ this.handles=handles;
+ changedWorkspace(this,'cst_obj');
+
+ end
+ function btObjRemove_Callback(this,hObject, ~)
+ % hObject handle to btnuiTableAdd (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ handles=this.handles;
+ ix = get(hObject,'UserData');
+
+ cst = evalin('base','cst');
+ %Add Standard Objective
+
+ cst{ix(1),6}(ix(2)) = [];
+
+ assignin('base','cst',cst);
+ this.handles=handles;
+ this.changedWorkspace('cst_obj');
+
+ %generateCstTable(this,cst);
+
+ end
+
+ function changeObjFunction_Callback(this,hObject, ~)
+ % hObject handle to btnuiTableAdd (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ handles=this.handles;
+ data = get(hObject,'UserData');
+ ix = data{1};
+ classNames = data{2};
+ classToCreate = classNames{get(hObject,'Value')};
+
+ cst = evalin('base','cst');
+ %Add Standard Objective
+
+ %We just check if the user really wanted to change the objective to be
+ %user-friendly
+ currentObj = cst{ix(1),6}{ix(2)};
+ currentClass = class(currentObj);
+ if ~strcmp(currentClass,classToCreate)
+ newObj = eval(classToCreate);
+
+ % Only if we have a penalty value for optimization, apply the new one
+ % Maybe this check should be more exact?
+
+ %if (isfield(currentObj,'penalty') || isobject (currentObj ) && isprop(currentObj,'penalty')) && isprop(newObj,'penalty')
+ if (isfield(currentObj,'penalty') || isa(currentObj,'DoseObjectives.matRad_DoseObjective')) && isa(newObj,'DoseObjectives.matRad_DoseObjective')
+ newObj.penalty = currentObj.penalty;
+ end
+
+ cst{ix(1),6}{ix(2)} = struct(newObj);
+
+ assignin('base','cst',cst);
+ this.handles=handles;
+ this.changedWorkspace('cst_obj');
+
+ %generateCstTable(this,cst);
+ end
+ end
+
+ function changeRobustness_Callback(this,hObject,~)
+ handles = this.handles;
+ data = hObject.UserData;
+ cst = evalin('base','cst');
+ ix = data{1};
+
+ contentRobust = get(hObject,'String');
+ cst{ix(1),6}{ix(2)}.robustness = contentRobust{get(hObject,'Value')};
+
+ assignin('base','cst',cst);
+ this.handles=handles;
+ this.changedWorkspace('cst');
+
+ end
+ %CST Param Callback
+ function editCstParams_Callback(this,hObject,~)
+ handles=this.handles;
+ data = hObject.UserData;
+ ix = data(1);
+ col = data(2);
+
+ cst = evalin('base','cst');
+
+ str = get(hObject,'String');
+ val = get(hObject,'Value');
+
+ switch col
+ case 2
+ cst{ix,col} = str;
+ case 3
+ cst{ix,col} = str{val};
+ case 5
+ cst{ix,col}.Priority = uint32(str2double(str));
+ %cst{ix,col}=setfield(cst{ix,col},'Priority',uint32(str2double(str)));
+ otherwise
+ this.showWarning('Wrong column assignment in GUI based cst setting');
+ end
+
+ assignin('base','cst',cst);
+ this.handles=handles;
+ this.changedWorkspace('cst_obj');
+
+ %generateCstTable(this,cst);
+ end
+
+ function editObjParam_Callback(this,hObject, ~)
+ % hObject handle to btnuiTableAdd (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ handles=this.handles;
+ ix = get(hObject,'UserData');
+
+ cst = evalin('base','cst');
+
+ if ix(3) == 0
+ cst{ix(1),6}{ix(2)}.penalty = str2double(hObject.String);
+ elseif isequal(hObject.Style,'popupmenu')
+ cst{ix(1),6}{ix(2)}.parameters{ix(3)} = hObject.Value;
+ else
+ cst{ix(1),6}{ix(2)}.parameters{ix(3)} = str2double(hObject.String);
+ end
+
+ assignin('base','cst',cst);
+ this.handles=handles;
+ this.changedWorkspace('cst_obj');
+
+ %generateCstTable(this,cst);
+
+ end
+
+ % --- Executes on slider movement.
+ function cstTableSlider_Callback(this,~,~)
+ % hObject handle to cstTableSlider (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'Value') returns position of slider
+ % get(hObject,'Min') and get(hObject,'Max') to determine range of slider
+ try
+ generateCstTable(this,evalin('base','cst'));
+ catch
+ end
+ end
+ end
+end
diff --git a/matRad/gui/widgets/matRad_PlanWidget.m b/matRad/gui/widgets/matRad_PlanWidget.m
new file mode 100644
index 000000000..43fad079f
--- /dev/null
+++ b/matRad/gui/widgets/matRad_PlanWidget.m
@@ -0,0 +1,1566 @@
+classdef matRad_PlanWidget < matRad_Widget
+ % matRad_PlanWidget class to generate GUI widget to set and get plan parameters
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ State = false
+ Machines
+ Optimizations
+ end
+
+ properties (Access = private)
+ hTissueWindow;
+ end
+
+ properties (Constant)
+
+ modalities = {'photons','protons','carbon', 'helium','brachy'};
+ availableProjections = { 'physicalDose'; 'RBExD'; 'effect'; 'BED'; }
+
+ end
+
+ methods
+ function this = matRad_PlanWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+ handleParent = figure(...
+ 'Units','characters',...
+ 'Position',[100 50 125 15],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad Plan',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1');
+ if matRad_cfg.isMatlab
+ handleParent.AutoResizeChildren = 'off';
+ end
+ end
+ this = this@matRad_Widget(handleParent);
+
+ handles=this.handles;
+
+ if matRad_cfg.eduMode
+ %Visisbility in Educational Mode
+ eduHideHandles = {handles.radiobutton3Dconf,...
+ handles.btnRunDAO};
+ eduDisableHandles = {handles.editCouchAngle,handles.popUpMachine};
+ cellfun(@(h) set(h,'Visible','Off'),eduHideHandles);
+ cellfun(@(h) set(h,'Enable','Off'),eduDisableHandles);
+ end
+ this.handles=handles;
+
+ end
+
+ end
+
+ methods(Access = protected)
+ function this = createLayout(this)
+ h12 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ gridSize = [5 8];
+ [i,j] = ndgrid(1:gridSize(1),1:gridSize(2));
+ gridPos = arrayfun(@(i,j) computeGridPos(this,[i j],gridSize),i,j,'UniformOutput',false);
+ txt = sprintf('Photons: Choose width (and height) of quadratic photon bixel (i.e. discrete fluence elements)\nParticles: Choose lateral spot distance');
+ %Text bixel width
+ h13 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','bixel width in [mm]',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,1},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtBixelWidth',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Edit Bixel Width
+ txt = sprintf('Photons: Choose width (and height) of quadratic photon bixel (i.e. discrete fluence elements)\nParticles: Choose lateral spot distance');
+ h14 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','5',...
+ 'TooltipString',txt,...
+ 'Style','edit',...
+ 'Position',gridPos{2,1},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Tag','editBixelWidth',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text Gantry Angle
+ txt = sprintf('Define gantry angles according to the matRad coordinate system\nEvery gantry angle defines a beam and needs a couch angle\nSeparate individual angles by blanks');
+ h15 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Gantry Angle in °',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,2},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtGantryAngle',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Edit Gantry Angle
+ txt = sprintf('Define gantry angles according to the matRad coordinate system\nEvery gantry angle defines a beam and needs a couch angle\nSeparate individual angles by blanks');
+ h16 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','0',...
+ 'TooltipString',txt,...
+ 'Style','edit',...
+ 'Position',gridPos{2,2},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Tag','editGantryAngle',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text Couch Angle
+ txt = sprintf('Define couch angles according to the matRad coordinate system\nEvery couch angle defines a beam and needs a gantry angle\nSeparate individual angles by blanks');
+ h17 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Couch Angle in °',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,3},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtCouchAngle',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Edit Couch Angle
+ txt = sprintf('Define couch angles according to the matRad coordinate system\nEvery couch angle defines a beam and needs a gantry angle\nSeparate individual angles by blanks');
+ h18 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','0',...
+ 'TooltipString',txt,...
+ 'Style','edit',...
+ 'Position',gridPos{2,3},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Tag','editCouchAngle',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %PopUp Menu RadMode
+ h19 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String',this.modalities,...,...
+ 'TooltipString','Choose a radiation modality (photons, protons, carbon, helium or brachy)',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{2,4},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)popupRadMode_Callback(this,hObject,eventdata),...
+ 'Tag','popupRadMode',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text RadMode
+ h20 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Radiation Mode',...
+ 'TooltipString','Choose a radiation modality (photons, protons, carbon, helium or brachy)',...
+ 'Style','text',...
+ 'Position',gridPos{1,4},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtRadMode');
+
+ %Text # Fractions
+ h21 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','# Fractions',...
+ 'TooltipString','Define the number of fractions',...
+ 'Style','text',...
+ 'Position',gridPos{1,7},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtNumOfFractions');
+
+ %Edit # Fraction
+ h22 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'TooltipString','Define the number of fractions',...
+ 'String','30',...
+ 'Style','edit',...
+ 'Position',gridPos{2,7},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Tag','editFraction',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text Iso Center
+ txt = sprintf('Choose the isocenter of the treatment plan in voxel coordinates within the ct.cube\nIf Auto. is checked, the isocenter is calculated as the center of gravity of all voxels belonging to structures that have been modeled as target volume in the cst cell');
+ h23 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','IsoCenter in [mm]',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,6},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Tag','txtIsoCenter',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Edit Iso centr
+ txt = sprintf('Choose the isocenter of the treatment plan in voxel coordinates within the ct.cube\nIf Auto. is checked, the isocenter is calculated as the center of gravity of all voxels belonging to structures that have been modeled as target volume in the cst cell');
+ h24 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','0 0 0',...
+ 'TooltipString', txt,...
+ 'Style','edit',...
+ 'Position',gridPos{2,6},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) editIsocenter_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'Tag','editIsoCenter',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Auto Iso Center Checkbox
+ h25 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Auto.',...
+ 'TooltipString','If this is checked, the isocenter is calculated as the center of gravity of all voxels belonging to structures that have been modeled as target volume in the cst cell',...
+ 'Style','checkbox',...
+ 'Value',1,...
+ 'Position',gridPos{3,6},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','checkIsoCenter');
+
+ %Popup menu for Machine file
+ txt = sprintf('Choose a base data set\nIf Generic is selected for a photon treatment plan, the already available photons_Generic.mat file is loaded');
+ h30 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String',{'Generic','generic_MCsquare'},...
+ 'TooltipString',txt,...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{2,5},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) popUpMachine_Callback(this,hObject,eventdata),...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','popUpMachine');
+
+ %Text Machine
+ h31 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Machine',...
+ 'TooltipString','Choose a base data set',...
+ 'Style','text',...
+ 'Position',gridPos{1,5},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtMachine' );
+
+ %Set tissue button
+ txt = sprintf('Set the tissue parameters of the VOIs\nThe base data file contains depth-dependent alpha and beta values, which are different depending on the tissue class');
+ h32 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Set Tissue α/β',...
+ 'TooltipString',txt,...
+ 'Position',gridPos{3,8},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) btnSetTissue_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','btnSetTissue');
+
+ %Popup menu for Biological model and optimized quantity
+ txt = sprintf('Choose a quantity to optimize \nPhysical Dose: physical dose is optimized\nRBExD: RBE-weighted dose is optimized\neffect: effect calculated according to LQ model is optimized');
+ h33 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String', this.availableProjections,...
+ 'TooltipString',txt,...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{2,8},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) popMenuQuantityOpt_Callback(this,hObject,eventdata),...
+ 'Tag','popMenuQuantityOpt',...
+ 'Enable', 'off',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Text for Biological model and optimized quantity
+ txt = sprintf('Choose a quantity to optimize \nPhysical Dose: physical dose is optimized\nRBExD: RBE-weighted dose is optimized\neffect: effect calculated according to LQ model is optimized');
+ h34 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Optimized quantity',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',gridPos{1,8},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Interruptible','off',...
+ 'Tag','txtQuantityOpt',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ % Radiobutton 3d Conformal
+ pos = gridPos{4,1};
+ pos(3) = pos(3)*2;
+
+ h36 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','3D conformal',...
+ 'TooltipString','Check this if you want to execute 3D conformal planning',...
+ 'Style','radiobutton',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','radiobutton3Dconf' );
+
+ %Run Sequencing radiobutton
+ pos = gridPos{5,1};
+ pos(3) = pos(3)*2;
+
+ txt = sprintf('Check this if you want to run a MLC sequencing\nThe number of stratification levels can be adjusted below');
+ h26 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Run Sequencing',...
+ 'TooltipString',txt,...
+ 'Style','radiobutton',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Enable','off',...
+ 'Tag','btnRunSequencing');
+
+ %Text Sequencing
+ pos = gridPos{4,2};
+ pos(3) = pos(3) * 1.5;
+
+ h28 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Stratification Levels:',...
+ 'TooltipString','Choose the number of stratification levels in case you run a MLC sequencing',...
+ 'HorizontalAlignment','left',...
+ 'Style','text',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtSequencing' );
+
+ %Sequencing Level Edit
+ pos = gridPos{5,2};
+ pos(3) = pos(3) / 2;
+
+ h29 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','7',...
+ 'TooltipString','Choose the number of stratification levels in case you run a MLC sequencing',...
+ 'Style','edit',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata)updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','editSequencingLevel');
+
+ %Text Sequencer
+ h40 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Sequencer : ',...
+ 'TooltipString','Set the sequencing algorithm',...
+ 'HorizontalAlignment','left',...
+ 'Style','text',...
+ 'Position',gridPos{4,3},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Interruptible','off',...
+ 'Tag','txtSequencer',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Popup menu selectin sequencing algorithm#
+ txt = sprintf('Choose a sequencing algorithm (siochi, xia or engel)');
+ h41 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String',{ 'siochi','xia','engel' },...
+ 'TooltipString',txt,...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{5,3},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) popUpMenuSequencer_Callback(this,hObject,eventdata),...
+ 'Tag','popUpMenuSequencer',...
+ 'Enable', 'off',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ % Direct Aperture Optimization radiobutton
+ pos = gridPos{4,4};
+ pos(3) = pos(3)*2;
+
+ h27 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Run Direct Aperture Optimization',...
+ 'TooltipString','Check this if you want to run an additional direct aperture optimization',...
+ 'Style','radiobutton',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata)updatePlnInWorkspace(this,hObject,eventdata),...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Enable','off',...
+ 'Tag','btnRunDAO' );
+
+ %Biological Model
+ pos = gridPos{4,5};
+ pos(3) = pos(3) * 1.5;
+
+ h45 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Biological model:',...
+ 'TooltipString','Choose the biological model',...
+ 'HorizontalAlignment','left',...
+ 'Style','text',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtBioModel');
+
+ pos = gridPos{5,5};
+ pos(3) = pos(3) / 2;
+ h55 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String',{ 'none';'constRBE'; 'MCN';'WED';'LEM';'HEL'; },...
+ 'TooltipString','Choose a biological model to be applied',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) popMenuBioModel_Callback(this,hObject,eventdata),...
+ 'Tag','popMenuBioModel',...
+ 'Enable', 'off',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ pos = gridPos{4,6};
+ pos(3) = pos(3) * 1.5;
+
+ h46 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Scenario Selection:',...
+ 'TooltipString','Choose the scenario sampling method',...
+ 'HorizontalAlignment','left',...
+ 'Style','text',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtMultScen' );
+
+ scenarioModels = matRad_ScenarioModel.getAvailableModels();
+ scenarioModels = {scenarioModels.shortName};
+
+ pos = gridPos{5,6};
+ pos(3) = pos(3) / 2;
+ h56 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String',scenarioModels,...
+ 'TooltipString','List of available scenario Models',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) popMenuMultScen_Callback(this,hObject,eventdata),...
+ 'Tag','popMenuMultScen',...
+ 'Enable', 'off',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+
+ % Text for dose Grid resolution
+ pos = gridPos{4,7};
+ pos(3) = pos(3)*2;
+
+ h35 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Dose Grid Resolution: ',...
+ 'TooltipString','Set the size of an individual voxel in the dose cube',...
+ 'HorizontalAlignment','left',...
+ 'Style','text',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Interruptible','off',...
+ 'Tag','textDoseGrid',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ pos = gridPos{5,7};
+ %pos(3) = pos(3)*2;
+
+ h36 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','use CT grid',...
+ 'TooltipString', txt,...
+ 'Style','pushbutton',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) applyCtGrid_callback(this,hObject,eventdata),...
+ 'Enable','on',...
+ 'Value',0,...
+ 'Tag','buttonUseCtGrid',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ % Edit dose grid x
+ pos = gridPos{4,8};
+ pos(3) = pos(3)*0.5;
+
+ h37 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','5',...
+ 'TooltipString','Set the size of an individual voxel in the dose cube in x-direction',...
+ 'Style','edit',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Tag','editDoseX',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+ % positioning dose grid size input boxes
+ pos(1) = pos(1) + pos(3) + 0.005;
+ % Edit dose grid y
+ h38 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','5',...
+ 'TooltipString','Set the size of an individual voxel in the dose cube in y-direction',...
+ 'Style','edit',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Tag','editDoseY',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ pos(1) = pos(1) + pos(3) + 0.005;
+ % Edit dose grid z
+ h39 = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','5',...
+ 'TooltipString','Set the size of an individual voxel in the dose cube in z-direction',...
+ 'Style','edit',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Tag','editDoseZ',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ pos(1) = pos(1) + pos(3) + 0.005;
+ % text dose grid dimension [mm]
+ h42 = uicontrol(...
+ 'Parent',h12,...setpln
+ 'Units','normalized',...
+ 'String','[mm]',...
+ 'TooltipString','Set the size of an individual voxel in the dose cube',...
+ 'HorizontalAlignment','left',...
+ 'Style','text',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Interruptible','off',...
+ 'Tag','txtGridmm',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ pos = gridPos{3,4};
+
+ hTxtDoseEngine = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String','Dose Engine',...
+ 'TooltipString','Set the size of an individual voxel in the dose cube',...
+ 'HorizontalAlignment','center',...
+ 'Style','text',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Interruptible','off',...
+ 'Tag','textDoseEngine',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ pos = gridPos{3,5};
+
+ hSelectDoseEngine = uicontrol(...
+ 'Parent',h12,...
+ 'Units','normalized',...
+ 'String',{'auto'},...
+ 'TooltipString',txt,...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) updatePlnInWorkspace(this,hObject,eventdata),...
+ 'Tag','popUpMenuDoseEngine',...
+ 'Enable', 'on',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ this.createHandles();
+ end
+
+ function this = doUpdate(this,evt)
+ doUpdate = true;
+ if nargin == 2
+ %At pln changes and at cst/cst (for Isocenter and new settings)
+ %we need to update
+ doUpdate = this.checkUpdateNecessary({'pln','ct','cst'},evt);
+ end
+
+ if doUpdate
+ if evalin('base','exist(''pln'')')
+ getPlnFromWorkspace(this);
+ else
+ setPlnDefaultValues(this);
+ end
+ end
+ end
+
+ %Set default values for the PLN on matRadGUI startup
+ function this = setPlnDefaultValues(this)
+
+ handles = this.handles;
+
+ this.getMachines();
+
+ %
+ vChar = get(handles.editGantryAngle,'String');
+ if strcmp(vChar(1,1),'0') && length(vChar)==6
+ set(handles.editGantryAngle,'String','0');
+ end
+ vChar = get(handles.editCouchAngle,'String');
+ if strcmp(vChar(1,1),'0') && length(vChar)==3
+ set(handles.editCouchAngle,'String','0')
+ end
+
+ % do not calculate / suggest isoCenter new by default
+ %this.checkIsoCenter_Callback(handles.checkIsoCenter);
+ set(handles.editIsoCenter,'Enable','on');
+ set(handles.popMenuQuantityOpt,'Value',1);
+ set(handles.popMenuBioModel,'Value',1);
+ set(handles.popMenuMultScen,'Value',1);
+ this.handles=handles;
+
+ updatePlnInWorkspace(this);
+
+ this.getPlnFromWorkspace();
+ end
+
+ %Get pln from workspace and update the Settings displayed in GUI
+ function this = getPlnFromWorkspace(this)
+ pln = evalin('base', 'pln');
+ handles = this.handles;
+
+ matRad_cfg = MatRad_Config.instance();
+
+
+ % sanity check of isoCenter
+ if size(pln.propStf.isoCenter,1) ~= pln.propStf.numOfBeams && size(pln.propStf.isoCenter,1) == 1
+ pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * pln.propStf.isoCenter(1,:);
+ elseif size(pln.propStf.isoCenter,1) ~= pln.propStf.numOfBeams && size(pln.propStf.isoCenter,1) ~= 1
+ this.showError('Isocenter in plan file are inconsistent.');
+ end
+
+
+ set(handles.editBixelWidth,'String',num2str(pln.propStf.bixelWidth));
+ set(handles.editGantryAngle,'String',num2str(pln.propStf.gantryAngles));
+ set(handles.editCouchAngle,'String',num2str(pln.propStf.couchAngles));
+
+ modIx = find(strcmp(pln.radiationMode,this.modalities));
+ set(handles.popupRadMode,'Value',modIx);
+
+ getMachines(this);
+ modIy = find(strcmp(pln.machine,this.Machines(this.modalities{modIx})));
+ set(handles.popUpMachine,'Value',modIy);
+
+ availableEngines = DoseEngines.matRad_DoseEngineBase.getAvailableEngines(pln);
+ set(handles.popUpMenuDoseEngine,'String',{availableEngines(:).shortName});
+
+ if isfield(pln.propStf,'isoCenter')
+ if size(unique(pln.propStf.isoCenter,'rows'),1) == 1
+ set(handles.editIsoCenter,'String',regexprep(num2str((round(pln.propStf.isoCenter(1,:)*10))./10), '\s+', ' '));
+ set(handles.checkIsoCenter,'Enable','on');
+ if get(handles.checkIsoCenter,'Value')
+ set(handles.editIsoCenter,'Enable','off');
+ else
+ set(handles.editIsoCenter,'Enable','on');
+ end
+
+ else
+ set(handles.editIsoCenter,'String','multiple isoCenter');
+ set(handles.editIsoCenter,'Enable','off');
+ set(handles.checkIsoCenter,'Value',0);
+ set(handles.checkIsoCenter,'Enable','off');
+ end
+ end
+
+ set(handles.editFraction,'String',num2str(pln.numOfFractions));
+
+ if ~isfield(pln,'bioParam')
+ pln.bioParam = matRad_bioModel(pln.radiationMode,'physicalDose','none');
+ end
+
+ contentPopUpQuantityOpt = get(handles.popMenuQuantityOpt,'String');
+
+ ix = find(strcmp(pln.bioParam.quantityOpt,contentPopUpQuantityOpt));
+
+ if isempty(ix)
+ ix = 1;
+ end
+
+ set(handles.popMenuQuantityOpt,'Value',ix);
+
+ contentPopUpBioModel = get(handles.popMenuBioModel,'String');
+ ix = find(strcmp(pln.bioParam.model,contentPopUpBioModel));
+ set(handles.popMenuBioModel,'Value',ix);
+
+ if evalin('base','exist(''ct'')')
+ contentPopUpMultScen = get(handles.popMenuMultScen,'String');
+ if ~isfield(pln,'multScen')
+ ix = 1;
+ else
+ ix = find(strcmp(pln.multScen.shortName,contentPopUpMultScen));
+ end
+ set(handles.popMenuMultScen,'Value',ix);
+ end
+
+
+ set(handles.btnRunDAO,'Value',pln.propOpt.runDAO);
+ if isfield(pln, 'propSeq') && isfield(pln.propSeq, 'sequencingLevel')
+ set(handles.btnRunSequencing,'Value',pln.propSeq.runSequencing);
+ set(handles.editSequencingLevel,'String',num2str(pln.propSeq.sequencingLevel));
+ else
+ set(handles.btnRunSequencing,'Value', 0 );
+ end
+
+ if isfield (pln.propOpt, 'conf3D')
+ set(handles.radiobutton3Dconf,'Value',pln.propOpt.conf3D);
+ end
+
+ if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'doseGrid')
+ pln.propDoseCalc.doseGrid.resolution = matRad_cfg.defaults.propDoseCalc.doseGrid.resolution;
+ end
+
+ set(handles.editDoseX,'String',num2str(pln.propDoseCalc.doseGrid.resolution.x));
+ set(handles.editDoseY,'String',num2str(pln.propDoseCalc.doseGrid.resolution.y));
+ set(handles.editDoseZ,'String',num2str(pln.propDoseCalc.doseGrid.resolution.z));
+
+ this.handles=handles;
+ this.switchEnables();
+ end
+
+ %Update the workspace pln from the Widget
+ function updatePlnInWorkspace(this,hObject,evtData)
+
+ if nargin < 3
+ evtData = [];
+ end
+
+ if nargin < 2
+ hObject = [];
+ end
+
+ this.getMachines();
+ handles = this.handles;
+
+ % evalin pln (if existant) in order to decide whether isoCenter should be calculated
+ % automatically
+ if evalin('base','exist(''pln'',''var'')')
+ pln = evalin('base','pln');
+ end
+
+ pln.propStf.bixelWidth = this.parseStringAsNum(get(handles.editBixelWidth,'String'),false); % [mm] / also corresponds to lateral spot spacing for particles
+
+ pln.propStf.gantryAngles = this.parseStringAsNum(get(handles.editGantryAngle,'String'),true); % [°]
+ pln.propStf.couchAngles = this.parseStringAsNum(get(handles.editCouchAngle,'String'),true); % [°]
+
+ if ~isempty(hObject) && strcmp(hObject.Tag,'editGantryAngle')
+ if numel(this.parseStringAsNum(get(handles.editCouchAngle,'String'),true))==1 % Feature: autofill couch angles to single plane by entering a single value
+ pln.propStf.couchAngles = this.parseStringAsNum(get(handles.editCouchAngle,'String'),true) * ones(1,numel(pln.propStf.gantryAngles));
+ else
+ pln.propStf.couchAngles = this.parseStringAsNum(get(handles.editCouchAngle,'String'),true); % [°]
+ end
+ elseif ~isempty(hObject) && strcmp(hObject.Tag,'editCouchAngle')
+ if numel(this.parseStringAsNum(get(handles.editGantryAngle,'String'),true))==1 % Feature: autofill gantry angles to single plane by entering a single value
+ pln.propStf.gantryAngles = this.parseStringAsNum(get(handles.editGantryAngle,'String'),true) * ones(1,numel(pln.propStf.couchAngles));
+ else
+ pln.propStf.gantryAngles = this.parseStringAsNum(get(handles.editGantryAngle,'String'),true); % [°]
+ end
+ end
+
+ pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+ pln.propStf.isoCenter = this.parseStringAsNum(get(handles.editIsoCenter,'String'),true);
+
+ % switch machines depending on radmode selection
+ selectedMachine = get(handles.popUpMachine,'Value');
+ popupMachines = get(handles.popUpMachine,'String');
+ pln.machine = popupMachines{selectedMachine};
+
+
+ pln.propDoseCalc.doseGrid.resolution.x = this.parseStringAsNum(get(handles.editDoseX,'String'),false);
+ pln.propDoseCalc.doseGrid.resolution.y = this.parseStringAsNum(get(handles.editDoseY,'String'),false);
+ pln.propDoseCalc.doseGrid.resolution.z = this.parseStringAsNum(get(handles.editDoseZ,'String'),false);
+
+ engines = get(handles.popUpMenuDoseEngine,'String');
+ selectedEngine = get(handles.popUpMenuDoseEngine,'Value');
+
+ if ~strcmp(engines{selectedEngine},'auto')
+ pln.propDoseCalc.engine = engines{selectedEngine};
+ else
+ if isfield(pln.propDoseCalc,'engine')
+ pln.propDoseCal = rmfield(pln.propDoseCalc,'engine');
+ end
+ end
+
+
+
+ pln.numOfFractions = this.parseStringAsNum(get(handles.editFraction,'String'),false);
+ contents = get(handles.popupRadMode,'String');
+ pln.radiationMode = contents{get(handles.popupRadMode,'Value')}; % either photons / protons / carbon
+ contents = get(handles.popUpMachine,'String');
+
+ % Biological model set
+ contentQuantityOpt = get(handles.popMenuQuantityOpt,'String');
+ contentBioModel = get(handles.popMenuBioModel,'String');
+ contentMultScen = get(handles.popMenuMultScen,'String');
+ pln.bioParam = matRad_bioModel(pln.radiationMode, contentQuantityOpt{get(handles.popMenuQuantityOpt,'Value'),:}, contentBioModel{get(handles.popMenuBioModel,'Value'),:});
+ if evalin('base','exist(''ct'')')
+ ct = evalin('base','ct');
+ pln.numOfVoxels = prod(ct.cubeDim);
+ pln.voxelDimensions = ct.cubeDim;
+ pln.multScen = matRad_multScen(ct,contentMultScen{get(handles.popMenuMultScen,'Value')});
+ end
+
+ % checkIsoCenter checkbox
+ W = evalin('base','whos');
+ doesPlnExist = ismember('pln',{W(:).name}) && evalin('base','exist(''cst'')') && evalin('base','exist(''ct'')');
+ if get(handles.checkIsoCenter,'Value') && doesPlnExist
+ try
+
+ if ~isfield(pln.propStf,'isoCenter')
+ pln.propStf.isoCenter = NaN;
+ end
+ tmpIsoCenter = matRad_getIsoCenter(evalin('base','cst'),evalin('base','ct'));
+ if ~isequal(tmpIsoCenter,pln.propStf.isoCenter)
+ pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1)*tmpIsoCenter;
+ %handles.State = 1;
+ %UpdateState(handles);
+ end
+ set(handles.editIsoCenter,'String',regexprep(num2str((round(tmpIsoCenter*10))./10), '\s+', ' '));
+ set(handles.editIsoCenter,'Enable','off')
+ assignin('base','pln',pln);
+ catch ME
+ this.showWarning('could not set isocenter in pln update! Reason: %s\n',ME.message)
+ end
+ else
+ set(handles.editIsoCenter,'Enable','on')
+ end
+ contents = get(handles.popUpMenuSequencer,'String');
+ pln.propSeq.sequencer = contents{get(handles.popUpMenuSequencer,'Value')};
+ pln.propSeq.runSequencing = logical(get(handles.btnRunSequencing,'Value'));
+ pln.propSeq.sequencingLevel = this.parseStringAsNum(get(handles.editSequencingLevel,'String'),false);
+ pln.propOpt.runDAO = logical(get(handles.btnRunDAO,'Value'));
+ pln.propOpt.conf3D = logical(get(handles.radiobutton3Dconf,'Value'));
+
+
+ if evalin('base','exist(''cst'')')
+ try
+ cst = evalin('base','cst');
+ if (sum(strcmp('TARGET',cst(:,3))) > 0 && get(handles.checkIsoCenter,'Value')) || ...
+ (sum(strcmp('TARGET',cst(:,3))) > 0 && ~isfield(pln.propStf,'isoCenter'))
+ pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct);
+ set(handles.checkIsoCenter,'Value',1);
+ else
+ if ~strcmp(get(handles.editIsoCenter,'String'),'multiple isoCenter')
+ pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * str2num(get(handles.editIsoCenter,'String'));
+ end
+ end
+ catch ME
+
+ this.showWarning('Could not set isocenter in pln update! Reason: %s\n',ME.message)
+ end
+ end
+
+ handles.pln = pln;
+ assignin('base','pln',pln);
+ this.handles = handles;
+ this.changedWorkspace('pln');
+ end
+ end
+
+ methods(Access = private)
+
+ % Enable/disable functionality in PlnWidget depending on the active
+ % Radmode
+ function switchEnables(this)
+ handles = this.handles;
+ hObject = handles.popupRadMode;
+
+ contents = cellstr(get(hObject,'String'));
+ RadIdentifier = contents{get(hObject,'Value')};
+ contentPopUpQuantityOpt = get(handles.popMenuQuantityOpt,'String');
+ contentPopUpBioModel = get(handles.popMenuBioModel,'String');
+ switch RadIdentifier
+ case 'photons'
+
+ set(handles.popMenuQuantityOpt,'Enable','on');
+% ix = find(strcmp(contentPopUpQuantityOpt,'physicalDose'));
+% set(handles.popMenuQuantityOpt,'Value',ix);
+ ix = find(strcmp(contentPopUpBioModel,'none'));
+ set(handles.popMenuBioModel,'Value',ix);
+ set(handles.popMenuBioModel,'Enable','off');
+ set(handles.btnSetTissue,'Enable','off');
+
+ set(handles.btnRunSequencing,'Enable','on');
+ set(handles.btnRunDAO,'Enable','on');
+ set(handles.radiobutton3Dconf,'Enable','on');
+ set(handles.txtSequencing,'Enable','on');
+ set(handles.editSequencingLevel,'Enable','on');
+ set(handles.popUpMenuSequencer,'Enable','on');
+ set(handles.txtSequencer,'Enable','on');
+ set(handles.popMenuMultScen, 'Enable','on');
+
+ if ~(get(handles.btnRunSequencing,'Value') || get(handles.btnRunDAO,'Value'))
+
+ set(handles.txtSequencing,'Enable','off');
+ set(handles.editSequencingLevel,'Enable','off');
+ set(handles.popUpMenuSequencer,'Enable','off');
+ set(handles.txtSequencer,'Enable','off');
+ else
+ set(handles.txtSequencing,'Enable','on');
+ set(handles.editSequencingLevel,'Enable','on');
+ set(handles.popUpMenuSequencer,'Enable','on');
+ set(handles.txtSequencer,'Enable','on');
+ end
+
+ case 'protons'
+ set(handles.popMenuQuantityOpt,'Enable','on');
+ set(handles.popMenuBioModel,'Enable','on');
+ set(handles.popMenuMultScen, 'Enable','on');
+ set(handles.btnSetTissue,'Enable','on');
+
+ set(handles.btnRunSequencing,'Enable','off');
+ set(handles.btnRunDAO,'Enable','off');
+ set(handles.radiobutton3Dconf,'Enable','off');
+ set(handles.txtSequencing,'Enable','off');
+ set(handles.editSequencingLevel,'Enable','off');
+ set(handles.popUpMenuSequencer,'Enable','off');
+ set(handles.txtSequencer,'Enable','off');
+
+ case 'carbon'
+
+ set(handles.popMenuQuantityOpt,'Enable','on');
+ set(handles.popMenuBioModel,'Enable','on');
+ set(handles.btnSetTissue,'Enable','on');
+ set(handles.popMenuMultScen, 'Enable','on');
+
+ set(handles.btnRunSequencing,'Enable','off');
+ set(handles.btnRunDAO,'Enable','off');
+ set(handles.radiobutton3Dconf,'Enable','off');
+ set(handles.txtSequencing,'Enable','off');
+ set(handles.editSequencingLevel,'Enable','off');
+ set(handles.popUpMenuSequencer,'Enable','off');
+ set(handles.txtSequencer,'Enable','off');
+
+ case 'brachy'
+
+ set(handles.popMenuQuantityOpt,'Enable','on');
+% ix = find(strcmp(contentPopUpQuantityOpt,'physicalDose'));
+% set(handles.popMenuQuantityOpt,'Value',ix);
+ ix = find(strcmp(contentPopUpBioModel,'none'));
+ set(handles.popMenuBioModel,'Value',ix);
+ set(handles.popMenuBioModel,'Enable','off');
+ set(handles.btnSetTissue,'Enable','off');
+
+ set(handles.btnRunSequencing,'Enable','on');
+ set(handles.btnRunDAO,'Enable','on');
+ set(handles.radiobutton3Dconf,'Enable','on');
+ set(handles.txtSequencing,'Enable','on');
+ set(handles.editSequencingLevel,'Enable','on');
+ set(handles.popUpMenuSequencer,'Enable','on');
+ set(handles.txtSequencer,'Enable','on');
+ set(handles.popMenuMultScen, 'Enable','on');
+
+ if ~(get(handles.btnRunSequencing,'Value') || get(handles.btnRunDAO,'Value'))
+
+ set(handles.txtSequencing,'Enable','off');
+ set(handles.editSequencingLevel,'Enable','off');
+ set(handles.popUpMenuSequencer,'Enable','off');
+ set(handles.txtSequencer,'Enable','off');
+ else
+ set(handles.txtSequencing,'Enable','on');
+ set(handles.editSequencingLevel,'Enable','on');
+ set(handles.popUpMenuSequencer,'Enable','on');
+ set(handles.txtSequencer,'Enable','on');
+ end
+ end
+
+ selectedQuantityOpt = get(handles.popMenuQuantityOpt,'Value');
+ if strcmp(contentPopUpQuantityOpt{selectedQuantityOpt},'physicalDose')
+ set(handles.btnSetTissue,'Enable','off');
+ else
+ set(handles.btnSetTissue,'Enable','on');
+ end
+
+ this.handles = handles;
+ end
+
+ function manageRadModeSpecificDisplay(this)
+ handles = this.handles;
+ hObject = this.popupRadMode('hObject');
+
+ this.handles = handles;
+ end
+%% CALLBACKS
+ function popupRadMode_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ defaultMachines.photons = 'Generic';
+ defaultMachines.protons = 'Generic';
+ defaultMachines.helium = 'Generic';
+ defaultMachines.carbon = 'Generic';
+ defaultMachines.brachy = 'HDR';
+ defaultMachines.fallback = 'Generic';
+
+ contents = cellstr(get(hObject,'String'));
+ RadIdentifier = contents{get(hObject,'Value')};
+ contentPopUp = get(handles.popMenuQuantityOpt,'String');
+
+ if any(strcmp(RadIdentifier,{'protons','helium','carbon'}))
+ ix = find(strcmp(contentPopUp,'RBExD'));
+ set(handles.popMenuQuantityOpt,'Value',ix);
+ end
+
+ pln = evalin('base','pln');
+
+ % new radiation modality is photons -> just keep physicalDose
+ try
+ AllVarNames = evalin('base','who');
+ if ismember('resultGUI',AllVarNames)
+ resultGUI = evalin('base','resultGUI');
+ radMode = contents(get(hObject,'Value'));
+ if any(strcmp(radMode,{'photons','brachy'}))
+ if isfield(resultGUI,'alpha'); resultGUI = rmfield(resultGUI,'alpha'); end
+ if isfield(resultGUI,'beta'); resultGUI = rmfield(resultGUI,'beta'); end
+ if isfield(resultGUI,'RBExDose'); resultGUI = rmfield(resultGUI,'RBExDose');end
+ if isfield(resultGUI,'RBE'); resultGUI = rmfield(resultGUI,'RBE'); end
+ assignin('base','resultGUI',resultGUI);
+ %handles = updateIsoDoseLineCache(handles);
+ elseif strcmp(radMode,'protons')
+ if isfield(resultGUI,'alpha'); resultGUI = rmfield(resultGUI,'alpha');end
+ if isfield(resultGUI,'beta'); resultGUI = rmfield(resultGUI,'beta'); end
+ if isfield(resultGUI,'RBE'); resultGUI = rmfield(resultGUI,'RBE'); end
+ end
+ assignin('base','resultGUI',resultGUI);
+ %handles = updateIsoDoseLineCache(handles);
+ end
+ catch
+ %Do nothing here
+ end
+
+ if ~strcmp(pln.radiationMode,RadIdentifier)
+ pln.radiationMode = RadIdentifier;
+ if isfield(defaultMachines,RadIdentifier)
+ pln.machine = defaultMachines.(RadIdentifier);
+ else
+ pln.machine = defaultMachines.fallback;
+ end
+ end
+
+ availableEngines = DoseEngines.matRad_DoseEngineBase.getAvailableEngines(pln);
+ set(handles.popUpMenuDoseEngine,'String',{availableEngines(:).shortName});
+
+ this.handles = handles;
+ updatePlnInWorkspace(this);
+ end
+
+ function editIsocenter_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ % checkIsoCenter checkbox
+ W = evalin('base','whos');
+ doesPlnExist = ismember('pln',{W(:).name}) && evalin('base','exist(''cst'')') && evalin('base','exist(''ct'')');
+ % evalin pln (if existant) in order to decide whether isoCenter should be calculated
+ % automatically
+ if doesPlnExist
+ pln = evalin('base','pln');
+ end
+
+
+ % editIsoCenter textbox
+ tmpIsoCenter = str2num(get(handles.editIsoCenter,'String'));
+
+ if length(tmpIsoCenter) == 3
+ if sum(any(unique(pln.propStf.isoCenter,'rows')~=tmpIsoCenter))
+ pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1)*tmpIsoCenter;
+
+ end
+ else
+ handles = showError(this,'EditIsoCenterCallback: Could not set iso center');
+ end
+ this.handles = handles;
+ updatePlnInWorkspace(this);
+ this.changedWorkspace('pln_display');
+ end
+
+ function applyCtGrid_callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ try
+ ct = evalin('base','ct');
+ resolution = ct.resolution;
+ %We use mat2str here because for some reason it prints to
+ %the required precision (not like num2str which rounds)
+ set(handles.editDoseX,'String',mat2str(resolution.x));
+ set(handles.editDoseY,'String',mat2str(resolution.y));
+ set(handles.editDoseZ,'String',mat2str(resolution.z));
+ catch ME
+ this.showWarning('Could not load resolution from CT!');
+ end
+ this.updatePlnInWorkspace();
+ end
+
+ function popUpMenuSequencer_Callback(this, hObject, eventdata)
+ handles = this.handles;
+ contents = cellstr(get(hObject,'String'));
+ SeqIdentifier = contents{get(hObject,'Value')};
+ contentPopUp = get(handles.popUpMenuSequencer,'String');
+
+ switch SeqIdentifier
+ case 'siochi'
+ ix = find(strcmp(contentPopUp,'siochi'));
+ set(handles.popUpMenuSequencer,'Value',ix);
+
+ case 'xia'
+ ix = find(strcmp(contentPopUp,'xia'));
+ set(handles.popUpMenuSequencer,'Value',ix);
+ case 'engel'
+ ix = find(strcmp(contentPopUp,'engel'));
+ set(handles.popUpMenuSequencer,'Value',ix);
+ end
+
+ pln = evalin('base','pln');
+
+
+ this.handles = handles;
+ updatePlnInWorkspace(this);
+ end
+
+ function popUpMachine_Callback(this, hObject, eventdata)
+ % MOEGLICHER FEHLER WEGEN VALUE WERT!
+ handles = this.handles;
+ contents = cellstr(get(hObject,'String'));
+ MachineIdentifier = contents{get(hObject,'Value')};
+ % contentPopUp = get(handles.)
+ flag=checkRadiationComposition(this);
+ if ~flag
+ this.showWarning(['No base data available for machine: ' MachineIdentifier '. Selecting default machine.']);
+ set(handles.popUpMachine,'Value',1);
+ end
+ getMachines(this);
+ pln = evalin('base','pln');
+
+ % MOEGLICHEE FEHLER HIER VALUE UND GENERIC WERDEN VERGLICHEN
+ if strcmp(contents(get(hObject,'Value')),'Generic')
+ try
+ AllVarNames = evalin('base','who');
+ if ismember('resultGUI',AllVarNames)
+ resultGUI = evalin('base','resultGUI');
+ if isfield(resultGUI,'alpha'); resultGUI = rmfield(resultGUI,'alpha'); end
+ if isfield(resultGUI,'beta'); resultGUI = rmfield(resultGUI,'beta'); end
+ if isfield(resultGUI,'RBExDose'); resultGUI = rmfield(resultGUI,'RBExDose');end
+ if isfield(resultGUI,'RBE'); resultGUI = rmfield(resultGUI,'RBE'); end
+ assignin('base','resultGUI',resultGUI);
+ %handles = updateIsoDoseLineCache(handles);
+ end
+ catch
+ end
+ % MOEGLICHEE FEHLER HIER VALUE UND GENERIC WERDEN VERGLICHEN
+ elseif strcmp(contents(get(hObject,'Value')),'generic_MCsquare')
+ try
+ AllVarNames = evalin('base','who');
+ if ismember('resultGUI',AllVarNames)
+ resultGUI = evalin('base','resultGUI');
+ if isfield(resultGUI,'alpha'); resultGUI = rmfield(resultGUI,'alpha');end
+ if isfield(resultGUI,'beta'); resultGUI = rmfield(resultGUI,'beta'); end
+ if isfield(resultGUI,'RBE'); resultGUI = rmfield(resultGUI,'RBE'); end
+ assignin('base','resultGUI',resultGUI);
+ %handles = updateIsoDoseLineCache(handles);
+ end
+ catch
+ end
+ end
+
+ availableEngines = DoseEngines.matRad_DoseEngineBase.getAvailableEngines(pln);
+ set(handles.popUpMenuDoseEngine,'String',{availableEngines(:).shortName});
+
+ this.handles = handles;
+ updatePlnInWorkspace(this);
+ end
+
+ function btnSetTissue_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ if evalin('base','exist(''cst'')') && evalin('base','exist(''pln'')')
+ try
+ %parse variables from base-workspace
+ cst = evalin('base','cst');
+ pln = evalin('base','pln');
+
+
+ fileName = [pln.radiationMode '_' pln.machine];
+ load(fileName);
+
+ % check for available cell types characterized by alphaX and betaX
+ for i = 1:size(machine.data(1).alphaX,2)
+ CellType{i} = [num2str(machine.data(1).alphaX(i)) ' ' num2str(machine.data(1).betaX(i))];
+ end
+
+ %fill table data array
+ for i = 1:size(cst,1)
+ data{i,1} = cst{i,2};
+ data{i,2} = [num2str(cst{i,5}.alphaX) ' ' num2str(cst{i,5}.betaX)];
+ data{i,3} = (cst{i,5}.alphaX / cst{i,5}.betaX );
+ end
+
+ Width = 400;
+ Height = 300 + 20*size(data,1);
+ ScreenSize = get(0,'ScreenSize');
+ % show "set tissue parameter" window
+ figHandles = get(0,'Children');
+ if ~isempty(figHandles)
+ IdxHandle = strcmp(get(figHandles,'Name'),'Set Tissue Parameters');
+ else
+ IdxHandle = [];
+ end
+
+ %check if window is already exists
+ if any(IdxHandle)
+ IdxTable = find(strcmp({figHandles(IdxHandle).Children.Type},'uitable'));
+ set(figHandles(IdxHandle).Children(IdxTable), 'Data', []);
+ figTissue = figHandles(IdxHandle);
+ %set focus
+ figure(figTissue);
+ else
+ figTissue = figure('Name','Set Tissue Parameters','Color',[.5 .5 .5],'NumberTitle','off','OuterPosition',...
+ [ceil(ScreenSize(3)/2) 100 Width Height]);
+ end
+
+ % define the tissue parameter table
+ cNames = {'VOI','alphaX betaX','alpha beta ratio'};
+ columnformat = {'char',CellType,'numeric'};
+
+ tissueTable = uitable('Parent', figTissue,'Data', data,'ColumnEditable',[false true false],...
+ 'ColumnName',cNames, 'ColumnFormat',columnformat,'Position',[50 150 10 10]);
+ set(tissueTable,'CellEditCallback',@(hObject,eventdata) tissueTable_CellEditCallback(this,hObject,eventdata));
+ % set width and height
+ currTablePos = get(tissueTable,'Position');
+ currTableExt = get(tissueTable,'Extent');
+ currTablePos(3) = currTableExt(3);
+ currTablePos(4) = currTableExt(4);
+ set(tissueTable,'Position',currTablePos);
+
+ % define two buttons with callbacks
+ uicontrol('Parent', figTissue,'Style', 'pushbutton', 'String', 'Save&Close',...
+ 'Position', [Width-(0.25*Width) 0.1 * Height 70 30],...
+ 'Callback', @(hpb,eventdata)SaveTissueParameters(this,hpb,eventdata));
+
+ uicontrol('Parent', figTissue,'Style', 'pushbutton', 'String', 'Cancel&Close',...
+ 'Position', [Width-(0.5*Width) 0.1 * Height 80 30],...
+ 'Callback', 'close');
+ catch ME
+ this.showWarning('Could not set Tissue parameter update! Reason: %s\n',ME.message)
+ end
+ end
+ this.handles = handles;
+
+ end
+
+ function popMenuBioModel_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ pln = evalin('base','pln');
+ contentBioModel = get(handles.popMenuBioModel,'String');
+ NewBioModel = contentBioModel(get(handles.popMenuBioModel,'Value'),:);
+
+% if (strcmp(pln.propOpt.bioOptimization,'LEMIV_effect') && strcmp(NewBioOptimization,'LEMIV_RBExD')) ||...
+% (strcmp(pln.propOpt.bioOptimization,'LEMIV_RBExD') && strcmp(NewBioOptimization,'LEMIV_effect'))
+% % do nothing - re-optimization is still possible
+% elseif ((strcmp(pln.propOpt.bioOptimization,'const_RBE') && strcmp(NewBioOptimization,'none')) ||...
+% (strcmp(pln.propOpt.bioOptimization,'none') && strcmp(NewBioOptimization,'const_RBE'))) && isequal(pln.radiationMode,'protons')
+% % do nothing - re-optimization is still possible
+% end
+%
+ this.handles = handles;
+ updatePlnInWorkspace(this);
+ end
+ function popMenuMultScen_Callback(this, hObject, eventdata)
+
+ updatePlnInWorkspace(this);
+ end
+
+ function popMenuQuantityOpt_Callback(this, hObject, eventdata)
+% handles = this.handles;
+%
+% pln = evalin('base','pln');
+% contentQuantityOpt = get(handles.popMenuQuantityOpt,'String');
+% NewQuantityOpt = contentQuantityOpt(get(handles.popMenuQuantityOpt,'Value'),:);
+%
+% % if (strcmp(pln.propOpt.bioOptimization,'LEMIV_effect') && strcmp(NewBioOptimization,'LEMIV_RBExD')) ||...
+% % (strcmp(pln.propOpt.bioOptimization,'LEMIV_RBExD') && strcmp(NewBioOptimization,'LEMIV_effect'))
+% % % do nothing - re-optimization is still possible
+% % elseif ((strcmp(pln.propOpt.bioOptimization,'const_RBE') && strcmp(NewBioOptimization,'none')) ||...
+% % (strcmp(pln.propOpt.bioOptimization,'none') && strcmp(NewBioOptimization,'const_RBE'))) && isequal(pln.radiationMode,'protons')
+% % % do nothing - re-optimization is still possible
+% % end
+% %
+% this.handles = handles;
+ updatePlnInWorkspace(this);
+ end
+
+ function tissueTable_CellEditCallback(this,hObject, eventdata)
+ if eventdata.Indices(2) == 2
+ alphaXBetaX = str2num(eventdata.NewData);
+ data = get(hObject,'Data');
+ data{eventdata.Indices(1),3} = alphaXBetaX(1)/alphaXBetaX(2);
+ set(hObject,'Data',data);
+ end
+ end
+%% END OF CALLBACKS
+
+ % load Machine File
+ function getMachines(this)
+ %matRad_cfg = MatRad_Config.instance();
+ %seach for availabes machines
+ handles = this.handles;
+ this.Machines = matRad_getAvailableMachines(this.modalities);
+
+ selectedRadMod = get(handles.popupRadMode,'Value');
+ nMachines = numel(this.Machines(this.modalities{selectedRadMod}));
+ selectedMachine = get(handles.popUpMachine,'Value');
+
+ if get(handles.popUpMachine,'Value') > nMachines
+ selectedMachine = 1;
+ end
+
+ set(handles.popUpMachine,'Value',selectedMachine,'String',this.Machines(this.modalities{selectedRadMod}));
+ this.handles = handles;
+ end
+
+ %String to num parser for edit fields
+ function number = parseStringAsNum(this,stringIn,isVector)
+ if isnumeric(stringIn)
+ number = stringIn;
+ else
+ number = str2num(stringIn);
+ if isempty(number) || length(number) > 1 && ~isVector
+ this.showWarning(['could not parse all parameters (pln, optimization parameter)']);
+ number = NaN;
+ elseif isVector && iscolumn(number)
+ number = number';
+ end
+ end
+ end
+
+ %Check if Machine File is available and correct
+ function flag = checkRadiationComposition(this)
+ matRad_cfg = MatRad_Config.instance();
+ handles = this.handles;
+
+ flag = true;
+ contents = cellstr(get(handles.popUpMachine,'String'));
+ Machine = contents{get(handles.popUpMachine,'Value')};
+ contents = cellstr(get(handles.popupRadMode,'String'));
+ radMod = contents{get(handles.popupRadMode,'Value')};
+
+ FoundFile = ismember(Machine,this.Machines(radMod));
+
+ if ~FoundFile
+ this.showWarning(['No base data available for machine: ' Machine '. Selecting default machine.']);
+ flag = false;
+ % set(handles.popUpMachine,'Value',1);
+ end
+ this.handles = handles;
+ end
+
+ %Save Tissue Parameters to cst
+ function SaveTissueParameters(this,~, ~)
+ cst = evalin('base','cst');
+ % get handle to uiTable
+ figHandles = get(0,'Children');
+ IdxHandle = find(strcmp(get(figHandles,'Name'),'Set Tissue Parameters'));
+ % find table in window
+
+ figHandleChildren = get(figHandles(IdxHandle),'Children');
+ IdxTable = find(strcmp(get(figHandleChildren,'Type'),'uitable'));
+ uiTable = figHandleChildren(IdxTable);
+ % retrieve data from uitable
+ data = get(uiTable,'data');
+
+ for i = 1:size(cst,1)
+ for j = 1:size(data,1)
+ if strcmp(cst{i,2},data{j,1})
+ alphaXBetaX = str2num(data{j,2});
+ cst{i,5}.alphaX = alphaXBetaX(1);
+ cst{i,5}.betaX = alphaXBetaX(2);
+ end
+ end
+ end
+ assignin('base','cst',cst);
+ close
+ updatePlnInWorkspace(this);
+ end
+
+
+
+
+ end
+end
diff --git a/matRad/gui/widgets/matRad_StatisticsWidget.m b/matRad/gui/widgets/matRad_StatisticsWidget.m
new file mode 100644
index 000000000..508c2d8ea
--- /dev/null
+++ b/matRad/gui/widgets/matRad_StatisticsWidget.m
@@ -0,0 +1,113 @@
+classdef matRad_StatisticsWidget < matRad_Widget
+ % matRad_StatisticsWidget class to generate GUI widget to display plan
+ % statistics.
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+ selectedCube = [];
+ end
+
+ events
+
+ end
+
+ methods
+
+ function this = matRad_StatisticsWidget(handleParent) % use (varargin) ?
+
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+
+ handleParent = figure(...
+ 'Units','normalized',...
+ 'Position',[0.005 0.05 0.495 0.45],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,... 'CloseRequestFcn',@(hObject,eventdata) figure1_CloseRequestFcn(this,hObject,eventdata),...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad Statistics',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figStat',...
+ 'PaperSize',[20.99999864 29.69999902]);
+
+
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+ end
+
+ methods(Access = protected)
+ function this = createLayout(this)
+ h88 = this.widgetHandle;
+ this.createHandles();
+
+ end
+
+ function this=doUpdate(this)
+ if ~this.updateLock && ~isempty(this.selectedCube)
+ doUpdate = true;
+ if nargin == 2
+ doUpdate = this.checkUpdateNecessary({'resultGUI','cst','pln'},evt);
+ end
+
+ if ~doUpdate
+ return;
+ end
+
+ if evalin('base','exist(''resultGUI'')')
+ this.showStatistics();
+ end
+ end
+ end
+ end
+
+ methods
+ function set.selectedCube(this,value)
+ this.selectedCube=value;
+ this.update();
+ end
+
+ function showStatistics(this)
+ if isempty(this.selectedCube)
+ return;
+ end
+
+ resultGUI = evalin('base','resultGUI');
+ pln = evalin('base','pln');
+ cst = evalin('base','cst');
+ doseCube = resultGUI.(this.selectedCube);
+
+ if ~exist('refVol', 'var')
+ refVol = [];
+ end
+
+ if ~exist('refGy', 'var')
+ refGy = [];
+ end
+
+ qi = matRad_calcQualityIndicators(cst,pln,doseCube,refGy,refVol);
+ ixVoi = cellfun(@(c) c.Visible == 1,cst(:,5));
+ qi = qi(ixVoi);
+ matRad_showQualityIndicators(this.widgetHandle,qi);
+
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/matRad/gui/widgets/matRad_StructureVisibilityWidget.m b/matRad/gui/widgets/matRad_StructureVisibilityWidget.m
new file mode 100644
index 000000000..5083129dc
--- /dev/null
+++ b/matRad/gui/widgets/matRad_StructureVisibilityWidget.m
@@ -0,0 +1,227 @@
+classdef matRad_StructureVisibilityWidget < matRad_Widget
+ % matRad_StructureVisibilityWidget class to generate GUI widget to set
+ % visibility of structure in viewing widget
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ methods
+ function this = matRad_StructureVisibilityWidget(handleParent)
+ if nargin < 1
+ matRad_cfg = MatRad_Config.instance();
+ handleParent = figure(...
+ 'Units','characters',...
+ 'Position',[200 10 30 30],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'IntegerHandle','off',...
+ 'MenuBar','none',...
+ 'Name','MatRad Structure Visibility',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1');
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+ end
+
+
+ methods (Access = protected)
+ function this = createLayout(this)
+ h86 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+ % List box of stuctures that can be selected for display
+
+ if this.isInUifigure
+ pos = getpixelposition(h86);
+ h87 = uilistbox(h86);
+ h87.Position = [0 0 pos(3) pos(4)];
+ h87.Tooltip = 'Choose which structures should be displayed in the GUI';
+ h87.BackgroundColor = matRad_cfg.gui.elementColor;
+ h87.FontColor = matRad_cfg.gui.textColor;
+ h87.FontSize = matRad_cfg.gui.fontSize;
+ h87.FontWeight = matRad_cfg.gui.fontWeight;
+ h87.FontName = matRad_cfg.gui.fontName;
+ h87.ClickedFcn = @(hObject,eventdata) legendTable_Callback(this,hObject,eventdata);
+ h87.Items = {'no data loaded'};
+ h87.Tag = 'legendTable';
+ else
+ h87 = uicontrol(...
+ 'Parent',h86,...
+ 'Units','normalized',...
+ 'TooltipString','Choose which structures should be displayed in the GUI',...
+ 'HorizontalAlignment','left',...
+ 'Max',1,...
+ 'Style','listbox',...
+ 'Value',1,...
+ 'String','no data loaded',...
+ 'Position',[0.02 0.01 0.97 0.98],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Callback',@(hObject,eventdata) legendTable_Callback(this,hObject,eventdata),...
+ 'Tag','legendTable');
+ end
+ this.createHandles();
+
+ end
+
+ function this=doUpdate(this,evt)
+ if evalin('base','exist(''ct'')') && evalin('base','exist(''cst'')')
+ updateStructureTable(this, evalin('base','cst'));
+ else
+ if this.isInUifigure()
+ this.handles.legendTable.Items = {'no data loaded'};
+ else
+ set(this.handles.legendTable,'String','no data loaded');
+ end
+ end
+ end
+ end
+
+
+
+ methods (Access = protected)
+ function legendTable_Callback(this, hObject, event)
+ % hObject handle to legendTable (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: contents = cellstr(get(hObject,'String')) returns legendTable contents as cell array
+ % contents{get(hObject,'Value')} returns selected item from legendTable
+
+ if this.isInUifigure
+ content = hObject.Items;
+ idx = event.InteractionInformation.Item;
+ else
+ content = get(hObject,'String');
+ idx = get(hObject,'Value');
+ end
+ if numel(content) == 1 && strcmp(content,'no data loaded')
+ return;
+ end
+
+ handles = this.handles;
+ cst = evalin('base','cst');
+
+ if handles.VOIPlotFlag(idx)
+ handles.VOIPlotFlag(idx) = false;
+ cst{idx,5}.Visible = false;
+ else
+ handles.VOIPlotFlag(idx) = true;
+ cst{idx,5}.Visible = true;
+ end
+
+ this.updateStructureTable(cst);
+
+ % update cst in workspace accordingly
+ assignin('base','cst',cst)
+ this.handles = handles;
+ this.changedWorkspace('cst_param');
+ %UpdatePlot(handles)
+ end
+
+ %Update cst with Visibility settings
+ function cst = updateStructureTable(this,cst)
+ handles=this.handles;
+ colorAssigned = true;
+
+
+ % check whether all structures have an assigned color
+ for i = 1:size(cst,1)
+ if ~isfield(cst{i,5},'visibleColor')
+ colorAssigned = false;
+ break;
+ elseif isempty(cst{i,5}.visibleColor)
+ colorAssigned = false;
+ break;
+ end
+ end
+
+ % assign color if color assignment is not already present or inconsistent
+ if colorAssigned == false
+ m = 64;
+ colorStep = ceil(m/size(cst,1));
+ colors = colorcube(colorStep*size(cst,1));
+ % spread individual VOI colors in the colorcube color palette
+ colors = colors(1:colorStep:end,:);
+
+ for i = 1:size(cst,1)
+ cst{i,5}.visibleColor = colors(i,:);
+ end
+ end
+
+ for s = 1:size(cst,1)
+ handles.VOIPlotFlag(s) = cst{s,5}.Visible;
+
+ [tmpString{s},tmpStyles{s}] = this.getListEntry(cst(s,:));
+ end
+ if this.isInUifigure
+ handles.legendTable.Items = tmpString;
+ for s = 1:numel(tmpStyles)
+ addStyle(handles.legendTable,tmpStyles{s},'Item',s);
+ end
+ else
+ set(handles.legendTable,'String',tmpString);
+ end
+ this.handles = handles;
+ end
+
+ function [item,style] = getListEntry(this,cstElement)
+ clr = cstElement{1,5}.visibleColor;
+
+ if cstElement{1,5}.Visible
+ checkbox = '☑ ';
+ else
+ checkbox = '☐ ';
+ end
+
+ % html is not supported in octave
+ if this.isInUifigure()
+ % calculate text color
+ intensity = clr * [0.299 0.587 0.114]';
+ if intensity > 150/255 %186/255 %https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
+ txtClr = [0 0 0];
+ else
+ txtClr = [1 1 1];
+ end
+
+ item = [checkbox cstElement{1,2}];
+ style = uistyle('BackgroundColor',clr,'FontColor',txtClr);
+ else
+ matRad_cfg = MatRad_Config.instance();
+ switch matRad_cfg.env
+ case 'OCTAVE'
+ item = [checkbox cstElement{1,2}];
+ otherwise
+ hexClr = dec2hex(round(cstElement{1,5}.visibleColor(:)*255),2)';
+ hexClr = ['#';hexClr(:)]';
+ if cstElement{1,5}.Visible
+ item = [''];
+ else
+ item = [''];
+ end
+ end
+ style = [];
+ end
+ end
+ end
+end
+
diff --git a/matRad/gui/widgets/matRad_ViewerOptionsWidget.m b/matRad/gui/widgets/matRad_ViewerOptionsWidget.m
new file mode 100644
index 000000000..3f3ddadb3
--- /dev/null
+++ b/matRad/gui/widgets/matRad_ViewerOptionsWidget.m
@@ -0,0 +1,946 @@
+classdef matRad_ViewerOptionsWidget < matRad_Widget
+ % matRad_ViewerOptionsWidget class to generate GUI widget to set
+ % options for the plan ViewingWidget
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ viewingWidgetHandle = [];
+ colormapLocked = false;
+ windowPresets;
+ end
+
+ methods
+ function this = matRad_ViewerOptionsWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+ handleParent = figure(...
+ 'Units','characters',...
+ 'Position',[170 15 30 30],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad Viewer Options',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','menuViewerOption');
+
+ end
+ this = this@matRad_Widget(handleParent);
+
+ handles = this.handles;
+
+ %Set up the colormap selection box
+ availableColormaps = matRad_getColormap();
+ set(handles.popupmenu_chooseColormap,'String',availableColormaps);
+
+ % setup ct window list
+ % data and values from CERR https://github.com/adityaapte/CERR
+ windowNames = {'Custom','Full','Abd/Med', 'Head', 'Liver', 'Lung', 'Spine', 'Vrt/Bone'};
+ windowCenter = {NaN, NaN, -10, 45, 80, -500, 30, 400};
+ windowWidth = {NaN, NaN, 330, 125, 305, 1500, 300, 1500};
+ windowPresets = cell2struct([windowNames', windowCenter', windowWidth'], {'name', 'center', 'width'},2);
+
+
+ this.windowPresets = windowPresets;
+
+ selectionList = {windowPresets(:).name};
+ set(handles.popupmenu_windowPreset,'String',selectionList(:));
+ set(handles.popupmenu_windowPreset,'Value',1);
+
+ this.handles=handles;
+
+ UpdateButtonState(this,'off');
+ end
+
+ function this = initialize(this)
+ end
+
+ % function viewingWidgetHandle=get.viewingWidgetHandle(this)
+ % viewingWidgetHandle=this.viewingWidgetHandle;
+ % end
+
+ function set.viewingWidgetHandle(this,value)
+ %handles=this.handles;
+ if isa(value,'matRad_ViewingWidget')
+ this.viewingWidgetHandle=value;
+
+ getFromViewingWidget(this);
+
+ else
+ % disable all buttons
+ UpdateButtonState(this,'off');
+ end
+ %this.handles=handles;
+ end
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this)
+ h98 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Create Main Grid layout
+ gridSize = [1 18];
+ elSize = [0.9 0.9];
+ [i,j] = ndgrid(1:gridSize(1),1:gridSize(2));
+ gridPos = arrayfun(@(i,j) computeGridPos(this,[i j],gridSize,elSize),i,j,'UniformOutput',false);
+
+ %Display info about current displayed cube
+ h84 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','Data Info:',...
+ 'TooltipString','Info about the currently displayed distribution',...
+ 'Style','text',...
+ 'HorizontalAlignment','left',...
+ 'Position',gridPos{1},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','DataInfo');
+
+ %Text Minimum Value
+ h84 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','min value:',...
+ 'TooltipString','Minimum value of the currently displayed distribution',...
+ 'Style','text',...
+ 'HorizontalAlignment','left',...
+ 'Position',gridPos{2} .* [1 1 0.5 1],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','MinVal');
+
+ h84 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','',...
+ 'TooltipString','Minimum value of the currently displayed distribution',...
+ 'Style','text',...
+ 'HorizontalAlignment','left',...
+ 'Position',(gridPos{2} + [0.5 0 0 0]) .* [1 1 0.5 1],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtMinVal');
+
+
+ %Text Maximum Value
+ h116 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','max value:',...
+ 'TooltipString','Maximum value of the currently displayed distribution',...
+ 'Style','text',...
+ 'HorizontalAlignment','left',...
+ 'Position',gridPos{3} .* [1 1 0.5 1],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','MaxVal');
+
+ h117 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','',...
+ 'TooltipString','Maximum value of the currently displayed distribution',...
+ 'Style','text',...
+ 'HorizontalAlignment','left',...
+ 'Position',(gridPos{3} + [0.5 0 0 0]) .* [1 1 0.5 1],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtMaxVal');
+
+ %Set IsoDose Levels
+ h85 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','Set IsoDose Levels',...
+ 'TooltipString','Set iso dose levels for displaying the distributions in the GUI',...
+ 'Position',gridPos{4},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)btnSetIsoDoseLevels_Callback(this,hObject,eventdata),...
+ 'Tag','btnSetIsoDoseLevels');
+
+ %Text Colorbar & Map section header
+ txt = sprintf('Choose to display the CT or the plan result &\nchoose a colormap');
+ h84 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','Colorbar & -map:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'HorizontalAlignment','left',...
+ 'Position',gridPos{5},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtColorbarMap');
+
+ %popUp Menu CT and plan result
+ h101 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String',{'CT (HU)','Plan Result'},...
+ 'TooltipString','Choose to display the CT or the plan result',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{6},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)popupmenu_chooseColorData_Callback(this,hObject,eventdata),...
+ 'Tag','popupmenu_chooseColorData');
+
+ %Popup Menu choose colormap
+ h104 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','Choose Colormap...',...
+ 'TooltipString','Choose a colormap for displaying the distributions in the GUI',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{7},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)popupmenu_chooseColormap_Callback(this,hObject,eventdata),...
+ 'Tag','popupmenu_chooseColormap');
+
+ %Text for CT window preset
+ h112 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','CT Window Presets N/A',...
+ 'TooltipString','Choose a customized CT window or a preset one',...
+ 'Style','text',...
+ 'Position',gridPos{8},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text_windowPreset' );
+
+ % Popup for CT window preset
+ h113 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String',{ 'Custom'; 'Full'; 'Abd/Med'; 'Head'; 'Liver'; 'Lung'; 'Spine'; 'Vrt/Bone' },...
+ 'TooltipString','Choose a customized CT window or a preset one',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{9},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)popupmenu_windowPreset_Callback(this,hObject,eventdata),...
+ 'Visible','on',... % Default should be off!
+ 'Tag','popupmenu_windowPreset');
+
+ %Text for CT window center
+ h99 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Window Center:',...
+ 'TooltipString','Set the window center',...
+ 'Style','text',...
+ 'Position',gridPos{10},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text_windowCenter' );
+
+ %Slider for CT window center
+ h102 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'SliderStep',[0.01 0.05],...
+ 'String','slider',...
+ 'TooltipString','Set the window center',...
+ 'Style','slider',...
+ 'Value',0.5,...
+ 'Position',gridPos{11} .* [1 1 0.7 1],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)slider_windowCenter_Callback(this,hObject,eventdata),...
+ 'Tag','slider_windowCenter');
+
+ %Edit CT window center
+ h107 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','0.5',...
+ 'TooltipString','Set the window center',...
+ 'Style','edit',...
+ 'Value',1,...
+ 'Position',gridPos{11} .* [1 1 0.25 1] + [0.7 0 0 0],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)edit_windowCenter_Callback(this,hObject,eventdata),...
+ 'Tag','edit_windowCenter');
+
+ %Text for CT window Width
+ h103 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Window Width:',...
+ 'TooltipString','Set the window width',...
+ 'Style','text',...
+ 'Position',gridPos{12},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text_windowWidth');
+
+ %slider for CT window Width
+ h114 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'SliderStep',[0.01 0.05],...
+ 'String','slider',...
+ 'TooltipString','Set the window width',...
+ 'Style','slider',...
+ 'Value',1,...
+ 'Position',gridPos{13} .* [1 1 0.7 1],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)slider_windowWidth_Callback(this, hObject, eventdata),...
+ 'Tag','slider_windowWidth');
+
+ %Edit for CT window Width
+ h108 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','1.0',...
+ 'TooltipString','Set the window width',...
+ 'Style','edit',...
+ 'Position',gridPos{13} .* [1 1 0.25 1] + [0.7 0 0 0],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)edit_windowWidth_Callback(this,hObject,eventdata),...
+ 'Tag','edit_windowWidth');
+
+
+ %Text for CT window Range
+ h105 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Range:',...
+ 'TooltipString','Set the window range',...
+ 'Style','text',...
+ 'Position',gridPos{14},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text_windowRange' );
+
+ %Edit CT window Range
+ h106 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','0 1',...
+ 'TooltipString','Set the window range',...
+ 'Style','edit',...
+ 'Position',gridPos{15},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'BackgroundColor',[1 1 1],...
+ 'Callback',@(hObject,eventdata)edit_windowRange_Callback(this,hObject,eventdata),...
+ 'Tag','edit_windowRange');
+
+ %Text Dose overlay Opacity
+ h100 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Overlay opacity:',...
+ 'TooltipString','Set the opacity of the displayed distribution',...
+ 'Style','text',...
+ 'Position',gridPos{16},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','textDoseOpacity' );
+
+ % Slider Dose overlay Opacity
+ h109 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'SliderStep',[0.01 0.05],...
+ 'String','slider',...
+ 'TooltipString','Set the opacity of the displayed distribution',...
+ 'Style','slider',...
+ 'Value',0.6,...
+ 'Position',gridPos{17},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)sliderOpacity_Callback(this,hObject,eventdata),...
+ 'Tag','sliderOpacity');
+
+ %LockSettings
+ h115 = uicontrol(...
+ 'Parent',h98,...
+ 'Units','normalized',...
+ 'String','Lock Settings',...
+ 'TooltipString','Lock current viewer settings',...
+ 'Style','checkbox',...
+ 'Position',gridPos{18},...
+ 'Value',this.colormapLocked,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata)checkbox_lockColormap_Callback(this,hObject,eventdata),...
+ 'Tag','checkbox_lockColormap' );
+
+
+ this.createHandles();
+ end
+
+ function this = doUpdate(this,~)
+ try
+ % minVal=num2str(this.viewingWidgetHandle.dispWindow{selectionIndex,2}(1,1));
+ % maxVal=num2str(this.viewingWidgetHandle.dispWindow{selectionIndex,2}(1,2));
+ if isa(this.viewingWidgetHandle,'matRad_ViewingWidget') %...
+ % && (~strcmp(get(this.handles.txtMinVal,'String'),minVal) ...
+ % || ~strcmp(get(this.handles.txtMaxVal,'String'),maxVal)) %% new data is loaded
+ this.getFromViewingWidget();
+ end
+ catch
+ end
+ this.UpdateColormapOptions();
+ end
+
+ end
+
+ methods
+
+ % H101
+ function popupmenu_chooseColorData_Callback(this,hObject, ~)
+ % hObject handle to popupmenu_chooseColorData (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_chooseColorData contents as cell array
+ % contents{get(hObject,'Value')} returns selected item from popupmenu_chooseColorData
+
+ this.viewingWidgetHandle.colorData = hObject.Value;
+ UpdateColormapOptions(this);
+ end
+
+ % H102
+ function slider_windowCenter_Callback(this, hObject, event)
+ % hObject handle to slider_windowCenter (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'Value') returns position of slider
+ % get(hObject,'Min') and get(hObject,'Max') to determine range of slider
+
+ handles = this.handles;
+
+
+ newCenter = get(hObject,'Value');
+ range = get(handles.slider_windowWidth,'Value');
+ selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
+
+ this.viewingWidgetHandle.dispWindow{selectionIndex,1} = [newCenter-range/2 newCenter+range/2];
+
+
+
+ %handles.cBarChanged = true;
+
+ this.handles = handles;
+ %UpdatePlot(handles);
+ UpdateColormapOptions(this);
+ end
+
+ % H 104
+ function popupmenu_chooseColormap_Callback(this,hObject, eventdata)
+ % hObject handle to popupmenu_chooseColormap (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_chooseColormap contents as cell array
+ % contents{get(hObject,'Value')} returns selected item from popupmenu_chooseColormap
+
+ handles = this.handles;
+
+ index = get(hObject,'Value');
+ strings = get(hObject,'String');
+
+ selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
+
+ switch selectionIndex
+ case 1
+ this.viewingWidgetHandle.ctColorMap = strings{index};
+ case 2
+ this.viewingWidgetHandle.doseColorMap = strings{index};
+ otherwise
+ end
+
+ %handles.cBarChanged = true;
+ % for difference maps cutoff level adjusted to show negative
+ % values in cube
+ if strcmp(this.viewingWidgetHandle.doseColorMap,'diffMap') || strcmp(this.viewingWidgetHandle.doseColorMap,'gammaIndex')
+ this.viewingWidgetHandle.CutOffLevel = [];
+ else
+ this.viewingWidgetHandle.CutOffLevel = 0.01; %default value
+ end
+ this.handles = handles;
+ %UpdatePlot(handles);
+ this.UpdateColormapOptions();
+ end
+
+ % H106
+ function edit_windowRange_Callback(this, hObject, eventdata)
+ % hObject handle to edit_windowRange (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'String') returns contents of edit_windowRange as text
+ % str2double(get(hObject,'String')) returns contents of edit_windowRange as a double
+
+ handles = this.handles;
+
+ selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
+
+ vRange = str2num(get(hObject,'String'));
+ % matlab adds a zero in the beginning when text field is changed
+ if numel(vRange) == 3
+ vRange = vRange(vRange~=0);
+ end
+
+ this.viewingWidgetHandle.dispWindow{selectionIndex,1} = sort(vRange);
+
+ this.handles = handles;
+ %UpdatePlot(handles);
+ this.UpdateColormapOptions();
+ end
+
+ % H107
+ function edit_windowCenter_Callback(this, hObject, eventdata)
+ % hObject handle to edit_windowCenter (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'String') returns contents of edit_windowCenter as text
+ % str2double(get(hObject,'String')) returns contents of edit_windowCenter as a double
+
+
+ handles = this.handles;
+
+ newCenter = str2double(get(hObject,'String'));
+ width = get(handles.slider_windowWidth,'Value');
+ selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
+ this.viewingWidgetHandle.dispWindow{selectionIndex,1} = [newCenter-width/2 newCenter+width/2];
+
+ this.handles = handles;
+ % UpdatePlot(handles);
+ UpdateColormapOptions(this);
+ end
+
+ % H108
+ function edit_windowWidth_Callback(this, hObject, eventdata)
+ % hObject handle to edit_windowWidth (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'String') returns contents of edit_windowWidth as text
+ % str2double(get(hObject,'String')) returns contents of edit_windowWidth as a double
+ handles = this.handles;
+
+ newWidth = str2double(get(hObject,'String'));
+ center = get(handles.slider_windowCenter,'Value');
+ selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
+ this.viewingWidgetHandle.dispWindow{selectionIndex,1} = [center-newWidth/2 center+newWidth/2];
+ %handles.cBarChanged = true;
+
+ this.handles = handles;
+ %this.viewingWidgetHandle.UpdatePlot();
+ %UpdatePlot(handles);
+ UpdateColormapOptions(this);
+ end
+
+ % H109
+ function sliderOpacity_Callback(this,hObject, eventdata)
+ % hObject handle to sliderOpacity (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ %handles = this.handles;
+
+ this.viewingWidgetHandle.doseOpacity = get(hObject,'Value');
+
+ %this.handles = handles;
+ %UpdatePlot(handles);
+
+
+ end
+
+ % H113
+ function popupmenu_windowPreset_Callback(this, hObject, event)
+ % hObject handle to popupmenu_windowPreset (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_windowPreset contents as cell array
+ % contents{get(hObject,'Value')} returns selected item from popupmenu_windowPreset
+ handles = this.handles;
+
+ selectionIndexCube = 1; % working on ct only
+ selectionIndexWindow = get(handles.popupmenu_windowPreset,'Value');
+ newCenter = this.windowPresets(selectionIndexWindow).center;
+ newWidth = this.windowPresets(selectionIndexWindow).width;
+
+ this.viewingWidgetHandle.dispWindow{selectionIndexCube,1} = [newCenter - newWidth/2 newCenter + newWidth/2];
+
+ this.handles = handles;
+ %UpdatePlot(handles);
+ UpdateColormapOptions(this);
+ end
+
+ % H114
+ function slider_windowWidth_Callback(this,hObject, eventdata)
+ % hObject handle to slider_windowWidth (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'Value') returns position of slider
+ % get(hObject,'Min') and get(hObject,'Max') to determine range of slider
+
+ handles = this.handles;
+
+ newWidth = get(hObject,'Value');
+ center = get(handles.slider_windowCenter,'Value');
+ selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
+ this.viewingWidgetHandle.dispWindow{selectionIndex,1} = [center-newWidth/2 center+newWidth/2];
+
+ this.handles = handles;
+ %UpdatePlot(handles);
+ UpdateColormapOptions(this);
+ end
+
+ % H115 Callback
+ function checkbox_lockColormap_Callback(this, hObject, ~)
+ % hObject handle to checkbox_lockColormap (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hint: get(hObject,'Value') returns toggle state of checkbox_lockColormap
+
+ handles = this.handles;
+ this.colormapLocked = get(hObject,'Value');
+ if isa(this.viewingWidgetHandle,'matRad_ViewingWidget')
+ this.viewingWidgetHandle.lockColorSettings=this.colormapLocked;
+ end
+ if this.colormapLocked
+ state = 'Off'; %'Inactive';
+ else
+ state = 'On';
+ end
+
+ set(handles.popupmenu_chooseColorData,'Enable',state);
+ set(handles.popupmenu_windowPreset,'Enable',state);
+ set(handles.slider_windowWidth,'Enable',state);
+ set(handles.slider_windowCenter,'Enable',state);
+ set(handles.edit_windowWidth,'Enable',state);
+ set(handles.edit_windowCenter,'Enable',state);
+ set(handles.edit_windowRange,'Enable',state);
+ set(handles.popupmenu_chooseColormap,'Enable',state);
+
+ this.handles = handles;
+ end
+
+ % button: set iso dose levels
+ function btnSetIsoDoseLevels_Callback(this,hObject, eventdata)
+ handles = this.handles;
+ prompt = {['Enter iso dose levels in [Gy]. Enter space-separated numbers, e.g. 1.5 2 3 4.98. Enter 0 to use default values']};
+ if isequal(this.viewingWidgetHandle.IsoDose_Levels,0) || ~isvector(this.viewingWidgetHandle.IsoDose_Levels) || any(~isnumeric(this.viewingWidgetHandle.IsoDose_Levels)) || any(isnan(this.viewingWidgetHandle.IsoDose_Levels))
+ defaultLine = {'1 2 3 '};
+ else
+ if isrow(this.viewingWidgetHandle.IsoDose_Levels)
+ defaultLine = cellstr(num2str(this.viewingWidgetHandle.IsoDose_Levels,'%.2g '));
+ else
+ defaultLine = cellstr(num2str(this.viewingWidgetHandle.IsoDose_Levels','%.2g '));
+ end
+ end
+
+ try
+ Input = inputdlg(prompt,'Set iso dose levels ', [1 70],defaultLine);
+ if ~isempty(Input)
+ this.viewingWidgetHandle.IsoDose_Levels = (sort(str2num(Input{1})));
+ if length(this.viewingWidgetHandle.IsoDose_Levels) == 1 && (this.viewingWidgetHandle.IsoDose_Levels(1) ~= 0)
+ this.viewingWidgetHandle.IsoDose_Levels = [this.viewingWidgetHandle.IsoDose_Levels this.viewingWidgetHandle.IsoDose_Levels];
+ end
+ %handles.IsoDose.NewIsoDoseFlag = true;
+ end
+ catch
+ this.showWarning('Couldnt parse iso dose levels - using default values');
+ this.viewingWidgetHandle.IsoDose_Levels = 0;
+ end
+ this.handles = handles;
+ %handles = updateIsoDoseLineCache(handles);
+ %this.viewingWidgetHandle.NewIsoDoseFlag = false;
+ %UpdatePlot(handles);
+ end
+
+ % Save and update the Colormap
+ function UpdateColormapOptions(this)
+ handles=this.handles;
+ if this.colormapLocked
+ return;
+ end
+
+ %selectionIndex = this.handles.popupmenu_chooseColorData;
+
+ % save the lock state
+ updateLockState = this.viewingWidgetHandle.updateLock;
+ %colorSettingLockState = this.viewingWidgetHandle.lockColorSettings;
+ this.viewingWidgetHandle.updateLock = true;
+ %this.viewingWidgetHandle.lockColorSettings = true;
+
+
+ cMapOptionsSelectList = {}; %get(handles.popupmenu_chooseColorData,'String');
+ %Set up the colordata selection box
+ if evalin('base','exist(''ct'')')
+
+ ct = evalin('base','ct');
+
+ if isfield(ct, 'cubeHU')
+ cMapOptionsSelectList{end+1} = 'CT (HU)';
+ set(handles.popupmenu_windowPreset,'Visible','on');
+ set(handles.text_windowPreset,'String','CT Window Preset');
+ else
+ cMapOptionsSelectList{end+1} = 'CT (ED)';
+ set(handles.popupmenu_windowPreset,'Visible','off');
+ set(handles.text_windowPreset,'String','CT Window Presets N/A');
+ end
+
+
+ if evalin('base','exist(''resultGUI'')')
+ cMapOptionsSelectList{end+1} = 'Plan result';
+ elseif this.viewingWidgetHandle.colorData>1
+ this.viewingWidgetHandle.colorData = 1;
+ end
+ else %no data is loaded
+ %disable all buttons
+ UpdateButtonState(this,'off');
+ cMapOptionsSelectList = {'No Data'};
+ end
+
+
+ set(handles.popupmenu_chooseColorData,'String',cMapOptionsSelectList)
+
+ selectionIndex=this.viewingWidgetHandle.colorData;
+ set(handles.popupmenu_chooseColorData,'Value',selectionIndex);
+
+ %selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
+
+ if ~isempty(this.viewingWidgetHandle.dispWindow{selectionIndex,2})
+ set(handles.txtMinVal,'String', num2str(this.viewingWidgetHandle.dispWindow{selectionIndex,2}(1,1)));
+ set(handles.txtMaxVal,'String', num2str(this.viewingWidgetHandle.dispWindow{selectionIndex,2}(1,2)));
+ end
+ cMapStrings = get(handles.popupmenu_chooseColormap,'String');
+
+
+ try
+ if selectionIndex == 1
+ ct = evalin('base','ct');
+ currentMap = this.viewingWidgetHandle.ctColorMap;
+ window = this.viewingWidgetHandle.dispWindow{selectionIndex,1};
+ if isfield(ct, 'cubeHU')
+ minMax = [min(ct.cubeHU{1}(:)) max(ct.cubeHU{1}(:))];
+ else
+ minMax = [min(ct.cube{1}(:)) max(ct.cube{1}(:))];
+ end
+ % adjust value for custom window to current
+ this.windowPresets(1).width = max(window) - min(window);
+ this.windowPresets(1).center = mean(window);
+ % update full window information
+ this.windowPresets(2).width = minMax(2) - minMax(1);
+ this.windowPresets(2).center = mean(minMax);
+ elseif selectionIndex == 2
+ result = evalin('base','resultGUI');
+ dose = result.(this.viewingWidgetHandle.SelectedDisplayOption);
+ currentMap = this.viewingWidgetHandle.doseColorMap;
+ minMax = [min(dose(:)) max(dose(:))];
+ window = this.viewingWidgetHandle.dispWindow{selectionIndex,1};
+ if isempty(window)
+ window = minMax;
+ end
+ else
+ window = [0 1];
+ minMax = window;
+ currentMap = 'bone';
+ end
+ catch
+ window = [0 1];
+ minMax = window;
+ currentMap = 'bone';
+ end
+
+ valueRange = minMax(2) - minMax(1);
+
+ windowWidth = window(2) - window(1);
+ windowCenter = mean(window);
+
+ %This are some arbritrary settings to configure the sliders
+ sliderCenterMinMax = [minMax(1)-valueRange/2 minMax(2)+valueRange/2];
+ sliderWidthMinMax = [0 valueRange*2];
+
+ %if we have selected a value outside this range, we adapt the slider
+ %windows
+ if windowCenter < sliderCenterMinMax(1)
+ sliderCenterMinMax(1) = windowCenter;
+ end
+ if windowCenter > sliderCenterMinMax(2)
+ sliderCenterMinMax(2) = windowCenter;
+ end
+ if windowWidth < sliderWidthMinMax(1)
+ sliderWidthMinMax(1) = windowWidth;
+ end
+ if windowWidth > sliderWidthMinMax(2)
+ sliderWidthMinMax(2) = windowWidth;
+ end
+
+ set(handles.edit_windowCenter,'String',num2str(windowCenter,3));
+ set(handles.edit_windowWidth,'String',num2str(windowWidth,3));
+ set(handles.edit_windowRange,'String',num2str(window,4));
+ set(handles.slider_windowCenter,'Min',sliderCenterMinMax(1),'Max',sliderCenterMinMax(2),'Value',windowCenter);
+ set(handles.slider_windowWidth,'Min',sliderWidthMinMax(1),'Max',sliderWidthMinMax(2),'Value',windowWidth);
+
+ cMapPopupIndex = find(strcmp(currentMap,cMapStrings));
+ set(handles.popupmenu_chooseColormap,'Value',cMapPopupIndex);
+ this.viewingWidgetHandle.updateLock=updateLockState;
+ this.handles=handles;
+ end
+ %Update button enable/disables
+ function UpdateButtonState(this,state)
+ % state is on or off
+ handles=this.handles;
+
+ set(handles.checkbox_lockColormap,'Value',this.colormapLocked);
+ if this.colormapLocked
+ state='off';
+ end
+
+ %set(handles.checkbox_lockColormap,'Enable',state);
+ set(handles.popupmenu_chooseColorData,'Enable',state);
+ set(handles.popupmenu_windowPreset,'Enable',state);
+ set(handles.slider_windowWidth,'Enable',state);
+ set(handles.slider_windowCenter,'Enable',state);
+ set(handles.edit_windowWidth,'Enable',state);
+ set(handles.edit_windowCenter,'Enable',state);
+ set(handles.edit_windowRange,'Enable',state);
+ set(handles.popupmenu_chooseColormap,'Enable',state);
+ set(handles.sliderOpacity,'Enable',state);
+ set(handles.btnSetIsoDoseLevels,'Enable',state);
+
+ this.handles=handles;
+ end
+
+ %get Viewing settings from the Viewer Widget
+ function getFromViewingWidget(this)
+ if evalin('base','exist(''ct'')')
+ % enable all buttons
+ UpdateButtonState(this,'on');
+ else
+ % no data loaded, disable all buttons
+ UpdateButtonState(this,'off');
+ end
+
+ handles=this.handles;
+
+ % get the default value from the viewer widget
+ set(handles.popupmenu_chooseColorData,'Value',this.viewingWidgetHandle.colorData);
+
+ availableColormaps = matRad_getColormap();
+ currentCtMapIndex = find(strcmp(availableColormaps,this.viewingWidgetHandle.ctColorMap));
+ currentDoseMapIndex = find(strcmp(availableColormaps,this.viewingWidgetHandle.doseColorMap));
+
+ if evalin('base','exist(''resultGUI'')') % state 3
+ set(handles.popupmenu_chooseColormap,'Value',currentDoseMapIndex);
+ else
+ set(handles.popupmenu_chooseColormap,'Value',currentCtMapIndex);
+ end
+ this.handles=handles;
+ end
+ end
+
+end
+
diff --git a/matRad/gui/widgets/matRad_ViewingWidget.m b/matRad/gui/widgets/matRad_ViewingWidget.m
new file mode 100644
index 000000000..de7f68ae0
--- /dev/null
+++ b/matRad/gui/widgets/matRad_ViewingWidget.m
@@ -0,0 +1,1347 @@
+classdef matRad_ViewingWidget < matRad_Widget
+ % matRad_ViewingWidget class to generate GUI widget to display plan
+ % dose distributions and ct
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ plane = 3;
+ slice = 1;
+ selectedBeam = 1;
+ numOfBeams=1;
+ profileOffset=0;
+ OffsetSliderStep;
+ OffsetMinMax;
+ typeOfPlot = 1;
+ colorData;
+ doseColorMap = 'jet';
+ ctColorMap = 'bone';
+ cMapSize = 64;
+ ctScen = 1;
+ plotCT = true;
+ plotContour = true;
+ plotIsoCenter = true;
+ plotPlan = false;
+ plotDose = true;
+ plotIsoDoseLines = true;
+ plotIsoDoseLinesLabels = false;
+ plotLegend = false;
+ plotColorBar = true;
+ ProfileType = 'lateral';
+ SelectedDisplayOption = 'physicalDose';
+ SelectedDisplayAllOptions = '';
+ CutOffLevel = 0.01;
+ dispWindow = cell(3,2);
+ doseOpacity = 0.6;
+ IsoDose_Levels= [];
+ NewIsoDoseFlag = true;
+ cBarHandle;
+ dcmHandle;
+ panHandle;
+ zoomHandle;
+ legendHandle;
+ scrollHandle;
+ lockColorSettings = false;
+ %plotlegend=false;
+ end
+
+ properties (SetAccess=private)
+
+ IsoDose_Contours; %only updated from within this class
+ VOIPlotFlag;
+ DispInfo;
+ AxesHandlesVOI;
+ cst;
+ vIsoCenter;
+ sliceContourLegend;
+ end
+
+ properties (SetAccess = protected)
+ initialized = false;
+ end
+
+ events
+ plotUpdated
+ end
+
+ methods
+ function this = matRad_ViewingWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+
+ if nargin < 1
+ handleParent = figure(...
+ 'Units','normalized',...
+ 'Position',[0.3 0.2 0.4 0.6],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,... 'CloseRequestFcn',@(hObject,eventdata) figure1_CloseRequestFcn(this,hObject,eventdata),...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad Viewing',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1');
+
+ end
+
+ this = this@matRad_Widget(handleParent);
+
+ matRad_cfg = MatRad_Config.instance();
+
+ if nargin < 1
+ % create the handle objects if there's no parent
+ this.scrollHandle = this.widgetHandle;
+
+ % only available in MATLAB
+ if matRad_cfg.isMatlab
+ this.dcmHandle = datacursormode(this.widgetHandle);
+ this.panHandle = pan(this.widgetHandle);
+ this.zoomHandle = zoom(this.widgetHandle);
+ end
+ end
+ this.initialize();
+ end
+
+ function initialize(this)
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function notifyPlotUpdated(obj)
+ % handle environment
+ matRad_cfg = MatRad_Config.instance();
+ switch matRad_cfg.env
+ case 'MATLAB'
+ notify(obj, 'plotUpdated');
+ case 'OCTAVE'
+ matRad_notifyOctave(obj, 'plotUpdated');
+ end
+
+ end
+
+ %% SET FUNCTIONS
+ function set.plane(this,value)
+ this.plane=value;
+ this.update();
+ end
+
+ function set.slice(this,value)
+ % project to allowed set (between min and max value)
+ newSlice = max(value,1);
+ if evalin('base','exist(''ct'')')
+ ct=evalin('base','ct');
+ newSlice = min(newSlice,ct.cubeDim(this.plane));
+ else
+ newSlice=1;
+ end
+
+ this.slice=newSlice;
+ this.update();
+ end
+
+ function set.selectedBeam(this,value)
+ this.selectedBeam=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.numOfBeams(this,value)
+ this.numOfBeams=value;
+ this.update();
+ end
+
+ function set.profileOffset(this,value)
+ this.profileOffset=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.OffsetSliderStep(this,value)
+ this.OffsetSliderStep=value;
+ this.update();
+ end
+
+ function set.OffsetMinMax(this,value)
+ this.OffsetMinMax=value;
+ this.update();
+ end
+
+
+ function set.typeOfPlot(this,value)
+ this.typeOfPlot=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ cla(this.handles.axesFig,'reset');
+ this.update(evt);
+ end
+
+ function set.colorData(this,value)
+ this.colorData=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.doseColorMap(this,value)
+ this.doseColorMap=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.ctColorMap(this,value)
+ this.ctColorMap=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.cMapSize(this,value)
+ this.cMapSize=value;
+ this.update();
+ end
+
+ function set.ctScen(this,value)
+ this.ctScen=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotCT(this,value)
+ this.plotCT=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotContour(this,value)
+ this.plotContour=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotIsoCenter(this,value)
+ this.plotIsoCenter=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotPlan(this,value)
+ this.plotPlan=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotDose(this,value)
+ this.plotDose=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotIsoDoseLines(this,value)
+ this.plotIsoDoseLines=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotIsoDoseLinesLabels(this,value)
+ this.plotIsoDoseLinesLabels=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotLegend(this,value)
+ this.plotLegend=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.plotColorBar(this,value)
+ this.plotColorBar=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.ProfileType(this,value)
+ this.ProfileType=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.SelectedDisplayOption(this,value)
+ this.SelectedDisplayOption=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.SelectedDisplayAllOptions(this,value)
+ this.SelectedDisplayAllOptions=value;
+ this.update();
+ end
+
+
+ function set.CutOffLevel(this,value)
+ this.CutOffLevel=value;
+ this.update();
+ end
+
+ function set.dispWindow(this,value)
+ this.dispWindow=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.doseOpacity(this,value)
+ this.doseOpacity=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.IsoDose_Levels(this,value)
+ this.IsoDose_Levels=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.IsoDose_Contours(this,value)
+ this.IsoDose_Contours=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.NewIsoDoseFlag(this,value)
+ this.NewIsoDoseFlag=value;
+ evt = matRad_WorkspaceChangedEvent('image_display');
+ this.update(evt);
+ end
+
+ function set.dcmHandle(this,value)
+ this.dcmHandle=value;
+ set(this.dcmHandle,'DisplayStyle','window');
+ %Add the callback for the datacursor display
+ set(this.dcmHandle,'UpdateFcn',@(hObject, eventdata)dataCursorUpdateFunction(this, hObject, eventdata));
+ end
+
+ function set.scrollHandle(this,value)
+ this.scrollHandle=value;
+ % set callback for scroll wheel function
+ set(this.scrollHandle,'WindowScrollWheelFcn',@(src,event)matRadScrollWheelFcn(this,src,event));
+ end
+ end
+ %%
+ methods(Access = protected)
+ function this = createLayout(this)
+ %Viewer Widget
+ h88 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ h89 = axes(...
+ 'Parent',h88,...
+ 'XTick',[0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1],...
+ 'XTickLabel',{ '0'; '0.1'; '0.2'; '0.3'; '0.4'; '0.5'; '0.6'; '0.7'; '0.8'; '0.9'; '1' },...
+ 'XColor',matRad_cfg.gui.textColor,...
+ 'YTick',[0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1],...
+ 'YTickLabel',{ '0'; '0.1'; '0.2'; '0.3'; '0.4'; '0.5'; '0.6'; '0.7'; '0.8'; '0.9'; '1' },...
+ 'YColor',matRad_cfg.gui.textColor,...
+ 'ZColor',matRad_cfg.gui.textColor,...
+ 'Units','normalized',...
+ 'Position',[0.0718390804597701 0.0654391371340524 0.902298850574712 0.899121725731895],...
+ 'Color',matRad_cfg.gui.elementColor,...
+ 'Box','on',...
+ 'BoxStyle','full',...
+ 'Tag','axesFig');
+
+ %Title
+ h90 = get(h89,'title');
+
+ set(h90, ...
+ 'Parent',h89,...
+ 'Visible','on',...
+ 'Units','data',...
+ 'Position',[0.500000554441759 1.00453467465753 0.5],...
+ 'PositionMode','auto', ...
+ 'Margin',2,...
+ 'Clipping','off',...
+ 'FontUnits','points',...
+ 'Color',[0 0 0],...
+ 'BackgroundColor','none',...
+ 'EdgeColor','none',...
+ 'Interpreter','tex',...
+ 'Rotation',0,...
+ 'RotationMode','auto',...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontAngle','normal',...
+ 'FontWeight','normal',...
+ 'HorizontalAlignment','center',...
+ 'HorizontalAlignmentMode','auto',...
+ 'VerticalAlignment','bottom',...
+ 'VerticalAlignmentMode','auto', ...
+ 'LineStyle','-',...
+ 'LineWidth',0.5,...
+ 'HandleVisibility','off',...
+ 'BusyAction','queue',...
+ 'Interruptible','on',...
+ 'HitTest','on');
+
+ %X Label
+ h91 = get(h89,'xlabel');
+
+ set(h91,...
+ 'Parent',h89,...
+ 'Units','data',...
+ 'FontUnits','points',...
+ 'Color',matRad_cfg.gui.textColor,...
+ 'Position',[0.500000476837158 -0.0373767115122652 0],...
+ 'PositionMode','auto',...
+ 'Interpreter','tex',...
+ 'Rotation',0,...
+ 'RotationMode','auto',...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontAngle','normal',...
+ 'FontWeight','normal',...
+ 'HorizontalAlignment','center',...
+ 'HorizontalAlignmentMode','auto',...
+ 'VerticalAlignment','top',...
+ 'VerticalAlignmentMode','auto',...
+ 'EdgeColor','none',...
+ 'LineStyle','-',...
+ 'LineWidth',0.5,...
+ 'BackgroundColor','none',...
+ 'Margin',3,...
+ 'Clipping','off',...
+ 'Visible','on',...
+ 'HandleVisibility','off',...
+ 'BusyAction','queue',...
+ 'Interruptible','on',...
+ 'HitTest','on');
+
+ %Y label
+ h92 = get(h89,'ylabel');
+
+ set(h92,...
+ 'Parent',h89,...
+ 'Units','data',...
+ 'FontUnits','points',...
+ 'Color',matRad_cfg.gui.textColor,...
+ 'Position',[-0.0474647368237942 0.500000476837158 0],...
+ 'PositionMode','auto',...
+ 'Interpreter','tex',...
+ 'Rotation',90,...
+ 'RotationMode','auto',...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontAngle','normal',...
+ 'FontWeight','normal',...
+ 'HorizontalAlignment','center',...
+ 'HorizontalAlignmentMode','auto',...
+ 'VerticalAlignment','bottom',...
+ 'VerticalAlignmentMode','auto',...
+ 'EdgeColor','none',...
+ 'LineStyle','-',...
+ 'LineWidth',0.5,...
+ 'BackgroundColor','none',...
+ 'Margin',3,...
+ 'Clipping','off',...
+ 'Visible','on',...
+ 'HandleVisibility','off',...
+ 'BusyAction','queue',...
+ 'Interruptible','on',...
+ 'HitTest','on');
+
+ %Z label
+ h93 = get(h89,'zlabel');
+
+ set(h93,...
+ 'Parent',h89,...
+ 'Units','data',...
+ 'FontUnits','points',...
+ 'Color',matRad_cfg.gui.textColor,...
+ 'Position',[0 0 0],...
+ 'PositionMode','auto',...
+ 'Interpreter','tex',...
+ 'Rotation',0,...
+ 'RotationMode','auto',...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontAngle','normal',...
+ 'FontWeight','normal',...
+ 'HorizontalAlignment','left',...
+ 'HorizontalAlignmentMode','auto',...
+ 'VerticalAlignment','middle',...
+ 'VerticalAlignmentMode','auto',...
+ 'EdgeColor','none',...
+ 'LineStyle','-',...
+ 'LineWidth',0.5,...
+ 'BackgroundColor','none',...
+ 'Margin',3,...
+ 'Clipping','off',...
+ 'Visible','off',...
+ 'HandleVisibility','off',...
+ 'BusyAction','queue',...
+ 'Interruptible','on',...
+ 'HitTest','on');
+
+ this.createHandles();
+
+ end
+
+ function this=doUpdate(this,evt)
+ if ~this.initialized
+ this.initValues();
+ this.initialized = true;
+ end
+
+ if ~this.updateLock
+
+ %doUpdate = false;
+ if nargin == 2
+ %At pln changes and at cst/cst (for Isocenter and new settings)
+ %we need to update
+ if this.checkUpdateNecessary({'ct','cst'},evt)
+ this.initValues();
+ end
+ this.updateValues();
+ %doUpdate = this.checkUpdateNecessary({'pln_display','ct','cst','resultGUI','image_display'},evt);
+ if this.checkUpdateNecessary({'resultGUI','image_display'},evt)
+ this.updateIsoDoseLineCache();
+ end
+ else
+ this.updateValues();
+ end
+
+ this.UpdatePlot();
+ end
+
+ end
+ end
+
+ methods
+ function UpdatePlot(this)
+ if this.updateLock
+ return
+ end
+
+ matRad_cfg = MatRad_Config.instance();
+
+ handles = this.handles;
+
+ %profile on;
+ axes(handles.axesFig);
+
+ % this is necessary to prevent multiple callbacks of update plot drawing on
+ % top of each other in matlab <2014
+ drawnow;
+
+ defaultFontSize = matRad_cfg.gui.fontSize;
+ currAxes = axis(handles.axesFig);
+ axesHandlesVOI = cell(0);
+
+ axesHandlesCT_Dose = cell(0);
+ axesHandlesIsoDose = cell(0);
+
+ if evalin('base','exist(''ct'')')
+ ct = evalin('base','ct');
+ else
+ cla(handles.axesFig);
+ return
+ end
+
+ if evalin('base','exist(''pln'')')
+ pln = evalin('base','pln');
+ else
+ pln = [];
+ end
+
+ %% If resultGUI exists, then an optimization has been performed
+ if evalin('base','exist(''resultGUI'')')
+ result = evalin('base','resultGUI');
+ end
+
+ %% set and get required variables
+ %plane = get(handles.popupPlane,'Value');
+ %slice = round(get(handles.sliderSlice,'Value'));
+ hold(handles.axesFig,'on');
+ if this.typeOfPlot==1 %get(handles.popupTypeOfPlot,'Value')==1
+ set(handles.axesFig,'YDir','Reverse');
+ end
+
+ selectIx = this.colorData; %get(handles.popupmenu_chooseColorData,'Value');
+
+ cla(handles.axesFig);
+ %% plot ct - if a ct cube is available and type of plot is set to 1 and not 2; 1 indicate cube plotting and 2 profile plotting
+ if ~isempty(ct) && this.typeOfPlot==1
+
+ if selectIx == 2
+ ctIx = 1;
+ else
+ ctIx = selectIx;
+ end
+ if isfield(ct, 'cubeHU')
+ plotCtCube = ct.cubeHU;
+ else
+ plotCtCube = ct.cube;
+ end
+ ctMap = matRad_getColormap(this.ctColorMap,this.cMapSize);
+
+% if isempty(this.dispWindow{ctIx,2})
+% this.dispWindow{ctIx,2} = [min(reshape([ct.cubeHU{:}],[],1)) max(reshape([ct.cubeHU{:}],[],1))];
+% end
+
+ if this.plotCT %get(handles.radiobtnCT,'Value')
+ [axesHandlesCT_Dose{end+1},~,~] = matRad_plotCtSlice(handles.axesFig,plotCtCube,this.ctScen,this.plane,this.slice,ctMap,this.dispWindow{ctIx,1});
+
+ % plot colorbar? If 1 the user asked for the CT.
+ % not available in octave
+ if strcmp(matRad_cfg.env,'MATLAB') && ~isempty(this.colorData) && this.colorData == 1
+ %Plot the colorbar
+ this.cBarHandle = matRad_plotColorbar(handles.axesFig,ctMap,this.dispWindow{ctIx,1},'FontSize',defaultFontSize,'Color',matRad_cfg.gui.textColor);
+
+ if this.plotColorBar
+ set(this.cBarHandle,'Visible','on')
+ else
+ set(this.cBarHandle,'Visible','off')
+ end
+
+ %adjust lables
+ if isfield(ct,'cubeHU')
+ set(get(this.cBarHandle,'ylabel'),'String', 'Hounsfield Units','fontsize',defaultFontSize);
+ else
+ set(get(this.cBarHandle,'ylabel'),'String', 'Electron Density','fontsize',defaultFontSize);
+ end
+ % do not interprete as tex syntax
+ set(get(this.cBarHandle,'ylabel'),'interpreter','none');
+ end
+ end
+ end
+
+ %% plot dose cube
+ if this.typeOfPlot== 1 && exist('result','var') % handles.State >= 1 &&
+ doseMap = matRad_getColormap(this.doseColorMap,this.cMapSize);
+ doseIx = 2;
+
+ dose = result.(this.SelectedDisplayOption);
+
+ % dose colorwash
+ if ~isempty(dose) && ~isvector(dose)
+
+ if this.plotDose
+ [doseHandle,~,~] = matRad_plotDoseSlice(handles.axesFig,dose,this.plane,this.slice,this.CutOffLevel,this.doseOpacity,doseMap,this.dispWindow{doseIx,1});
+ axesHandlesCT_Dose{end+1} = doseHandle;
+ end
+
+ % plot colorbar
+ if matRad_cfg.isMatlab && ~isempty(this.colorData) && this.colorData > 1
+ %Plot the colorbar
+ this.cBarHandle = matRad_plotColorbar(handles.axesFig,doseMap,this.dispWindow{selectIx,1},'fontsize',defaultFontSize,'Color',matRad_cfg.gui.textColor);
+
+ if this.plotColorBar
+ set(this.cBarHandle,'Visible','on')
+ else
+ set(this.cBarHandle,'Visible','off')
+ end
+ %adjust lables
+ Idx = find(strcmp(this.SelectedDisplayOption,this.DispInfo(:,1)));
+ set(get(this.cBarHandle,'ylabel'),'String', [this.DispInfo{Idx,1} ' ' this.DispInfo{Idx,3} ],'fontsize',defaultFontSize);
+ % do not interprete as tex syntax
+ set(get(this.cBarHandle,'ylabel'),'interpreter','none');
+ end
+ end
+
+
+ %% plot iso dose lines
+ if this.plotIsoDoseLines
+ plotLabels = this.plotIsoDoseLinesLabels;
+ axesHandlesIsoDose = matRad_plotIsoDoseLines(handles.axesFig,dose,this.IsoDose_Contours,this.IsoDose_Levels,plotLabels,this.plane,this.slice,doseMap,this.dispWindow{doseIx,1},'LineWidth',1.5);
+ end
+ end
+
+ %% plot VOIs
+ if this.plotContour && this.typeOfPlot==1 && exist('ct','var') %&& get(handles.radiobtnContour,'Value') && handles.State>0
+ [AxVOI, this.sliceContourLegend] = matRad_plotVoiContourSlice(handles.axesFig,this.cst,ct,this.ctScen,this.VOIPlotFlag,this.plane,this.slice,[],'LineWidth',2);
+ axesHandlesVOI = [axesHandlesVOI AxVOI];
+ end
+ this.AxesHandlesVOI=axesHandlesVOI;
+
+ %% Set axis labels and plot iso center
+ matRad_plotAxisLabels(handles.axesFig,ct,this.plane,this.slice,defaultFontSize);
+ set(get(handles.axesFig,'Title'),'Color',matRad_cfg.gui.textColor);
+
+ if this.plotIsoCenter && this.typeOfPlot == 1 && ~isempty(pln) %get(handles.radioBtnIsoCenter,'Value') == 1
+ hIsoCenterCross = matRad_plotIsoCenterMarker(handles.axesFig,pln,ct,this.plane,this.slice);
+ end
+
+ if this.plotPlan && ~isempty(pln) %get(handles.radiobtnPlan,'value') == 1
+ matRad_plotProjectedGantryAngles(handles.axesFig,pln,ct,this.plane);
+ end
+
+ %set axis ratio
+ ratios = [1/ct.resolution.x 1/ct.resolution.y 1/ct.resolution.z];
+ set(handles.axesFig,'DataAspectRatioMode','manual');
+ if this.plane == 1
+ res = [ratios(3) ratios(1)]./max([ratios(3) ratios(1)]);
+ set(handles.axesFig,'DataAspectRatio',[res 1])
+ elseif this.plane == 2 % sagittal plane
+ res = [ratios(3) ratios(2)]./max([ratios(3) ratios(2)]);
+ set(handles.axesFig,'DataAspectRatio',[res 1])
+ elseif this.plane == 3 % Axial plane
+ res = [ratios(1) ratios(2)]./max([ratios(1) ratios(2)]);
+ set(handles.axesFig,'DataAspectRatio',[res 1])
+ end
+
+ axis(handles.axesFig,'tight');
+
+
+ %% profile plot
+ if this.typeOfPlot == 2 && exist('result','var')
+ % set SAD
+ fileName = [pln.radiationMode '_' pln.machine];
+ try
+ load(fileName);
+ SAD = machine.meta.SAD;
+ catch
+ this.showError(['Could not find the following machine file: ' fileName ]);
+ end
+
+ % clear view and initialize some values
+ cla(handles.axesFig,'reset')
+ set(handles.axesFig,'YDir','normal','Color',matRad_cfg.gui.elementColor,'XColor',matRad_cfg.gui.textColor);
+ hold(handles.axesFig,'on');
+ tmpColor = rgb2hsv(matRad_cfg.gui.elementColor);
+ if tmpColor(3) > 0.5
+ tmpColor = 'black';
+ else
+ tmpColor = 'white';
+ end
+ ylabel(['{\color{' tmpColor '}dose [Gy]}']);
+ cColor={tmpColor,'green','magenta','cyan','yellow','red','blue'};
+
+ % Rotate the system into the beam.
+ % passive rotation & row vector multiplication & inverted rotation requires triple matrix transpose
+ rotMat_system_T = transpose(matRad_getRotationMatrix(pln.propStf.gantryAngles(this.selectedBeam),pln.propStf.couchAngles(this.selectedBeam)));
+
+ if strcmp(this.ProfileType,'longitudinal')
+ sourcePointBEV = [this.profileOffset -SAD 0];
+ targetPointBEV = [this.profileOffset SAD 0];
+ elseif strcmp(this.ProfileType,'lateral')
+ sourcePointBEV = [-SAD this.profileOffset 0];
+ targetPointBEV = [ SAD this.profileOffset 0];
+ end
+
+ rotSourcePointBEV = sourcePointBEV * rotMat_system_T;
+ rotTargetPointBEV = targetPointBEV * rotMat_system_T;
+
+ % perform raytracing on the central axis of the selected beam, use unit
+ % electron density for plotting against the geometrical depth
+ cubeIsoCenter = matRad_world2cubeCoords(pln.propStf.isoCenter(this.selectedBeam,:),ct);
+ [~,l,rho,~,ix] = matRad_siddonRayTracer(cubeIsoCenter,ct.resolution,rotSourcePointBEV,rotTargetPointBEV,{0*ct.cubeHU{1}+1});
+ d = [0 l .* rho{1}];
+ % Calculate accumulated d sum.
+ vX = cumsum(d(1:end-1));
+
+ % plot physical dose
+ %Content =this.SelectedDisplayOption; %get(this.popupDisplayOption,'String');
+ SelectedCube = this.SelectedDisplayOption; %Content{get(this.popupDisplayOption,'Value')};
+ if sum(strcmp(SelectedCube,{'physicalDose','effect','RBExD','alpha','beta','RBE','BED'})) > 0
+ Suffix = '';
+ else
+ Idx = find(SelectedCube == '_');
+ Suffix = SelectedCube(Idx:end);
+ end
+
+ mPhysDose = result.(['physicalDose' Suffix]);
+ PlotHandles{1} = plot(handles.axesFig,vX,mPhysDose(ix),'color',cColor{1,1},'LineWidth',3); hold(handles.axesFig,'on');
+ PlotHandles{1,2} ='physicalDose';
+ ylabel(handles.axesFig,'dose in [Gy]');
+ set(handles.axesFig,'FontSize',defaultFontSize);
+
+ % plot counter
+ Cnt=2;
+
+ rightAx = [];
+
+ if isfield(result,['RBE' Suffix])
+
+ %disbale specific plots
+ %this.DispInfo{6,2}=0;
+ %this.DispInfo{5,2}=0;
+ %this.DispInfo{2,2}=0;
+
+ % generate two lines for ylabel
+ StringYLabel1 = ['\fontsize{8}{\color{red}RBE x dose [Gy(RBE)] \color{' tmpColor '}dose [Gy] '];
+ StringYLabel2 = '';
+ for i=1:1:size(this.DispInfo,1)
+ if this.DispInfo{i,2} && sum(strcmp(this.DispInfo{i,1},{['effect' Suffix],['alpha' Suffix],['beta' Suffix]})) > 0
+ %physicalDose is already plotted and RBExD vs RBE is plotted later with plotyy
+ if ~strcmp(this.DispInfo{i,1},['RBExD' Suffix]) &&...
+ ~strcmp(this.DispInfo{i,1},['RBE' Suffix]) && ...
+ ~strcmp(this.DispInfo{i,1},['physicalDose' Suffix])
+
+ mCube = result.([this.DispInfo{i,1}]);
+ PlotHandles{Cnt,1} = plot(handles.axesFig,vX,mCube(ix),'color',cColor{1,Cnt},'LineWidth',3); hold(handles.axesFig,'on');
+ PlotHandles{Cnt,2} = this.DispInfo{i,1};
+ StringYLabel2 = [StringYLabel2 ' \color{' cColor{1,Cnt} '}' this.DispInfo{i,1} ' [' this.DispInfo{i,3} ']'];
+ Cnt = Cnt+1;
+ end
+ end
+ end
+ StringYLabel2 = [StringYLabel2 '}'];
+ % always plot RBExD against RBE
+ mRBExDose = result.(['RBExD' Suffix]);
+ vBED = mRBExDose(ix);
+ mRBE = result.(['RBE' Suffix]);
+ vRBE = mRBE(ix);
+
+ % plot biological dose against RBE
+ [ax, PlotHandles{Cnt,1}, PlotHandles{Cnt+1,1}]=plotyy(handles.axesFig,vX,vBED,vX,vRBE,'plot');
+ hold(ax(2),'on');
+ PlotHandles{Cnt,2}='RBExD';
+ PlotHandles{Cnt+1,2}='RBE';
+
+ % set plotyy properties
+ set(get(ax(2),'Ylabel'),'String','RBE [1]','FontSize',defaultFontSize);
+
+ ylabel({StringYLabel1;StringYLabel2})
+ set(PlotHandles{Cnt,1},'Linewidth',4,'color','r');
+ set(PlotHandles{Cnt+1,1},'Linewidth',3,'color','b');
+ set(ax(1),'ycolor','r')
+ set(ax(2),'ycolor','b')
+ set(ax,'FontSize',defaultFontSize);
+ Cnt=Cnt+2;
+ rightAx = ax(2);
+ end
+
+ % asses target coordinates
+ tmpPrior = intmax;
+ tmpSize = 0;
+ for i=1:size(this.cst,1)
+ if strcmp(this.cst{i,3},'TARGET') && tmpPrior >= this.cst{i,5}.Priority && tmpSize= 1
+% plane = get(handles.popupPlane,'Value');
+% slice = round(get(handles.sliderSlice,'Value'));
+
+ %Get the CT values
+ ct = evalin('base','ct');
+
+ %We differentiate between pos and ix, since the user may put
+ %the datatip on an isoline which returns a continous position
+ cubePos = zeros(1,3);
+ cubePos(this.plane) = this.slice;
+ cubePos(1:end ~= this.plane) = fliplr(pos);
+ cubeIx = round(cubePos);
+ vCubeIdx = [cubeIx(2),cubeIx(1),cubeIx(3)];
+ %Here comes the index permutation stuff
+ %Cube Index
+ cursorText{end+1,1} = ['Cube Index: ' mat2str(vCubeIdx)];
+ %Space Coordinates
+ coords = zeros(1,3);
+ coords(1) = ct.y(cubePos(2));
+ coords(2) = ct.x(cubePos(1));
+ coords(3) = ct.z(cubePos(3));
+ cursorText{end+1,1} = ['Space Coordinates: ' mat2str(coords,5) ' mm'];
+
+ ctVal = ct.cubeHU{1}(cubeIx(1),cubeIx(2),cubeIx(3));
+ cursorText{end+1,1} = ['HU Value: ' num2str(ctVal,3)];
+ end
+ catch
+ cursorText{end+1,1} = 'Error while retreiving CT Data!';
+ end
+
+
+ %Add dose information if available
+ if evalin('base','exist(''resultGUI'')') %handles.State == 3
+ %get result structure
+ result = evalin('base','resultGUI');
+
+ %get all cubes from the ResultGUI
+ resultNames = fieldnames(result); %get(handles.popupDisplayOption,'String');
+
+ %Display all values of fields found in the resultGUI struct
+ for runResult = 1:numel(resultNames)
+ if ~isstruct(result.(resultNames{runResult,1})) && ~isvector(result.(resultNames{runResult,1}))
+ %try
+ name = resultNames{runResult};
+ if isfield(result,name) % (check the dimensions, same as CT)
+ field = result.(name);
+ val = field(cubeIx(1),cubeIx(2),cubeIx(3));
+ cursorText{end+1,1} = [name ': ' num2str(val,3)];
+ end
+ %catch
+ %cursorText{end+1,1} = 'Error while retreiving Data!';
+ end
+ end
+ end
+
+ else %Profile view
+ cursorText = cell(2,1);
+ cursorText{1} = ['Radiological Depth: ' num2str(pos(1),3) ' mm'];
+ cursorText{2} = [get(target,'DisplayName') ': ' num2str(pos(2),3)];
+ end
+
+ end
+
+ %Scroll wheel update
+ function matRadScrollWheelFcn(this,src,event)
+ % compute new slice
+ this.slice= this.slice - event.VerticalScrollCount;
+
+ end
+
+ %Toggle Legend
+ function legendToggleFunction(this,src,event)
+ if isempty(this.legendHandle) || ~isobject(this.legendHandle)
+ return;
+ end
+ if this.plotLegend
+ set(this.legendHandle,'Visible','on')
+ else
+ set(this.legendHandle,'Visible','off')
+ end
+ end
+
+ %Toggle Colorbar
+ function colorBarToggleFunction(this,src,event)
+ if isempty(this.cBarHandle) || ~isobject(this.cBarHandle) || this.updateLock
+ return;
+ end
+ if this.plotColorBar
+
+ if evalin('base','exist(''resultGUI'')')
+ this.colorData=2;
+ else evalin('base','exist(''ct'')')
+ this.colorData=1;
+ end
+ set(this.cBarHandle,'Visible','on')
+ else
+ set(this.cBarHandle,'Visible','off');
+ end
+ % send a notification that the plot has changed (to update the options)
+ %this.notifyPlotUpdated();
+ end
+
+ %
+ function initValues(this)
+ lockState=this.updateLock;
+
+ if lockState
+ return;
+ end
+
+ this.updateLock=true;
+
+ if isempty(this.plane)
+ this.plane=3;
+ end
+
+ if evalin('base','exist(''ct'')') && evalin('base','exist(''cst'')')
+ % update slice, beam and offset sliders parameters
+
+ ct = evalin('base','ct');
+ cst = evalin('base','cst');
+ cst = matRad_computeVoiContoursWrapper(cst,ct);
+ assignin('base','cst',cst);
+ this.cst = cst;
+
+ % define context menu for structures
+ this.VOIPlotFlag=false(size(this.cst,1),1);
+ for i = 1:size(this.cst,1)
+ if this.cst{i,5}.Visible
+ this.VOIPlotFlag(i) = true;
+ end
+ end
+
+ if isfield(ct, 'cubeHU')
+ minMax = [min(ct.cubeHU{1}(:)) max(ct.cubeHU{1}(:))];
+
+ if diff(minMax) == 0
+ minMax = [-1000 2000];
+ end
+ else
+ minMax = [min(ct.cube{1}(:)) max(ct.cube{1}(:))];
+
+ if diff(minMax) == 0
+ minMax = [0 2];
+ end
+ end
+
+
+ planeCenters = ceil(ct.cubeDim./ 2);
+ this.numOfBeams = 1;
+ if evalin('base','exist(''pln'')')
+ pln = evalin('base','pln');
+
+ if isfield(pln,'bioParam')
+ visQuantity = pln.bioParam.quantityVis;
+ else
+ visQuantity = [];
+ end
+
+ if isfield(pln,'propStf')
+ isoCoordinates = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:), ct);
+ planeCenters = ceil(isoCoordinates);
+ this.numOfBeams=pln.propStf.numOfBeams;
+ end
+ else
+ visQuantity = [];
+ end
+
+ this.slice = planeCenters(this.plane);
+
+ % set profile offset slider
+ this.OffsetMinMax = [-100 100];
+ vRange = sum(abs(this.OffsetMinMax));
+
+ if strcmp(this.ProfileType,'lateral')
+ this.OffsetSliderStep = vRange/ct.resolution.x;
+ else
+ this.OffsetSliderStep = vRange/ct.resolution.y;
+ end
+ this.OffsetSliderStep=[1/this.OffsetSliderStep 1/this.OffsetSliderStep];
+
+
+ selectionIndex=1;
+ this.plotColorBar=true;
+
+
+ if evalin('base','exist(''resultGUI'')')
+ this.colorData=2;
+ this.plotColorBar=true;
+ selectionIndex=1;
+
+ Result = evalin('base','resultGUI');
+
+ this.DispInfo = fieldnames(Result);
+
+ this.updateDisplaySelection(visQuantity);
+ else
+ this.colorData=1;
+ if evalin('base','exist(''resultGUI'')')
+ this.SelectedDisplayAllOptions ='physicalDose';
+ this.SelectedDisplayOption ='physicalDose';
+ else
+ this.SelectedDisplayAllOptions = 'no option available';
+ this.SelectedDisplayOption = '';
+ end
+ end
+ else %no data is loaded
+ this.slice=1;
+ this.numOfBeams=1;
+ this.OffsetMinMax = [1 1];
+ this.profileOffset=1;
+ this.OffsetSliderStep=[1 1];
+ this.colorData=1;
+ this.plotColorBar=false;
+ selectionIndex=1;
+ minMax = [0 1];
+ this.SelectedDisplayAllOptions = 'no option available';
+ this.SelectedDisplayOption = '';
+ end
+
+ this.dispWindow{selectionIndex,1} = minMax;
+ this.dispWindow{selectionIndex,2} = minMax;
+
+ this.updateLock=lockState;
+ end
+
+ %update the Viewer
+ function updateValues(this)
+ lockState=this.updateLock;
+
+ if lockState
+ return;
+ end
+
+ this.updateLock=true;
+
+ if evalin('base','exist(''ct'')') && evalin('base','exist(''cst'')')
+ % update slice, beam and offset sliders parameters
+ ct = evalin('base','ct');
+ cst = evalin('base','cst');
+ this.cst = cst;
+
+ % define context menu for structures
+ this.VOIPlotFlag=false(size(this.cst,1),1);
+ for i = 1:size(this.cst,1)
+ if this.cst{i,5}.Visible
+ this.VOIPlotFlag(i) = true;
+ end
+ end
+ % set isoCenter values
+ % Note: only defined for the first Isocenter
+ if evalin('base','exist(''pln'')')
+ pln = evalin('base','pln');
+ if isfield(pln,'propStf')
+ this.vIsoCenter = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:), ct);
+ else
+ this.plotIsoCenter = false;
+ end
+ else
+ this.plotIsoCenter = false;
+ end
+
+ % set profile offset slider
+ this.OffsetMinMax = [-100 100];
+ vRange = sum(abs(this.OffsetMinMax));
+
+ if strcmp(this.ProfileType,'lateral')
+ this.OffsetSliderStep = vRange/ct.resolution.x;
+ else
+ this.OffsetSliderStep = vRange/ct.resolution.y;
+ end
+ this.OffsetSliderStep=[1/this.OffsetSliderStep 1/this.OffsetSliderStep];
+
+ this.updateDisplaySelection();
+ end
+
+ this.updateLock=lockState;
+ end
+
+ function updateDisplaySelection(this,visSelection)
+ %Lock triggering an update during isoline caching
+ currLock = this.updateLock;
+ this.updateLock = true;
+
+ if nargin < 2
+ visSelection = [];
+ end
+
+ if evalin('base','exist(''resultGUI'')')
+ result = evalin('base','resultGUI');
+
+ this.DispInfo = fieldnames(result);
+ for i = 1:size(this.DispInfo,1)
+
+ % delete weight vectors in Result struct for plotting
+ if isstruct(result.(this.DispInfo{i,1})) || isvector(result.(this.DispInfo{i,1}))
+ result = rmfield(result,this.DispInfo{i,1});
+ this.DispInfo{i,2}=false;
+ else
+ %second dimension indicates if it should be plotted
+ this.DispInfo{i,2} = true;
+ % determine units (third dimension) and left or
+ % right axis (fourth dimension, ignored so far
+ if strfind(this.DispInfo{i,1},'physicalDose')
+ this.DispInfo{i,3} = 'Gy';
+ this.DispInfo{i,4} = 'left';
+ elseif strfind(this.DispInfo{i,1},'alpha')
+ this.DispInfo{i,3} = 'Gy^{-1}';
+ this.DispInfo{i,4} = 'left';
+ elseif strfind(this.DispInfo{i,1},'beta')
+ this.DispInfo{i,3} = 'Gy^{-2}';
+ this.DispInfo{i,4} = 'left';
+ elseif strfind(this.DispInfo{i,1},'RBExD')
+ this.DispInfo{i,3} = 'Gy(RBE)';
+ this.DispInfo{i,4} = 'left';
+ elseif strfind(this.DispInfo{i,1},'LET')
+ this.DispInfo{i,3} = 'keV/um';
+ this.DispInfo{i,4} = 'left';
+ elseif strfind(this.DispInfo{i,1},'effect')
+ this.DispInfo{i,3} = '1';
+ this.DispInfo{i,4} = 'right';
+ elseif strfind(this.DispInfo{i,1},'RBE')
+ this.DispInfo{i,3} = '1';
+ this.DispInfo{i,4} = 'right';
+ elseif strfind(this.DispInfo{i,1},'BED')
+ this.DispInfo{i,3} = 'Gy';
+ this.DispInfo{i,4} = 'left';
+ else
+ this.DispInfo{i,3} = 'a.u.';
+ this.DispInfo{i,4} = 'right';
+ end
+ end
+ end
+
+ this.SelectedDisplayAllOptions=fieldnames(result);
+
+ if ~isempty(visSelection) && isfield(result,visSelection)
+ this.SelectedDisplayOption = visSelection;
+ elseif ~isfield(result,this.SelectedDisplayOption)
+ this.SelectedDisplayOption = 'physicalDose';
+ else
+ %Keep option
+ end
+
+ if ~any(strcmp(this.SelectedDisplayOption,fieldnames(result)))
+ this.SelectedDisplayOption = 'physicalDose';
+ if ~any(strcmp(this.SelectedDisplayOption,fieldnames(result)))
+ this.SelectedDisplayOption = this.DispInfo{find([this.DispInfo{:,2}],1,'first'),1};
+ end
+
+ this.updateIsoDoseLineCache();
+ end
+ else
+ this.SelectedDisplayAllOptions = 'no option available';
+ this.SelectedDisplayOption = '';
+ end
+
+ this.updateLock = currLock;
+ end
+ end
+end
\ No newline at end of file
diff --git a/matRad/gui/widgets/matRad_VisualizationWidget.m b/matRad/gui/widgets/matRad_VisualizationWidget.m
new file mode 100644
index 000000000..50a8aa224
--- /dev/null
+++ b/matRad/gui/widgets/matRad_VisualizationWidget.m
@@ -0,0 +1,991 @@
+classdef matRad_VisualizationWidget < matRad_Widget
+ % matRad_VisualizationWidget class to generate GUI widget to set
+ % viewing options
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+ viewingWidgetHandle;
+ dvhStatWidgetHandle;
+ end
+
+ methods
+ function this = matRad_VisualizationWidget(handleParent)
+ if nargin < 1
+ matRad_cfg = MatRad_Config.instance();
+ handleParent = figure(...
+ 'Units','characters',...
+ 'Position',[170 45 140 15],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,... 'CloseRequestFcn',@(hObject,eventdata) figure1_CloseRequestFcn(this,hObject,eventdata),...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad Visualization',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1');
+
+ end
+ this = this@matRad_Widget(handleParent);
+
+ handles=this.handles;
+
+ set(handles.btnDVH,'Enable','off');
+ set(handles.popupDisplayOption,'Enable','off');
+ set(handles.popupProfileType,'Enable','off');
+ set(handles.popupTypeOfPlot,'Enable','off');
+ set(handles.popupPlane,'Enable','off');
+ set(handles.radiobtnCT,'Enable','off');
+ set(handles.radiobtnContour,'Enable','off');
+ set(handles.radiobtnDose,'Enable','off');
+ set(handles.radiobtnIsoDoseLines,'Enable','off');
+ set(handles.sliderSlice,'Enable','off');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','off');
+ set(handles.radioBtnIsoCenter,'Enable','off');
+ set(handles.radiobtnPlan,'Enable','off');
+ set(handles.btn3Dview,'Enable','off');
+ set(handles.sliderCTScen,'Enable','off');
+
+ this.handles = handles;
+ end
+
+% function viewingWidgetHandle=get.viewingWidgetHandle(this)
+% viewingWidgetHandle=this.viewingWidgetHandle;
+% end
+
+ function set.viewingWidgetHandle(this,value)
+ if isa(value,'matRad_ViewingWidget')
+ this.viewingWidgetHandle=value;
+
+ % get the default values from the viewer widget
+ this.getFromViewingWidget();
+
+ else
+ handles=this.handles;
+ % disable all buttons
+ set(handles.popupDisplayOption,'Enable','off');
+ set(handles.popupProfileType,'Enable','off');
+ set(handles.popupTypeOfPlot,'Enable','off');
+ set(handles.popupPlane,'Enable','off');
+ set(handles.radiobtnCT,'Enable','off');
+ set(handles.radiobtnContour,'Enable','off');
+ set(handles.radiobtnDose,'Enable','off');
+ set(handles.radiobtnIsoDoseLines,'Enable','off');
+ set(handles.sliderSlice,'Enable','off');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','off');
+ set(handles.radioBtnIsoCenter,'Enable','off');
+ set(handles.radiobtnPlan,'Enable','off');
+ set(handles.btn3Dview,'Enable','off');
+ set(handles.sliderCTScen,'Enable','off');
+ this.handles=handles;
+ end
+ end
+
+ function initialize(this)
+ initialize@matRad_Widget(this);
+ end
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this)
+ h36 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Create Main Grid layout
+ gridSize = [7 4];
+ elSize = [0.9 0.6];
+ [i,j] = ndgrid(1:gridSize(1),1:gridSize(2));
+ gridPos = arrayfun(@(i,j) computeGridPos(this,[i j],gridSize,elSize),i,j,'UniformOutput',false);
+
+ %First column
+ h40 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Slice Selection',...
+ 'TooltipString','Choose which slice should be displayed in intensity plots',...
+ 'Style','text',...
+ 'Position',gridPos{1,1},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','textSliceSelection');
+
+ h48 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Beam Selection',...
+ 'TooltipString','Choose which beam should be displayed in profile plots',...
+ 'Style','text',...
+ 'Position',gridPos{1,2},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtBeamSelection' );
+
+ h54 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Offset',...
+ 'TooltipString','Define an offset value for profile plots',...
+ 'Style','text',...
+ 'Position',gridPos{1,3},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','textOffset');
+
+ %Second Column (Sliders)
+ h38 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','Slider',...
+ 'TooltipString','Choose which slice should be displayed in intensity plots',...
+ 'Style','slider',...
+ 'Callback',@(hObject,eventdata) sliderSlice_Callback(this,hObject,eventdata),...
+ 'BusyAction','cancel',...
+ 'Interruptible','off',...
+ 'Position',gridPos{2,1},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','sliderSlice');
+
+ h49 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','SliderBeamSelection',...
+ 'TooltipString','Choose which beam should be displayed in profile plots',...
+ 'Style','slider',...
+ 'BackgroundColor',[0.9 0.9 0.9],...
+ 'Callback',@(hObject,eventdata) sliderBeamSelection_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'Position',gridPos{2,2},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','sliderBeamSelection');
+
+ h55 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','SliderOffset',...
+ 'TooltipString','Define an offset value for profile plots',...
+ 'Style','slider',...
+ 'Position',gridPos{2,3},...
+ 'BackgroundColor',[0.9 0.9 0.9],...
+ 'Callback',@(hObject,eventdata) sliderOffset_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','sliderOffset');
+ h25 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','Recenter',...
+ 'TooltipString','Recenter viewing widget to isocenter',...
+ 'Position',gridPos{2,4},...
+ 'BackgroundColor',[0.8 0.8 0.8],...
+ 'Callback',@(hObject,eventdata) btnRecenter_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'Tag','btnRecenter',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Third Column
+ h44 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Type of plot',...
+ 'TooltipString','Display intensity or profile plot',...
+ 'Style','text',...
+ 'Position',gridPos{3,1},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtTypeOfPlot' );
+
+ h39 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','Plane Selection',...
+ 'TooltipString','Display coronal, sagital or axial plane in intensity plots',...
+ 'Style','text',...
+ 'Position',gridPos{3,2},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtPlanSelection');
+
+ h46 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Display Quantity',...
+ 'TooltipString','Select the result distribution which should be displayed',...
+ 'Style','text',...
+ 'Position',gridPos{3,3},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtDisplayOption' );
+
+ h47 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','CT Scenario',...
+ 'TooltipString','Select the ct scenario which should be displayed',...
+ 'Style','text',...
+ 'Position',gridPos{3,4},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','txtCtScen' );
+
+ %Fourth Column
+
+ h45 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String',{ 'intensity'; 'profile' },...
+ 'TooltipString','Display intensity or profile plot',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{4,1},...
+ 'Callback',@(hObject,eventdata) popupTypeOfPlot_Callback(this,hObject,eventdata),...
+ 'Tag','popupTypeOfPlot',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ h37 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String',{ 'coronal'; 'sagital'; 'axial' },...
+ 'TooltipString','Display coronal, sagital or axial plane in intensity plots',...
+ 'Style','popupmenu',...
+ 'Value',3,...
+ 'Position',gridPos{4,2},...
+ 'Callback',@(hObject,eventdata) popupPlane_Callback(this,hObject,eventdata),...
+ 'Tag','popupPlane',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ h47 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','Please select...',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{4,3},...
+ 'Callback',@(hObject,eventdata)popupDisplayOption_Callback(this,hObject,eventdata),...
+ 'Tag','popupDisplayOption',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ h60 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'Value',1,...
+ 'SliderStep',[1 1],...
+ 'TooltipString', 'Choose the ct Scenario to be display',...
+ 'Style','slider',...
+ 'Position',gridPos{4,4},...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,....
+ 'Callback',@(hObject,eventdata) ctScen_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'Tag','sliderCTScen',...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Fifth Column
+ h50 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String',{'depth','lateral'},...
+ 'TooltipString','Select which 1D profile through the isocenter you would like to see',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',gridPos{5,1},...
+ 'Callback',@(hObject,eventdata) popupProfileType_Callback(this,hObject,eventdata),...
+ 'Tag','popupProfileType',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %{
+ %Old display with button
+ pos = gridPos{5,1};
+ pos(3) = pos(3)*0.4975;
+ h51 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','Profile:',...
+ 'TooltipString','Display depth or lateral profile',...
+ 'Style','text',...
+ 'Position',pos,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text16' );
+
+ pos = gridPos{5,1};
+ pos(3) = pos(3)*0.4975;
+ pos(1) = pos(1) + pos(3) + 0.0025;
+
+ h50 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','lateral',...
+ 'TooltipString','Display depth or lateral profile',...
+ 'Position',pos,...
+ 'Callback',@(hObject,eventdata) popupProfileType_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'Tag','popupProfileType',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+ %}
+
+
+ h57 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','Open 3D-View',...
+ 'TooltipString','Get a 3 dimensional illustration of the patient',...
+ 'Position',gridPos{5,2},...
+ 'Callback',@(hObject,eventdata) btn3Dview_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'Tag','btn3Dview',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ h52 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','Show DVH/QI',...
+ 'TooltipString','Render the dose volume histogram along with quality indicators',...
+ 'Position',gridPos{5,3},...
+ 'Callback',@(hObject,eventdata) btnDVH_Callback(this,hObject,eventdata),...
+ 'Enable','off',...
+ 'Tag','btnDVH',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight);
+
+ %Sixth Column
+ %Recompute Grid to allow 7 rows
+ gridSize = [7 7];
+ elSize = [0.9 0.6];
+ [i,j] = ndgrid(1:gridSize(1),1:gridSize(2));
+ newPos = arrayfun(@(i,j) computeGridPos(this,[i j],gridSize,elSize),i,j,'UniformOutput',false);
+
+ %We need only the 6th col and make it double wide
+ newPos = newPos(6,:);
+ newPos = cellfun(@(pos) pos .* [1 1 2 1],newPos,'UniformOutput',false);
+
+ h58 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','plot CT',...
+ 'TooltipString','If this checked, the CT is displayed',...
+ 'Style','radiobutton',...
+ 'Value',1,...
+ 'Position',newPos{1},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata) radiobtnCT_Callback(this,hObject,eventdata),...
+ 'Tag','radiobtnCT');
+
+ h41 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','plot contour',...
+ 'TooltipString','If this checked, the contours of the VOIs are displayed',...
+ 'Style','radiobutton',...
+ 'Value',1,...
+ 'Position',newPos{2},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata) radiobtnContour_Callback(this,hObject,eventdata),...
+ 'Tag','radiobtnContour' );
+
+ h42 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','plot dose',...
+ 'TooltipString','If this checked, the optimized distribution is displayed',...
+ 'Style','radiobutton',...
+ 'Value',1,...
+ 'Position',newPos{3},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata) radiobtnDose_Callback(this,hObject,eventdata),...
+ 'Tag','radiobtnDose' );
+
+ h43 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','plot isolines',...
+ 'TooltipString','If this checked, isolines are displayed',...
+ 'Style','radiobutton',...
+ 'Value',1,...
+ 'Position',newPos{4},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata) radiobtnIsoDoseLines_Callback(this,hObject,eventdata),...
+ 'Tag','radiobtnIsoDoseLines');
+
+ h53 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','plot isolines labels',...
+ 'TooltipString','If this checked, isoline labels are displayed',...
+ 'Style','radiobutton',...
+ 'Position',newPos{5},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata) radiobtnIsoDoseLinesLabels_Callback(this,hObject,eventdata),...
+ 'Tag','radiobtnIsoDoseLinesLabels');
+
+ h56 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','plot iso center',...
+ 'TooltipString','If this checked, the iso center is displayed',...
+ 'Style','radiobutton',...
+ 'Value',1,...
+ 'Position',newPos{6},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata) radioBtnIsoCenter_Callback(this,hObject,eventdata),...
+ 'Tag','radioBtnIsoCenter');
+
+ h59 = uicontrol(...
+ 'Parent',h36,...
+ 'Units','normalized',...
+ 'String','visualize plan/beams',...
+ 'TooltipString','If this checked, the beam angles are displayed',...
+ 'Style','radiobutton',...
+ 'Value',1,...
+ 'Position',newPos{7},...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,eventdata) radiobtnPlan_Callback(this,hObject,eventdata),...
+ 'Tag','radiobtnPlan');
+
+
+ this.createHandles();
+ end
+
+ function this = doUpdate(this,~)
+ if isa(this.viewingWidgetHandle,'matRad_ViewingWidget')
+ % get the default values from the viewer widget
+ this.getFromViewingWidget();
+ else
+ handles = this.handles;
+ % disable all buttons
+ set(handles.popupDisplayOption,'Enable','off');
+ set(handles.popupProfileType,'Enable','off');
+ set(handles.popupTypeOfPlot,'Enable','off');
+ set(handles.popupPlane,'Enable','off');
+ set(handles.radiobtnCT,'Enable','off');
+ set(handles.radiobtnContour,'Enable','off');
+ set(handles.radiobtnDose,'Enable','off');
+ set(handles.radiobtnIsoDoseLines,'Enable','off');
+ set(handles.sliderSlice,'Enable','off');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','off');
+ set(handles.radioBtnIsoCenter,'Enable','off');
+ set(handles.radiobtnPlan,'Enable','off');
+ this.handles = handles;
+ end
+ end
+ end
+
+ methods (Access = protected)
+ function getFromViewingWidget(this)
+ handles = this.handles;
+ if strcmp(this.viewingWidgetHandle.ProfileType,'lateral')
+ set(handles.popupProfileType,'Value',2);
+ else
+ set(handles.popupProfileType,'Value',1);
+ end
+
+ %Check if CT / Cst exists
+ if evalin('base','exist(''ct'') & exist(''cst'')')
+ set(handles.btn3Dview,'Enable','on');
+ set(handles.popupTypeOfPlot,'Enable','on');
+ set(handles.radiobtnCT,'Enable','on');
+ set(handles.radiobtnContour,'Enable','on');
+ set(handles.sliderSlice,'Enable','on');
+ set(handles.popupPlane,'Enable','on');
+ ct = evalin('base','ct');
+ if isfield(ct,'numOfCtScen')
+ numScen = ct.numOfCtScen;
+ else
+ numScen = numel(ct.cubeHU);
+ end
+
+ sliceSliderStep=[1/(ct.cubeDim(this.viewingWidgetHandle.plane)-1) 1/(ct.cubeDim(this.viewingWidgetHandle.plane)-1)];
+ ctDim = ct.cubeDim;
+ else
+ set(handles.btn3Dview,'Enable','off');
+ set(handles.popupTypeOfPlot,'Enable','off');
+ set(handles.radiobtnCT,'Enable','off');
+ set(handles.radiobtnContour,'Enable','off');
+ set(handles.sliderSlice,'Enable','off');
+ set(handles.popupPlane,'Enable','off');
+ numScen = 1;
+ sliceSliderStep=[1 1];
+ ctDim = [1 1 1];
+ end
+
+
+ set(handles.popupTypeOfPlot,'Value',this.viewingWidgetHandle.typeOfPlot);
+ set(handles.popupPlane,'Value',this.viewingWidgetHandle.plane);
+ set(handles.radiobtnContour,'Value',this.viewingWidgetHandle.plotContour);
+ set(handles.radiobtnDose,'Value',this.viewingWidgetHandle.plotDose);
+ set(handles.radiobtnIsoDoseLines,'Value',this.viewingWidgetHandle.plotIsoDoseLines);
+ set(handles.radiobtnIsoDoseLinesLabels,'Value',this.viewingWidgetHandle.plotIsoDoseLinesLabels);
+ set(handles.radioBtnIsoCenter,'Value',this.viewingWidgetHandle.plotIsoCenter);
+ set(handles.radiobtnPlan,'Value',this.viewingWidgetHandle.plotPlan);
+
+ % update the sliders
+ % Slice Slider
+
+ set(handles.sliderSlice,'Min',1,'Max',ctDim(this.viewingWidgetHandle.plane),...
+ 'Value', this.viewingWidgetHandle.slice, ...
+ 'SliderStep',sliceSliderStep);
+
+ % Beam Slider (Profile Plot)
+ if this.viewingWidgetHandle.numOfBeams>1
+ set(handles.sliderBeamSelection,'Min',1,'Max',this.viewingWidgetHandle.numOfBeams,...
+ 'Value',this.viewingWidgetHandle.selectedBeam,...
+ 'SliderStep',[1/(this.viewingWidgetHandle.numOfBeams-1) 1/(this.viewingWidgetHandle.numOfBeams-1)]);
+ else
+ set(handles.sliderBeamSelection,'Min',1,'Max',1, 'Value',1,'SliderStep',[1 1]);
+ end
+
+ % Offset Slider (profile plot)
+ set(handles.sliderOffset,'Min',this.viewingWidgetHandle.OffsetMinMax(1),'Max',this.viewingWidgetHandle.OffsetMinMax(2),...
+ 'Value',this.viewingWidgetHandle.profileOffset,...
+ 'SliderStep',this.viewingWidgetHandle.OffsetSliderStep);
+
+ %CT Scenario Slider
+ if numScen > 1
+ set(handles.sliderCTScen,'Min',1,'Max',numScen,'Enable','on','SliderStep',[1 1]./(numScen-1));
+ else
+ set(handles.sliderCTScen,'Enable','off');
+ end
+
+ set(handles.popupDisplayOption,'String',this.viewingWidgetHandle.SelectedDisplayAllOptions);
+ if any(strcmp(this.viewingWidgetHandle.SelectedDisplayOption,this.viewingWidgetHandle.SelectedDisplayAllOptions))
+ set(handles.popupDisplayOption,'Value',find(strcmp(this.viewingWidgetHandle.SelectedDisplayOption,this.viewingWidgetHandle.SelectedDisplayAllOptions)));
+ else
+ set(handles.popupDisplayOption,'Value',1);
+ end
+
+ if strcmp(this.viewingWidgetHandle.SelectedDisplayOption,'') % no data is loaded
+ % disable 3D and DVH button
+ set(handles.btnDVH,'Enable','off');
+ set(handles.radiobtnDose,'Enable','off');
+ set(handles.radiobtnIsoDoseLines,'Enable','off');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','off');
+ else
+
+ if evalin('base','exist(''resultGUI'')')
+ set(handles.btnDVH,'Enable','on');
+ set(handles.radiobtnDose,'Enable','on');
+ set(handles.radiobtnIsoDoseLines,'Enable','on');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','on');
+ else
+ set(handles.btnDVH,'Enable','off');
+ set(handles.radiobtnDose,'Enable','off');
+ set(handles.radiobtnIsoDoseLines,'Enable','off');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','off');
+ end
+ end
+
+ %% enable and diasble buttons according to type of plot
+ % intensity plot
+ if this.viewingWidgetHandle.typeOfPlot == 1
+
+ set(handles.sliderBeamSelection,'Enable','off');
+ set(handles.sliderOffset,'Enable','off');
+ set(handles.popupDisplayOption,'Enable','on');
+ set(handles.popupProfileType,'Enable','off');
+ set(handles.popupPlane,'Enable','on');
+ set(handles.radiobtnCT,'Enable','on');
+ set(handles.radiobtnContour,'Enable','on');
+ set(handles.sliderSlice,'Enable','on');
+ set(handles.btnRecenter, 'Enable','on');
+ set(handles.radioBtnIsoCenter,'Enable','on');
+ set(handles.radiobtnPlan,'Enable','on');
+
+ % profile plot
+ elseif this.viewingWidgetHandle.typeOfPlot == 2
+
+ set(handles.popupDisplayOption,'Enable','on');
+ set(handles.popupProfileType,'Enable','on');
+ set(handles.popupPlane,'Enable','off');
+ set(handles.radiobtnCT,'Enable','off');
+ set(handles.radiobtnContour,'Enable','off');
+ set(handles.radiobtnDose,'Enable','off');
+ set(handles.radiobtnIsoDoseLines,'Enable','off');
+ set(handles.sliderSlice,'Enable','off');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','off');
+ set(handles.popupProfileType,'Enable','on');
+ set(handles.btnRecenter, 'Enable','off');
+ set(handles.radioBtnIsoCenter,'Enable','off');
+ set(handles.radiobtnPlan,'Enable','off');
+ end
+
+
+ this.handles = handles;
+ end
+
+ % H37 Calback
+ function popupPlane_Callback(this, hObject, event)
+ % hObject handle to popupPlane (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: contents = cellstr(get(hObject,'String')) returns popupPlane contents as cell array
+ % contents{get(hObject,'Value')} returns selected item from popupPlane
+
+ % set slice slider
+ handles = this.handles;
+ this.viewingWidgetHandle.plane = get(handles.popupPlane,'value');
+ this.handles = handles;
+ end
+
+ %45 Callback
+ function popupTypeOfPlot_Callback(this, hObject, event)
+ this.viewingWidgetHandle.typeOfPlot = get(hObject,'Value');
+ handles = this.handles;
+
+ % intensity plot
+ if get(hObject,'Value') == 1
+
+ set(handles.sliderBeamSelection,'Enable','off')
+ set(handles.sliderOffset,'Enable','off')
+ set(handles.popupDisplayOption,'Enable','on')
+ set(handles.popupProfileType,'Enable','off');
+ set(handles.popupPlane,'Enable','on');
+ set(handles.radiobtnCT,'Enable','on');
+ set(handles.radiobtnContour,'Enable','on');
+ set(handles.radiobtnDose,'Enable','on');
+ set(handles.radiobtnIsoDoseLines,'Enable','on');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','on');
+ set(handles.sliderSlice,'Enable','on');
+
+ % profile plot
+ elseif get(hObject,'Value') == 2
+
+ if evalin('base','exist(''pln'')') && evalin('base','exist(''ct'')')
+ if this.viewingWidgetHandle.numOfBeams>1
+ set(handles.sliderBeamSelection,'Enable','on');
+ end
+ set(handles.sliderOffset,'Enable','on');
+ end
+
+ set(handles.popupDisplayOption,'Enable','on');
+ set(handles.popupProfileType,'Enable','on');
+ set(handles.popupPlane,'Enable','off');
+ set(handles.radiobtnCT,'Enable','off');
+ set(handles.radiobtnContour,'Enable','off');
+ set(handles.radiobtnDose,'Enable','off');
+ set(handles.radiobtnIsoDoseLines,'Enable','off');
+ set(handles.sliderSlice,'Enable','off');
+ set(handles.radiobtnIsoDoseLinesLabels,'Enable','off');
+
+ set(handles.popupProfileType,'Enable','on')
+
+% end
+ end
+
+
+
+ this.handles = handles;
+ end
+
+ % 47 Callback
+ function popupDisplayOption_Callback(this, hObject, event)
+ %this.updateIsodoseLine();
+ content = get(hObject,'String');
+ if strcmp(content,'no option available')
+ return
+ end
+
+ handles = this.handles;
+ this.viewingWidgetHandle.SelectedDisplayOption = content{get(hObject,'Value'),1};
+ this.handles = handles;
+ % if matRad Plan Analysis exists use that
+ fh = findobj( 'Type', 'Figure', 'Name', 'MatRad Plan Analysis' );
+ if ~isempty(fh)
+ this.dvhStatWidgetHandle.selectedDisplayOption = content{get(hObject,'Value'),1};
+ end
+
+
+ end
+
+ % H49 Callback
+ function sliderBeamSelection_Callback(this, hObject, event)
+ % hObject handle to sliderBeamSelection (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'Value') returns position of slider
+ % get(hObject,'Min') and get(hObject,'Max') to determine range of slider
+
+ handles = this.handles;
+ this.viewingWidgetHandle.selectedBeam = round(get(hObject,'Value'));
+ set(hObject, 'Value', this.viewingWidgetHandle.selectedBeam);
+% handles.rememberCurrAxes = false;
+% UpdatePlot(handles);
+% handles.rememberCurrAxes = true;
+
+ this.handles = handles;
+ end
+
+ % 50 Callback
+ function popupProfileType_Callback(this, hObject, event)
+ % hObject handle to popupProfileType (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ %handles = this.handles;
+
+ val = get(hObject,'Value');
+
+ switch val
+ case 1
+ this.viewingWidgetHandle.ProfileType = 'longitudinal';
+ case 2
+ this.viewingWidgetHandle.ProfileType = 'lateral';
+ otherwise
+ this.showError('Invalid selection for Profile Plot!');
+ end
+ %this.handles = handles;
+ end
+
+ % 52 Callback
+ function btnDVH_Callback(this, hObject, event)
+ this.dvhStatWidgetHandle = matRad_DVHStatsWidget(); % pass fieldname in resultGUI
+ this.dvhStatWidgetHandle.selectedDisplayOption = this.viewingWidgetHandle.SelectedDisplayOption;
+ end
+
+ %H55 Callback
+ function sliderOffset_Callback(this, hObject, event)
+ this.viewingWidgetHandle.profileOffset = get(hObject,'Value');
+ %UpdatePlot(handles);
+ end
+
+ % 57 Callback
+ function btn3Dview_Callback(this,hObject, event)
+ h3Dwidget = matRad_3DWidget();
+ h3Dwidget.viewingWidgetHandle = this.viewingWidgetHandle;
+
+ %Initialize event Listeners
+ matRad_cfg = MatRad_Config.instance();
+ if matRad_cfg.isMatlab
+ %Event listeners triggered on events
+ hListener = addlistener(this.viewingWidgetHandle,'plotUpdated',@(src,hEvent) h3Dwidget.update());
+ addlistener(h3Dwidget.handles.figure1,'ObjectBeingDestroyed',@(src,hEvent) delete(hListener));
+ else
+ % addlistener is not yet available in octave
+ hListener = matRad_addListenerOctave(this.viewingWidgetHandle,'plotUpdated',@(src,hEvent) h3Dwidget.update());
+ matRad_addListenerOctave(h3Dwidget.handles.figure1,'ObjectBeingDestroyed',@(src,hEvent) delete(hListener));
+ end
+
+ end
+ % --- Executes on button press in radiobtnContour.
+ function radiobtnContour_Callback(this,hObject, ~)
+ % hObject handle to radiobtnContour (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hint: get(hObject,'Value') returns toggle state of radiobtnContour
+ %UpdatePlot(handles)
+ this.viewingWidgetHandle.plotContour = get(hObject,'Value');
+ end
+ % --- Executes on slider movement.
+ function sliderSlice_Callback(this,hObject, ~)
+ % hObject handle to sliderSlice (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hints: get(hObject,'Value') returns position of slider
+ % get(hObject,'Min') and get(hObject,'Max') to determine range of slider
+ %UpdatePlot(handles)
+
+ this.viewingWidgetHandle.slice = round(get(hObject,'Value'));
+ end
+
+ function ctScen_Callback(this,hObject, ~)
+ % hObject handle to ctScen (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ this.viewingWidgetHandle.ctScen = get(hObject,'Value');
+ end
+
+ function radiobtnCT_Callback(this,hObject, ~)
+ % hObject handle to radiobtnCT (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hint: get(hObject,'Value') returns toggle state of radiobtnCT
+ %UpdatePlot(handles)
+ this.viewingWidgetHandle.plotCT = get(hObject,'Value');
+ end
+
+ % --- Executes on button press in radiobtnPlan.
+ function radiobtnPlan_Callback(this,hObject, ~)
+ % hObject handle to radiobtnPlan (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hint: get(hObject,'Value') returns toggle state of radiobtnPlan
+ %UpdatePlot(handles)
+ this.viewingWidgetHandle.plotPlan = get(hObject,'Value');
+ end
+
+ % --- Executes on button press in radiobtnIsoDoseLines.
+ function radiobtnIsoDoseLines_Callback(this,hObject, ~)
+ % hObject handle to radiobtnIsoDoseLines (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hint: get(hObject,'Value') returns toggle state of radiobtnIsoDoseLines
+ this.viewingWidgetHandle.plotIsoDoseLines = get(hObject,'Value');
+
+ end
+
+ % --- Executes on button press in radiobtnDose.
+ function radiobtnDose_Callback(this,hObject, ~)
+ % hObject handle to radiobtnDose (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hint: get(hObject,'Value') returns toggle state of radiobtnDose
+ %UpdatePlot(handles)
+ this.viewingWidgetHandle.plotDose=get(hObject,'Value');
+ end
+
+ % radio button: plot isolines labels
+ function radiobtnIsoDoseLinesLabels_Callback(this,hObject, ~)
+ %UpdatePlot(handles);
+ this.viewingWidgetHandle.plotIsoDoseLinesLabels = get(hObject,'Value');
+ end
+
+ % --- Executes on button press in radioBtnIsoCenter.
+ function radioBtnIsoCenter_Callback(this,hObject, eventdata)
+ % hObject handle to radioBtnIsoCenter (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ %UpdatePlot(handles)
+ % Hint: get(hObject,'Value') returns toggle state of radioBtnIsoCenter
+ this.viewingWidgetHandle.plotIsoCenter = get(hObject,'Value');
+ end
+
+ % --- Executes on button press in btnRecenter
+ % currently imlpemented for one single isocenter for all beams
+ function btnRecenter_Callback(this,hObject, eventdata)
+ % hObject handle to radioBtnIsoCenter (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ %UpdatePlot(handles)
+ % Hint: get(hObject,'Value') returns toggle state of radioBtnIsoCenter
+
+ isoSlice = this.viewingWidgetHandle.vIsoCenter(this.viewingWidgetHandle.plane);
+ this.viewingWidgetHandle.slice = isoSlice;
+ end
+
+ end
+end
diff --git a/matRad/gui/widgets/matRad_WorkflowWidget.m b/matRad/gui/widgets/matRad_WorkflowWidget.m
new file mode 100644
index 000000000..3c1561ebf
--- /dev/null
+++ b/matRad/gui/widgets/matRad_WorkflowWidget.m
@@ -0,0 +1,891 @@
+classdef matRad_WorkflowWidget < matRad_Widget
+ % matRad_WorkflowWidget class to generate GUI widget to run through the
+ % treatment planning workflow
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ end
+
+ methods
+ function this = matRad_WorkflowWidget(handleParent)
+ if nargin < 1
+ matRad_cfg = MatRad_Config.instance();
+ handleParent = figure(...
+ 'Units','characters',...
+ 'Position',[130 45 150 15],...
+ 'Visible','on',...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'IntegerHandle','off',...
+ 'Colormap',[0 0 0.5625;0 0 0.625;0 0 0.6875;0 0 0.75;0 0 0.8125;0 0 0.875;0 0 0.9375;0 0 1;0 0.0625 1;0 0.125 1;0 0.1875 1;0 0.25 1;0 0.3125 1;0 0.375 1;0 0.4375 1;0 0.5 1;0 0.5625 1;0 0.625 1;0 0.6875 1;0 0.75 1;0 0.8125 1;0 0.875 1;0 0.9375 1;0 1 1;0.0625 1 1;0.125 1 0.9375;0.1875 1 0.875;0.25 1 0.8125;0.3125 1 0.75;0.375 1 0.6875;0.4375 1 0.625;0.5 1 0.5625;0.5625 1 0.5;0.625 1 0.4375;0.6875 1 0.375;0.75 1 0.3125;0.8125 1 0.25;0.875 1 0.1875;0.9375 1 0.125;1 1 0.0625;1 1 0;1 0.9375 0;1 0.875 0;1 0.8125 0;1 0.75 0;1 0.6875 0;1 0.625 0;1 0.5625 0;1 0.5 0;1 0.4375 0;1 0.375 0;1 0.3125 0;1 0.25 0;1 0.1875 0;1 0.125 0;1 0.0625 0;1 0 0;0.9375 0 0;0.875 0 0;0.8125 0 0;0.75 0 0;0.6875 0 0;0.625 0 0;0.5625 0 0],...
+ 'MenuBar','none',...
+ 'Name','MatRad Workflow',...
+ 'NumberTitle','off',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure1',...
+ 'PaperSize',[20.99999864 29.69999902]);
+
+ set(handleParent,'Units','normalized')
+ pos = get(handleParent,'Position');
+ pos(1:2) = [0.5 0.5] - pos(3:4)./2;
+ set(handleParent,'Position',pos);
+
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+
+ function this = initialize(this)
+ this.update();
+ end
+
+
+ % moved so it can be called from the toolbar button
+ % H74 Callback
+ function btnLoadMat_Callback(this, hObject, event)
+ handles = this.handles;
+ matRad_cfg = MatRad_Config.instance();
+ [FileName, FilePath] = uigetfile(fullfile(matRad_cfg.matRadSrcRoot,'phantoms','*.mat'));
+ if FileName == 0 % user pressed cancel --> do nothing.
+ return;
+ end
+
+ try
+ % delete existing workspace - parse variables from base workspace
+ AllVarNames = evalin('base','who');
+ RefVarNames = {'ct','cst','pln','stf','dij','resultGUI'};
+
+ for i = 1:length(RefVarNames)
+ if sum(ismember(AllVarNames,RefVarNames{i}))>0
+ evalin('base',['clear ', RefVarNames{i}]);
+ end
+ end
+
+ % read new data
+ load([FilePath FileName]);
+
+ catch ME
+ this.handles=handles;
+ getFromWorkspace(this);
+ showError(this,'LoadMatFileFnc: Could not load *.mat file',ME);
+ return
+ end
+
+ try
+ %cst = generateCstTable(this,cst);
+ %handles.TableChanged = false;
+ %set(handles.popupTypeOfPlot,'Value',1);
+ %cst = matRad_computeVoiContoursWrapper(cst,ct);
+
+ assignin('base','ct',ct);
+ assignin('base','cst',cst);
+
+ catch ME
+ showError(this,'LoadMatFileFnc: Could not load *.mat file',ME);
+ end
+
+ % check if a optimized plan was loaded
+ if exist('stf','var')
+ assignin('base','stf',stf);
+ end
+ if exist('pln','var')
+ assignin('base','pln',pln);
+ end
+ if exist('dij','var')
+ assignin('base','dij',dij);
+ end
+
+ if exist('resultGUI','var')
+ assignin('base','resultGUI',resultGUI);
+ end
+
+ this.handles=handles;
+ %updateInWorkspace(this);
+ this.changedWorkspace();
+ %getFromWorkspace(this); %update the buttons
+ end
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this)
+
+ parent = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+
+ h72 = this.addControlToGrid([2 4],...
+ 'Style','text',...
+ 'String','Status:',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtStatus',...
+ 'FontSize',round(matRad_cfg.gui.fontSize*1.2));
+
+
+ h73 = this.addControlToGrid([3 4],...
+ 'String','no data loaded',...
+ 'Style','text',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Tag','txtInfo',...
+ 'FontSize',round(matRad_cfg.gui.fontSize*1.2));
+
+ hMatLoad = this.addControlToGrid([2 1],...
+ 'String','Load *.mat data',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) btnLoadMat_Callback(this,hObject,eventdata),...
+ 'Tag','btnLoadMat');
+
+ hDijCalc = this.addControlToGrid([3 1],...
+ 'String','Calc. Dose Influence',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) btnCalcDose_Callback(this,hObject,eventdata),...
+ 'Tag','btnCalcDose');
+
+ hOpt = this.addControlToGrid([4 1],...
+ 'Parent',parent,...
+ 'String','Optimize',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) btnOptimize_Callback(this,hObject,eventdata),...
+ 'Tag','btnOptimize');
+
+ hLoadDicom = this.addControlToGrid([2 2],...
+ 'String','Load DICOM',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) btnLoadDicom_Callback(this,hObject,eventdata),...
+ 'Tag','btnLoadDicom');
+
+
+ hRefresh = this.addControlToGrid([1 1],...
+ 'String','Refresh',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) btnRefresh_Callback(this,hObject,eventdata),...
+ 'Tag','btnRefresh');
+
+ hRecalc = this.addControlToGrid([4 2],...
+ 'String','Recalculate Dose',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) pushbutton_recalc_Callback(this,hObject,eventdata),...
+ 'Tag','pushbutton_recalc');
+
+ hKeep = this.addControlToGrid([5 1],...
+ 'String','Save/Keep Result',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) btnSaveToGUI_Callback(this,hObject,eventdata),...
+ 'Tag','btnSaveToGUI');
+
+ hExportBin = this.addControlToGrid([5 2],...
+ 'String','Export Binary',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) btn_export_Callback(this,hObject,eventdata),...
+ 'Children',[],...
+ 'Tag','btn_export');
+
+ hImportDose = this.addControlToGrid([4 3],...
+ 'String','Import Dose',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) importDoseButton_Callback(this, hObject,eventdata),...
+ 'Tag','importDoseButton');
+
+ hImportBin = this.addControlToGrid([2 3],...
+ 'String','Import from Binary',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) pushbutton_importFromBinary_Callback(this,hObject,eventdata),...
+ 'TooltipString','Imports a patient data set from binary datafiles describing CT and segmentations',...
+ 'Tag','pushbutton_importFromBinary');
+
+ hExportDicom = this.addControlToGrid([5 3],...
+ 'String','Export Dicom',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'Callback',@(hObject,eventdata) exportDicomButton_Callback(this, hObject,eventdata),...
+ 'Tag','exportDicomButton');
+
+ this.createHandles();
+
+ handles=this.handles;
+ matRad_cfg = MatRad_Config.instance();
+ if matRad_cfg.eduMode
+ %Visisbility in Educational Mode
+ eduHideHandles = {handles.pushbutton_importFromBinary,...
+ handles.btnLoadDicom,...
+ handles.btn_export,...
+ handles.exportDicomButton,...
+ handles.importDoseButton};
+ cellfun(@(h) set(h,'Visible','Off'),eduHideHandles);
+ end
+ this.handles=handles;
+ end
+
+ function this = doUpdate(this,evt)
+ %If the pln was changed, we do not do a consistency check (or
+ %at least we do not throw a warning when it is inconsistent)
+ if nargin < 2 || any(strcmp(evt.changedVariables,'pln'))
+ noCheck = true;
+ else
+ noCheck = false;
+ end
+ this.getFromWorkspace(noCheck);
+ end
+
+ function this = getFromWorkspace(this,noCheck)
+
+ if nargin < 2
+ noCheck = false;
+ end
+
+ handles = this.handles;
+ matRad_cfg = MatRad_Config.instance();
+ % no data loaded, disable the buttons
+ set(handles.txtInfo,'String','no data loaded');
+ set(handles.btnCalcDose,'Enable','off');
+ set(handles.btnOptimize ,'Enable','off');
+ set(handles.pushbutton_recalc,'Enable','off');
+ set(handles.btnSaveToGUI,'Enable','off');
+ set(handles.importDoseButton,'Enable','off');
+ set(handles.btn_export,'Enable','off');
+ set(handles.exportDicomButton,'Enable','off');
+
+
+ if evalin('base','exist(''ct'')') && evalin('base','exist(''cst'')')
+
+ set(handles.txtInfo,'String','loaded and ready');
+
+ if evalin('base','exist(''pln'')')
+
+
+ % ct cst and pln available; ready for dose calculation
+ set(handles.txtInfo,'String','ready for dose calculation');
+ set(handles.btnCalcDose,'Enable','on');
+ set(handles.btn_export,'Enable','on');
+ set(handles.exportDicomButton,'Enable','on');
+
+ % check if stf exists
+ if evalin('base','exist(''stf'')')
+ % check if dij, stf and pln match
+ [plnStfMatch, msg] = matRad_comparePlnStf(evalin('base','pln'),evalin('base','stf'));
+ if plnStfMatch
+ % plan is ready for optimization
+ set(handles.txtInfo,'String','ready for dose calculation');
+ set(handles.btnOptimize ,'Enable','on');
+ elseif ~noCheck
+ this.showWarning(msg);
+ else
+ %Nothing
+ end
+
+ % check if dij exist
+ if evalin('base','exist(''dij'')')
+ [dijStfMatch, msg] = matRad_compareDijStf(evalin('base','dij'),evalin('base','stf'));
+ if plnStfMatch && dijStfMatch
+ set(handles.txtInfo,'String','ready for optimization');
+ set(handles.btnOptimize ,'Enable','on');
+ elseif ~noCheck
+ this.showWarning(msg);
+ else
+ %Nothing
+ end
+ end
+
+ end
+
+ % does resultGUI exist
+ if evalin('base','exist(''resultGUI'')')
+ set(handles.pushbutton_recalc,'Enable','on');
+ set(handles.btnSaveToGUI,'Enable','on');
+ % resultGUI struct needs to be available to import dose
+ % otherwise inconsistent states can be achieved
+ set(handles.importDoseButton,'Enable','on');
+ end
+ end
+ else
+ % Do Nothing
+ end
+ this.handles=handles;
+ end
+
+
+ end
+ methods (Access = private)
+
+ function h = addControlToGrid(this,gridPos,varargin)
+ matRad_cfg = MatRad_Config.instance();
+ parent = this.widgetHandle;
+
+ %Use a 5 x 5 grid
+ pos = this.computeGridPos(gridPos,[5 5]);
+
+ h = uicontrol('Parent',parent,...
+ 'Units','normalized',...
+ 'Position',pos,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ varargin{:});
+ end
+
+
+ % H75 Callback
+ function btnCalcDose_Callback(this, hObject, eventdata)
+ % hObject handle to btnCalcDose (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % http://stackoverflow.com/questions/24703962/trigger-celleditcallback-before-button-callback
+ % http://www.mathworks.com/matlabcentral/newsreader/view_thread/332613
+ % wait some time until the CallEditCallback is finished
+ % Callback triggers the cst saving mechanism from GUI
+
+ handles = this.handles;
+ try
+ % indicate that matRad is busy
+ % change mouse pointer to hour glass
+ Figures = gcf;%findobj('type','figure');
+ set(Figures, 'pointer', 'watch');
+ drawnow;
+ % disable all active objects
+ InterfaceObj = findobj(Figures,'Enable','on');
+ set(InterfaceObj,'Enable','off');
+
+
+ % read plan from gui and save it to workspace
+ %handles=getPlnFromGUI(this);
+
+ % get default iso center as center of gravity of all targets if not
+ % already defined
+ pln = evalin('base','pln');
+
+ catch ME
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+ showError(this,'CalcDoseCallback: Error in preprocessing!',ME);
+ return;
+ end
+
+ % generate steering file
+ try
+ currPln = evalin('base','pln');
+ % % if we run 3d conf opt -> hijack runDao to trigger computation of
+ % % connected bixels
+ % if strcmp(pln.radiationMode,'photons') && get(handles.radiobutton3Dconf,'Value')
+ % currpln.propOpt.runDAO = true;
+ % end
+ stf = matRad_generateStf(evalin('base','ct'),...
+ evalin('base','cst'),...
+ currPln);
+ assignin('base','stf',stf);
+ catch ME
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+ showError(this,'CalcDoseCallback: Error in steering file generation!',ME);
+ return;
+ end
+
+ % carry out dose calculation
+ try
+ dij = matRad_calcDoseInfluence(evalin('base','ct'),evalin('base','cst'),stf,pln);
+
+ % assign results to base worksapce
+ assignin('base','dij',dij);
+
+
+ catch ME
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+ showError(this,'CalcDoseCallback: Error in dose calculation!',ME);
+ return;
+ end
+
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+ this.changedWorkspace('stf','dij');
+ %getFromWorkspace(this);
+ end
+
+ % H76 Callback
+ function btnOptimize_Callback(this, hObject, eventdata)
+ % hObject handle to btnOptimize (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ handles = this.handles;
+ try
+ % indicate that matRad is busy
+ % change mouse pointer to hour glass
+ Figures = gcf;%findobj('type','figure');
+ set(Figures, 'pointer', 'watch');
+ drawnow;
+ % disable all active objects
+ InterfaceObj = findobj(Figures,'Enable','on');
+ set(InterfaceObj,'Enable','off');
+
+ pln = evalin('base','pln');
+ dij = evalin('base','dij');
+ cst = evalin('base','cst');
+ % optimize
+ [resultGUIcurrentRun,usedOptimizer] = matRad_fluenceOptimization(dij,cst,pln);
+ if isfield(pln,'propOpt') && isfield(pln.propOpt,'conf3D') && pln.propOpt.conf3D && strcmp(pln.radiationMode,'photons')
+ resultGUIcurrentRun.w = resultGUIcurrentRun.w .* ones(dij.totalNumOfBixels,1);
+ resultGUIcurrentRun.wUnsequenced = resultGUIcurrentRun.w;
+ end
+
+ %if resultGUI already exists then overwrite the "standard" fields
+ AllVarNames = evalin('base','who');
+ if ismember('resultGUI',AllVarNames)
+ resultGUI = evalin('base','resultGUI');
+ sNames = fieldnames(resultGUIcurrentRun);
+ oldNames = fieldnames(resultGUI);
+ if(length(oldNames) > length(sNames))
+ for j = 1:length(oldNames)
+ if strfind(oldNames{j}, 'beam')
+ resultGUI = rmfield(resultGUI, oldNames{j});
+ end
+ end
+ end
+ for j = 1:length(sNames)
+ resultGUI.(sNames{j}) = resultGUIcurrentRun.(sNames{j});
+ end
+ else
+ resultGUI = resultGUIcurrentRun;
+ end
+
+ assignin('base','resultGUI',resultGUI);
+
+ if ~pln.propOpt.runDAO || ~strcmp(pln.radiationMode,'photons')
+ CheckOptimizerStatus(this,usedOptimizer,'Fluence')
+ end
+
+ catch ME
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+ showError(this,'OptimizeCallback: Could not optimize!',ME);
+ return;
+ end
+
+ % perform sequencing and DAO
+ try
+ %% sequencing
+
+ resultGUI = matRad_sequencing(resultGUI,evalin('base','stf'),dij,pln);
+ assignin('base','resultGUI',resultGUI);
+
+
+ catch ME
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+ showError(this,'OptimizeCallback: Could not perform sequencing',ME);
+ return;
+ end
+
+ try
+ %% DAO
+ if strcmp(pln.radiationMode,'photons') && pln.propOpt.runDAO
+
+ showWarning(this,['Observe: You are running direct aperture optimization' filesep 'This is experimental code that has not been thoroughly debugged - especially in combination with constrained optimization.']); % was assigned to handles WHY ?
+ [resultGUI,usedOptimizer] = matRad_directApertureOptimization(evalin('base','dij'),evalin('base','cst'),...
+ resultGUI.apertureInfo,resultGUI,pln);
+ assignin('base','resultGUI',resultGUI);
+ % check IPOPT status and return message for GUI user
+ CheckOptimizerStatus(this,usedOptimizer,'DAO');
+ end
+
+
+ if strcmp(pln.radiationMode,'photons') && (pln.propSeq.runSequencing || pln.propOpt.runDAO)
+
+ matRad_visApertureInfo(resultGUI.apertureInfo);
+ end
+
+ catch ME
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+ showError(this,'OptimizeCallback: Could not perform direct aperture optimization',ME);
+ return;
+ end
+
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+
+ this.changedWorkspace('resultGUI');
+ %getFromWorkspace(this);
+ end
+
+ % H77 Callback
+ function btnLoadDicom_Callback(this, hObject, event)
+ % hObject handle to btnLoadDicom (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ handles = this.handles;
+ try
+ % delete existing workspace - parse variables from base workspace
+ AllVarNames = evalin('base','who');
+ RefVarNames = {'ct','cst','pln','stf','dij','resultGUI'};
+ for i = 1:length(RefVarNames)
+ if sum(ismember(AllVarNames,RefVarNames{i}))>0
+ evalin('base',['clear ', RefVarNames{i}]);
+ end
+ end
+ matRad_importDicomWidget;
+
+ catch ME
+ showError(this,'DicomImport: Could not import data', ME);
+ end
+
+ this.handles = handles;
+
+ end
+
+ % H78 Callback - button: refresh
+ function btnRefresh_Callback(this, hObject, event)
+ % notify so all widgets refresh
+ this.changedWorkspace();
+ end
+
+
+ % H79 Callback
+ function pushbutton_recalc_Callback(this, hObject, eventdata)
+
+ handles = this.handles;
+
+ try
+ % indicate that matRad is busy
+ % change mouse pointer to hour glass
+ Figures = gcf;
+ set(Figures, 'pointer', 'watch');
+ drawnow;
+ % disable all active objects
+ InterfaceObj = findobj(Figures,'Enable','on');
+ set(InterfaceObj,'Enable','off');
+
+ % get all data from workspace
+ pln = evalin('base','pln');
+ stf = evalin('base','stf');
+ ct = evalin('base','ct');
+ cst = evalin('base','cst');
+ resultGUI = evalin('base','resultGUI');
+
+
+ if sum([stf.totalNumOfBixels]) ~= length(resultGUI.w)%(['w' Suffix]))
+ this.showWarning('weight vector does not corresponding to current steering file');
+ return
+ end
+
+ % change isocenter if that was changed and do _not_ recreate steering
+ % information
+ for i = 1:numel(pln.propStf.gantryAngles)
+ stf(i).isoCenter = pln.propStf.isoCenter(i,:);
+ end
+
+ resultGUIreCalc = matRad_calcDoseForward(ct,cst,stf,pln,resultGUI.w);
+
+ % delete old variables to avoid confusion
+ if isfield(resultGUI,'effect')
+ resultGUI = rmfield(resultGUI,'effect');
+ resultGUI = rmfield(resultGUI,'RBExD');
+ resultGUI = rmfield(resultGUI,'RBE');
+ resultGUI = rmfield(resultGUI,'alpha');
+ resultGUI = rmfield(resultGUI,'beta');
+ end
+
+ if isfield(resultGUI,'LET')
+ resultGUI = rmfield(resultGUI,'LET');
+ end
+
+ % overwrite the "standard" fields
+ sNames = fieldnames(resultGUIreCalc);
+ for j = 1:length(sNames)
+ resultGUI.(sNames{j}) = resultGUIreCalc.(sNames{j});
+ end
+
+ % assign results to base worksapce
+ %assignin('base','dij',dij);
+ assignin('base','resultGUI',resultGUI);
+
+
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+
+ this.handles = handles;
+ this.changedWorkspace('resultGUI');
+
+ catch ME
+ % change state from busy to normal
+ set(Figures, 'pointer', 'arrow');
+ set(InterfaceObj,'Enable','on');
+ this.handles = handles;
+ showError(this,'CalcDoseCallback: Error in dose recalculation!',ME);
+ return;
+
+ end
+
+ end
+
+ % H80 Callback
+ function btnSaveToGUI_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ Width = 400;
+ Height = 200;
+ ScreenSize = get(0,'ScreenSize');
+
+ % show "Provide result name" window
+ figHandles = get(0,'Children');
+ if ~isempty(figHandles)
+ IdxHandle = strcmp(get(figHandles,'Name'),'Provide result name');
+ else
+ IdxHandle = [];
+ end
+
+ %check if window is already exists
+ if any(IdxHandle)
+ figDialog = figHandles(IdxHandle);
+ %set focus
+ figure(figDialog);
+ else
+ figDialog = dialog('Position',[ceil(ScreenSize(3)/2) ceil(ScreenSize(4)/2) Width Height],'Name','Provide result name','Color',[0.5 0.5 0.5]);
+
+ uicontrol('Parent',figDialog,...
+ 'Style','text',...
+ 'Position',[20 Height - (0.35*Height) 350 60],...
+ 'String','Please provide a decriptive name for your optimization result:','FontSize',10,'BackgroundColor',[0.5 0.5 0.5]);
+
+ try
+ pln = evalin('base','pln');
+ numOfBeams = pln.propStf.numOfBeams;
+ radMode = pln.radiationMode;
+ fractions = pln.numOfFractions;
+
+ saveString = sprintf('%s_%dbeams_%dfrac',radMode,numOfBeams,fractions);
+ catch
+ saveString = datestr(now,'mmddyyHHMM');
+ end
+
+ hFocus = uicontrol('Parent',figDialog,...
+ 'Style','edit',...
+ 'Position',[30 60 350 60],...
+ 'String',saveString,'FontSize',10,'BackgroundColor',[0.55 0.55 0.55],...
+ 'Callback', @(hpb,eventdata)SaveResultToGUI(this,hpb,eventdata));
+
+ uicontrol('Parent', figDialog,'Style', 'pushbutton', 'String', 'Save','FontSize',10,...
+ 'Position', [0.42*Width 0.1 * Height 70 30],...
+ 'Callback', @(hpb,eventdata)SaveResultToGUI(this,hpb,eventdata));
+
+ uicontrol(hFocus);
+ end
+
+ uiwait(figDialog);
+ this.handles = handles;
+
+ end
+
+ function SaveResultToGUI(this, ~, ~)
+ AllFigHandles = get(0,'Children');
+ ixHandle = strcmp(get(AllFigHandles,'Name'),'Provide result name');
+ uiEdit = get(AllFigHandles(ixHandle),'Children');
+
+
+ % delete special characters
+ Suffix = get(uiEdit(2),'String');
+ logIx = isstrprop(Suffix,'alphanum');
+ Suffix = ['_' Suffix(logIx)];
+
+ pln = evalin('base','pln');
+ resultGUI = evalin('base','resultGUI');
+
+ if isfield(resultGUI,'physicalDose')
+ resultGUI.(['physicalDose' Suffix]) = resultGUI.physicalDose;
+ end
+ if isfield(resultGUI,'w')
+ resultGUI.(['w' Suffix]) = resultGUI.w;
+ end
+
+ if isfield(resultGUI,'LET')
+ resultGUI.(['LET' Suffix]) = resultGUI.LET;
+ end
+
+
+ if isfield(pln,'bioParam') && ~strcmp(pln.bioParam.quantityOpt,'none')
+
+ if isfield(resultGUI,'RBExD')
+ resultGUI.(['RBExD' Suffix]) = resultGUI.RBExD;
+ end
+
+ if strcmp(pln.radiationMode,'carbon') == 1
+ if isfield(resultGUI,'effect')
+ resultGUI.(['effect' Suffix])= resultGUI.effect;
+ end
+
+ if isfield(resultGUI,'RBE')
+ resultGUI.(['RBE' Suffix]) = resultGUI.RBE;
+ end
+ if isfield(resultGUI,'alpha')
+ resultGUI.(['alpha' Suffix]) = resultGUI.alpha;
+ end
+ if isfield(resultGUI,'beta')
+ resultGUI.(['beta' Suffix]) = resultGUI.beta;
+ end
+ end
+ end
+
+ close(AllFigHandles(ixHandle));
+ assignin('base','resultGUI',resultGUI);
+
+ this.changedWorkspace('resultGUI');
+ %getFromWorkspace(this);
+ end
+
+ % H81 Callback
+ function btn_export_Callback(this, hObject, eventdata)
+ % hObject handle to btn_export (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ try
+ matRad_exportWidget;
+ catch ME
+ showError(this,'Could not export data. Reason: ', ME);
+ end
+ end
+
+ % H82 Callback
+ function importDoseButton_Callback(this, hObject, eventdata)
+ % hObject handle to importDoseButton (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ handles = this.handles;
+
+ extensions{1} = '*.nrrd';
+ [filenames,filepath,~] = uigetfile(extensions,'MultiSelect','on');
+
+ %Import aborted
+ if filenames == 0
+ return;
+ end
+
+ %Something was selected
+ try
+ if ~iscell(filenames)
+ tmp = filenames;
+ filenames = cell(1);
+ filenames{1} = tmp;
+ end
+
+ ct = evalin('base','ct');
+ resultGUI = evalin('base','resultGUI');
+
+ for filename = filenames
+ [~,name,~] = fileparts(filename{1});
+ [cube,~] = matRad_readCube(fullfile(filepath,filename{1}));
+ if ~isequal(ct.cubeDim, size(cube))
+ this.showError('Dimensions of the imported cube do not match with ct','Import failed!','modal');
+ continue;
+ end
+
+ fieldname = ['import_' matlab.lang.makeValidName(name, 'ReplacementStyle','delete')];
+ resultGUI.(fieldname) = cube;
+ end
+
+ assignin('base','resultGUI',resultGUI);
+ catch ME
+ this.handles = handles;
+ showError(this,'Dose Import: Could not import data. Reason: ', ME);
+ return;
+ end
+ this.handles = handles;
+ this.changedWorkspace('resultGUI');
+ %getFromWorkspace(this);
+ end
+
+ % H83 Callback
+ function pushbutton_importFromBinary_Callback(this, hObject, eventdata)
+ % hObject handle to pushbutton_importFromBinary (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ handles = this.handles;
+
+ try
+ %call the gui
+ h=matRad_importWidget;
+ uiwait(h.widgetHandle);
+
+ this.handles = handles;
+ this.changedWorkspace();
+ catch ME
+ this.handles = handles;
+ getFromWorkspace(this);
+ showError(this,'Binary Patient Import: Could not import data. Reason: ', ME);
+ return;
+ end
+
+
+ %getFromWorkspace(this);
+ end
+
+ % H84 Callback
+ function exportDicomButton_Callback(this, hObject, eventdata)
+ % hObject handle to exportDicom (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ try
+ matRad_exportDicomWidget;
+ catch ME
+ showError(this,'DicomImport: Could not export data', ME);
+ end
+ end
+
+ function CheckOptimizerStatus(this, usedOptimizer,OptCase)
+
+ [statusmsg,statusflag] = usedOptimizer.GetStatus();
+
+ if statusflag == 0 || statusflag == 1
+ statusIcon = 'none';
+ else
+ statusIcon = 'warn';
+ end
+
+ this.showMessage(sprintf('Optimizer finished with status %d (%s)',statusflag,statusmsg),'Optimization finished!',statusIcon,'modal');
+ end
+ end
+end
+
+
diff --git a/matRad/gui/widgets/matRad_exportDicomWidget.m b/matRad/gui/widgets/matRad_exportDicomWidget.m
new file mode 100644
index 000000000..c4b89f998
--- /dev/null
+++ b/matRad/gui/widgets/matRad_exportDicomWidget.m
@@ -0,0 +1,303 @@
+classdef matRad_exportDicomWidget < matRad_Widget
+ % matRad_exportDicomWidget class to generate GUI widget to export plan
+ % to dicom files
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ variables = {'ct','cst','resultGUI'}; %variables to export
+ end
+
+ methods
+ function this = matRad_exportDicomWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+ handleParent = figure(...
+ 'IntegerHandle','off',...
+ 'MenuBar','none',...
+ 'NumberTitle','off',...
+ 'PaperUnits','inches',...
+ 'Position', [450 170 480 350],...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'Name','Export Dicom',...
+ 'Tag','figure1');
+ end
+ this = this@matRad_Widget(handleParent);
+
+ update(this);
+ end
+ function this = initialize(this)
+
+ end
+ end
+
+
+ methods (Access = protected)
+
+ function this = createLayout(this)
+
+ h1 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %EXPORT BUTTON
+ h2 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Export',...
+ 'UserData',[],...
+ 'Position',[0.75 0.1 0.2 0.1],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'TooltipString', 'Export selected variables to selected folder',...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','btn_export',...
+ 'Callback',@this.btn_export_Callback);
+
+ %TEXT
+ h6 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'FontUnits',get(0,'defaultuicontrolFontUnits'),...
+ 'HorizontalAlignment','left',...
+ 'String','Select export folder:',...
+ 'TooltipString', 'Select the folder you want to export to',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Style','text',...
+ 'Position',[0.035 0.8 0.7 0.1 ],...
+ 'Tag','label_dir_export');
+
+ %BROWSER PUSHBUTTON
+ h7 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'String','Browse',...
+ 'TooltipString', 'Choose the export directory',...
+ 'Position',[0.75 0.75 0.2 0.1],...
+ 'Callback',@this.pushbutton_dir_export_browse_Callback,...
+ 'Tag','pushbutton_dir_export_browse');
+
+ %EDIT TEXTFELD
+ h8 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'Style','edit',...
+ 'String',fullfile(matRad_cfg.primaryUserFolder,'dicomExport'),...
+ 'Position',[0.035 0.75 0.7 0.1 ],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'TooltipString', 'Export path',...
+ 'Callback',@this.edit_dir_export_Callback,...
+ 'Tag','edit_dir_export');
+
+
+ %Text Variable to export
+ h10 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'FontUnits',get(0,'defaultuicontrolFontUnits'),...
+ 'HorizontalAlignment','left',...
+ 'String','Select variables to export:',...
+ 'TooltipString', 'Select the variables you want to export',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Style','text',...
+ 'Position',[0.035 0.63 0.7 0.04 ],...
+ 'Tag','label_variables');
+
+ % Table variables to export
+ h11 = uitable(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'ColumnWidth',{ 20 278 },...
+ 'Position',[0.035 0.3 0.915 0.3],...
+ 'ColumnEditable',[true false],...
+ 'ColumnFormat',{ 'logical' 'char' },...
+ 'RowName',{},...
+ 'ColumnName',{},...
+ 'Tag','uitable_variables');
+
+ set(h11,'Units','pixels');
+ pos = get(h11,'Position');
+ set(h11,'ColumnWidth',{20 pos(3)-39});
+
+ this.createHandles();
+ end
+
+ function this = doUpdate(this,evt)
+ doUpdate = true;
+ if nargin == 2
+ %At pln changes and at cst/cst (for Isocenter and new settings)
+ %we need to update
+ doUpdate = this.checkUpdateNecessary(this.variables,evt);
+ end
+
+ if doUpdate
+ handles = this.handles;
+
+ % load table with available variables that can be exported
+ vExists=false(1,numel(this.variables));
+ for i= 1:numel(this.variables)
+ var= char(this.variables(i));
+ vExists(i) = evalin('base',['exist(''' var ''',''var'')']);
+ end
+
+ if find(vExists,1) % not empty
+ tableData(:,2)= this.variables(vExists);
+ tableData(:,1) ={true};
+ set(handles.uitable_variables,'ColumnEditable',[true,false]);
+ else
+ tableData(1,2) = {'No variables to export'};
+ set(handles.btn_export,'Enable','off');
+ set(handles.uitable_variables,'ColumnEditable',[false,false]);
+ end
+ set(handles.uitable_variables,'data',tableData);
+
+ this.handles = handles;
+ end
+ end
+
+ end
+
+
+ %CALLBACK METHODS
+ methods
+ % CALLBACK FOR H2 BUTTON EXPORT
+ function this = btn_export_Callback(this, hObject, event)
+ handles = this.handles;
+
+ exportDir = get(handles.edit_dir_export,'String');
+
+ %Sanity check-
+ if numel(exportDir) == 0
+ this.showError('No Export folder selected!');
+ return;
+ elseif ~exist(exportDir,'dir')
+ try
+ mkdir(exportDir);
+ catch ME
+ this.showError(['Folder ' exportDir ' does not exist!'],ME);
+ end
+ else
+ %Add file separator if necessary
+ if exportDir(end) ~= filesep
+ exportDir = [exportDir filesep];
+ end
+ this.handles = handles;
+ end
+
+ try
+ dcmExport = matRad_DicomExporter;
+ dcmExport.dicomDir = exportDir;
+
+ varData = get(handles.uitable_variables,'Data');
+ var_selected=false;
+ for i= 1:size(varData,1)
+ if varData{i,1} == true
+ var_selected=true;
+ switch varData{i,2}
+ case 'ct'
+ dcmExport.matRad_exportDicomCt();
+ case 'cst'
+ dcmExport.matRad_exportDicomRTStruct();
+ case 'resultGUI'
+ dcmExport.matRad_exportDicomRTDoses();
+ end
+ end
+ end
+ catch ME
+ this.showWarning('couldn''t export! Reason: %s\n',ME.message)
+ end
+
+
+ if ~var_selected
+ this.showWarning('No variables selected!');
+ return;
+ end
+
+
+ end
+
+ %------------CALLBACK FOR H3 BUTTON CANCEL
+ function this = btn_cancel_Callback(this, hObject, event, guidata)
+ close;
+ % close(handles.figure1);
+ end
+
+
+ %-------------CALLBACK FOR H7 PUSHBUTTON DIR EXPORT BROWSE
+ function this = pushbutton_dir_export_browse_Callback(this, hObject, event)
+ handles = this.handles;
+
+ exportDir = uigetdir('', 'Choose the export directory...');
+ if exportDir ~= 0
+ exportDir = [exportDir filesep];
+ set(handles.edit_dir_export,'String',exportDir);
+ % Update handles structure
+ this.handles = handles;
+ end
+ end
+
+ %------------CALLBACK FOR H8 EDIT DIR EXPORT
+ function this = edit_dir_export_Callback(this, hObject, event)
+ handles = this.handles;
+
+ exportDir = get(handles.edit_dir_export,'String');
+
+ %Add filesperator
+ if exportDir(end) ~= filesep
+ exportDir = [exportDir filesep];
+ end
+
+ %Check if the user specified an existing directory
+ if ~exist(exportDir,'dir')
+ this.showWarning(['Folder ' exportDir ' does not exist!']);
+ exportDir = '';
+ end
+
+ set(handles.edit_dir_export,'String',exportDir);
+ this.handles = handles;
+ end
+
+ end
+end
+
+
+
diff --git a/matRad/gui/widgets/matRad_exportWidget.m b/matRad/gui/widgets/matRad_exportWidget.m
new file mode 100644
index 000000000..0054f22ec
--- /dev/null
+++ b/matRad/gui/widgets/matRad_exportWidget.m
@@ -0,0 +1,561 @@
+classdef matRad_exportWidget < matRad_Widget
+
+ % matRad_exportWidget class to generate GUI widget to export plan as
+ % dicom, nrrd etc.
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ end
+
+ methods
+ function this = matRad_exportWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+ handleParent = figure(...
+ 'IntegerHandle','off',...
+ 'MenuBar','none',...
+ 'NumberTitle','off',...
+ 'PaperUnits','inches',...
+ 'Position', [450 170 440 500],...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'Name','Export Patient',...
+ 'Tag','figure1');
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+ end
+
+ methods (Access = protected)
+ function this = doUpdate(this,evt)
+
+ updateNeeded = true;
+ if nargin == 2
+ updateNeeded = this.checkUpdateNecessary({'resultGUI','ct','cst'},evt);
+ end
+
+ if ~updateNeeded
+ return;
+ end
+
+ handles = this.handles;
+ % handles = guidata(this.widgetHandle);
+
+ %Fills structure export table
+ if evalin('base','exist(''cst'',''var'')') == 1
+ cst = evalin( 'base', 'cst' );
+ tableData = cell(numel(cst(:,2)),2);
+ tableData(:,2) = cst(:,2);
+ tableData(:,1) = {true};
+ else
+ tableData = cell(0);
+ set(handles.checkbox_CT,'Enable','off');
+ end
+ set(handles.uitable_vois,'data',tableData);
+
+ %Fills result cubes export table
+ if evalin('base','exist(''resultGUI'',''var'')')
+ result = evalin( 'base', 'resultGUI' );
+ cubeNames = fieldnames(result);
+ cubeIx = 1;
+ for f = 1:numel(cubeNames)
+ if ndims(result.(cubeNames{f})) < 3
+ continue;
+ end
+ cubes{cubeIx} = cubeNames{f};
+ cubeIx = cubeIx + 1;
+ end
+ numCubes = cubeIx - 1;
+ tableData = cell(numCubes,2);
+ tableData(:,2) = cubes;
+ tableData(:,1) = {true};
+ else
+ tableData = cell(0);
+ set(handles.checkbox_dose,'Enable','off'); %CHANGED CODE!ALTE VERSION: set(handles.checkbox_dose,'Enable','off');
+ end
+ set(handles.uitable_doseCubes,'data',tableData);
+
+ % Update handles structure
+ this.handles = handles;
+ end
+
+ function this = createLayout(this)
+
+ h1 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %EXPORT BUTTON
+ h2 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Export',...
+ 'TooltipString', 'Export selected quantites to selected folder',...
+ 'UserData',[],...
+ 'Position',[0.75 0.025 0.2 0.05],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','btn_export',...
+ 'Callback',@this.btn_export_Callback);
+
+ %CT CHECKBOX
+ h4 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','CT',...
+ 'TooltipString', 'Export CT of selected structures',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Style','checkbox',...
+ 'Position',[0.035 0.8 0.7 0.05],...
+ 'Callback',@this.checkbox_CT_Callback,...
+ 'Tag','checkbox_CT');
+
+
+ %Compress CHECKBOX
+ h14 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Compress',...
+ 'TooltipString', 'Export compressed data',...
+ 'Style','checkbox',...
+ 'Value',1,...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Position',[0.035 0.13 0.7 0.05],...
+ 'Tag','checkbox_compress',...
+ 'Callback',@this.checkbox_Compress_Callback);
+
+
+ % RESULT CUBES CHECKBOX
+ h5 = uicontrol(...
+ 'Parent',h1,...
+ 'HorizontalAlignment','left',...
+ 'Units','normalized',...
+ 'String','Result Cubes',...
+ 'TooltipString', 'Export selected result cubes',...
+ 'Style','checkbox',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Position',[0.035 0.45 0.7 0.04],...
+ 'Callback',@this.checkbox_dose_Callback,...
+ 'Tag','checkbox_dose');
+
+ %TEXT
+ h6 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Select export folder:',...
+ 'TooltipString', 'Select the folder you want to export to',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Style','text',...
+ 'Position',[0.035 0.925 0.915 0.05 ],...
+ 'Tag','label_dir_export');
+
+ %BROWSER PUSHBUTTON
+ h7 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'String','Browse',...
+ 'TooltipString', 'Choose the export directory',...
+ 'Position',[0.75 0.875 0.2 0.05],...
+ 'Callback',@this.pushbutton_dir_export_browse_Callback,...
+ 'Tag','pushbutton_dir_export_browse');
+
+ %EDIT TEXTFELD
+ h8 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'Style','edit',...
+ 'Position',[0.035 0.875 0.7 0.05 ],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@this.edit_dir_export_Callback,...
+ 'TooltipString', 'Export path',...
+ 'String',fullfile(matRad_cfg.primaryUserFolder,'export'),...
+ 'Tag','edit_dir_export');
+
+ %EXTENSION TEXT
+ h9 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Extension',...
+ 'TooltipString', 'Select file format',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Style','text',...
+ 'Position',[0.035 0.225 0.7 0.05],...
+ 'Tag','text_extension');
+
+ [~,writers] = matRad_supportedBinaryFormats();
+
+ % DROPDOWN MENU
+ h10 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String',{writers.fileFilter},...
+ 'TooltipString', 'File format',...
+ 'Style','popupmenu',...
+ 'Value',1,...
+ 'Position',[0.035 0.21 0.915 0.03],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@this.popupmenu_extension_Callback,...
+ 'Tag','popupmenu_extension');
+
+ % CT-TABLE
+ h11 = uitable(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'ColumnWidth',{20 400},...
+ 'Position',[0.035 0.5 0.915 0.3],...
+ 'ColumnEditable',[true false],...
+ 'ColumnFormat',{ 'logical' 'char' },...
+ 'ColumnName',{},...
+ 'RowName',{},...
+ 'Enable','off',...
+ 'Tag','uitable_vois');
+
+ set(h11,'Units','pixels');
+ pos = get(h11,'Position');
+ set(h11,'ColumnWidth',{20 pos(3)-39});
+
+
+ %RESLT CUBES-TABLES
+ h12 = uitable(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'ColumnName',{},...
+ 'ColumnWidth',{20 400},...
+ 'RowName',{},...
+ 'Position',[0.035 0.3 0.915 0.15],...
+ 'ColumnEditable',[true false],...
+ 'ColumnFormat',{ 'logical' 'char' },...
+ 'Tag','uitable_doseCubes',...
+ 'Enable','off',...
+ 'UserData',[]);
+
+ set(h12,'Units','pixels');
+ pos = get(h12,'Position');
+ set(h12,'ColumnWidth',{20 pos(3)-39});
+
+ this.createHandles();
+ end
+ end
+
+
+ %CALLBACK METHODAS
+ methods
+ %---------------CALLBACK FOR H2 BUTTON EXPORT
+ function this = btn_export_Callback(this, hObject, event)
+
+ matRad_cfg = MatRad_Config.instance();
+
+ handles = this.handles;
+
+ exportDir = get(handles.edit_dir_export,'String');
+
+ %Sanity check
+ if numel(exportDir) == 0
+ this.showError('No Export folder selected!');
+ return;
+ elseif ~exist(exportDir,'dir')
+ try
+ mkdir(exportDir);
+ catch ME
+ this.showError(['Folder ' exportDir ' does not exist!'],ME);
+ end
+ else
+ %Add file separator if necessary
+ if exportDir(end) ~= filesep
+ exportDir = [exportDir filesep];
+ end
+ this.handles = handles;
+ end
+
+ %Get the file extension
+ extensionIndex = get(handles.popupmenu_extension,'Value');
+ extensions = get(handles.popupmenu_extension,'String');
+ extension = extensions{extensionIndex};
+ extension = extension(2:end);
+
+ saveCT = get(handles.checkbox_CT,'Value');
+ saveResults = get(handles.checkbox_dose,'Value');
+
+ if (saveCT)
+ voiDir = [exportDir '/vois/'];
+ if ~exist(voiDir,'dir')
+ if ~mkdir(voiDir)
+ this.showWarning('Could not create subfolder for VOI masks. Masks will be stored in base folder.');
+ voiDir = exportDir;
+ end
+ end
+ end
+
+ %If we export results, try to create a subdirectory for VOIs
+ if (saveResults)
+ resultDir = [exportDir '/results/'];
+ if ~exist(resultDir,'dir')
+ if ~mkdir(resultDir)
+ this.showWarning('Could not create subfolder for resulting dose cubes. Cubes will be stored in base folder.');
+ resultDir = exportDir;
+ end
+ end
+ end
+
+ try
+ %prepare metadata
+ ct = evalin('base','ct');
+
+ metadata.resolution = [ct.resolution.x ct.resolution.y ct.resolution.z];
+ metadata.compress = get(handles.checkbox_compress,'Value');
+
+ %Check if we have position information
+ if isfield(ct,'dicomInfo')
+ if isfield(ct.dicomInfo,'ImagePositionPatient')
+ metadata.imageOrigin = ct.dicomInfo.ImagePositionPatient;
+ if ~isrow(metadata.imageOrigin)
+ metadata.imageOrigin = transpose(metadata.imageOrigin);
+ end
+ end
+ end
+
+ %This is only for the waitbar to get the number of cubes you wanna save
+ numExportCubes = 0;
+ if (saveCT)
+ if isfield(ct,'cubeHU')
+ numExportCubes = numExportCubes + 1;
+ end
+
+ if isfield(ct,'cube')
+ numExportCubes = numExportCubes + 1;
+ end
+ voiNames = get(handles.uitable_vois,'Data');
+ voiIndices = find([voiNames{:,1}] == true);
+ numExportCubes = numExportCubes + numel(voiIndices);
+
+ else
+ numExportCubes = 0;
+ end
+
+ if saveResults
+ cubeNames = get(handles.uitable_doseCubes,'data');
+ cubeIndices = find([cubeNames{:,1}] == true);
+ numExportCubes = numExportCubes + numel(cubeIndices);
+ end
+
+ %Give an error if nothing was selected
+ if numExportCubes == 0
+ this.showError('No data was selected for export!');
+ return;
+ end
+
+ currentCube = 0;
+
+ hWaitbar = waitbar(0,'Exporting...','WindowStyle', 'modal','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor);
+ matRad_applyThemeToWaitbar(hWaitbar);
+ cleanUp = onCleanup(@() close(hWaitbar));
+
+ %CT and Mask export
+ if saveCT
+
+ if isfield(ct,'cube')
+ %Export the CT (ED suffix to clarify it is not in HU)
+ currentCube = currentCube + 1;
+ waitbar(currentCube/numExportCubes,hWaitbar,['Exporting CT Intensity values (' num2str(currentCube) '/' num2str(numExportCubes) ') ...']);
+ matRad_writeCube(fullfile(exportDir,['CT_ED' extension]),ct.cube{1},'double',metadata);
+ end
+
+ if isfield(ct,'cubeHU')
+ currentCube = currentCube + 1;
+ waitbar(currentCube/numExportCubes,hWaitbar,['Exporting CT in HU (' num2str(currentCube) '/' num2str(numExportCubes) ') ...']);
+ matRad_writeCube(fullfile(exportDir,['CT_HU' extension]),ct.cubeHU{1},'double',metadata);
+ end
+
+ %Export VOI masks
+ cst = evalin('base','cst');
+
+ for voiIx = voiIndices
+ %Waitbar
+ currentCube = currentCube + 1;
+ waitbar(currentCube/numExportCubes,hWaitbar,['Exporting Segmentation Mask (' num2str(currentCube) '/' num2str(numExportCubes) ') ...']);
+
+ %Get the index list
+ voiRow = find(strcmp(voiNames{voiIx,2},cst(:,2)));
+ voiIndexList = cst{voiRow,4}{1};
+ %Set up the full mask
+ voiMask = zeros(ct.cubeDim);
+ voiMask(voiIndexList) = 1;
+ %Export...
+ matRad_writeCube(fullfile(voiDir,[voiNames{voiIx,2} extension]),voiMask,'uint8',metadata);
+ end
+
+ end
+ catch ME
+ this.showWarning('couldn''t export! Reason:', ME)
+ end
+ %Results Export
+ if saveResults
+ results = evalin('base','resultGUI');
+ cubeNames = get(handles.uitable_doseCubes,'data');
+
+ for cubeIx = cubeIndices
+ %Export
+ currentCube = currentCube + 1;
+ waitbar(currentCube/numExportCubes,hWaitbar,['Exporting Results (' num2str(currentCube) '/' num2str(numExportCubes) ') ...']);
+ matRad_writeCube(fullfile(resultDir,[cubeNames{cubeIx,2} extension]),results.(cubeNames{cubeIx,2}),'double',metadata);
+ end
+ end
+
+ %close(data.figure1);
+
+ end
+
+ %------------CALLBACK FOR H3 BUTTON CANCEL
+ function this = btn_cancel_Callback(this, hObject, event, guidata)
+ close;
+ % close(handles.figure1);
+ end
+
+ %------------CALLBACK FOR H4 BUTTON CHECKBOX CT
+ function this = checkbox_CT_Callback(this, hObject, event)
+ handles = this.handles;
+ saveCT = get(hObject,'Value');
+ %Show the VOI-table only if we want to save a CT
+ if (saveCT)
+ set(handles.uitable_vois, 'Enable','on');
+ else
+ set(handles.uitable_vois,'Enable','off');
+ end
+ this.handles = handles;
+ end
+
+ %-------------CALLBACK FOR H5 BUTTON CHECKBOX DOSE
+ function this = checkbox_dose_Callback(this, hObject, event)
+ handles = this.handles;
+ saveDose = get(hObject,'Value');
+ if (saveDose)
+ set(handles.uitable_doseCubes, 'Enable','on');
+ %ORIGINAL: set(guidata.uitable_doseCubes,'Visible', 'on', 'Enable','on');
+
+ else
+ set(handles.uitable_doseCubes,'Enable','off');
+ %ORIGINAL: set(guidata.uitable_doseCubes,'Visible', 'off', 'Enable','off');
+
+ end
+ this.handles = handles;
+ end
+
+ %-------------CALLBACK FOR H7 PUSHBUTTON DIR EXPORT BROWSE
+ function this = pushbutton_dir_export_browse_Callback(this, hObject, event)
+ handles = this.handles;
+
+ path = get(handles.edit_dir_export,'String');
+
+ if ~exist(path,'dir')
+ matRad_cfg = MatRad_Config.instance();
+ path = matRad_cfg.userfolders{1};
+ end
+
+ exportDir = uigetdir(path, 'Choose the export directory...');
+ if exportDir ~= 0
+ exportDir = [exportDir filesep];
+ set(handles.edit_dir_export,'String',exportDir);
+ % Update handles structure
+ this.handles = handles;
+ end
+ end
+
+ %------------CALLBACK FOR H8 EDIT DIR EXPORT
+ function this = edit_dir_export_Callback(this, hObject, event)
+ handles = this.handles;
+
+ exportDir = get(handles.edit_dir_export,'String');
+
+ %Add filesperator
+ if exportDir(end) ~= filesep
+ exportDir = [exportDir filesep];
+ end
+
+ %Check if the user specified an existing directory
+ if ~exist(exportDir,'dir')
+ this.showWarning(['Folder ' exportDir ' does not exist!']);
+ exportDir = '';
+ end
+
+ set(handles.edit_dir_export,'String',exportDir);
+ this.handles = handles;
+ end
+
+ %------------CALLBACK FOR H10 DROPDOWN MENU EXTENSION
+ function this = popupmenu_extension_Callback(this, hObject, event)
+
+ end
+
+ %------------CALLBACK FOR H14 CHECKBOX COMPRESS
+ function this = checkbox_Compress_Callback(this, hObject, event)
+
+ end
+ end
+end
+
+
+
diff --git a/matRad/gui/widgets/matRad_importDicomWidget.m b/matRad/gui/widgets/matRad_importDicomWidget.m
new file mode 100644
index 000000000..dd55e339c
--- /dev/null
+++ b/matRad/gui/widgets/matRad_importDicomWidget.m
@@ -0,0 +1,986 @@
+classdef matRad_importDicomWidget < matRad_Widget
+ % matRad_importDicomWidget class to generate GUI widget to import dicom
+ % files
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ importer;
+ end
+
+ methods
+
+ function this = matRad_importDicomWidget(handleParent)
+ %MATRAD_IMPORTDICOMWIDGET Construct an instance of this class
+
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+ handleParent = figure(...
+ 'IntegerHandle','off',...
+ 'Units','characters',...
+ 'MenuBar','none',...
+ 'NumberTitle','off',...
+ 'Position',[25 10 220 40],...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'Name','Import Dicom',...
+ 'Tag','figure1');
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+
+
+
+ % INITIALIZE FUNCTION
+ function this = initialize(this)
+ handles = this.handles;
+ handles.output = handles;
+
+ [im, alpha] = matRad_getLogoDKFZ();
+ q = image(im,'Parent',handles.axesDKFZLogo);
+ axis(handles.axesDKFZLogo,'equal','off');
+ set(q, 'AlphaData', alpha);
+
+ % show dkfz logo
+ [im,alpha] = matRad_getLogo();
+
+ f = image(im,'Parent',handles.axesMatRadLogo);
+ axis(handles.axesMatRadLogo,'equal','off');
+ set(f, 'AlphaData', alpha);
+
+
+ % Update handles structure
+ % guidata(hObject, handles);
+ this.handles = handles;
+ end
+
+ end
+
+ % CALLBACKS
+ methods
+
+ % H15 BROWSER BUTTON CALLBACK
+ function patDir = browse_button_Callback(this, hObject, eventdata)
+ handles = this.handles;
+ patDir = uigetdir('', 'Choose the input directory...');
+ if patDir ~= 0
+ patDir = [patDir filesep];
+ %handles.dir_path_field.String = patDir;
+ set(handles.dir_path_field,'String',patDir);
+ % Update handles structure
+ % guidata(hObject, handles);
+ this.handles = handles;
+ this.scanFolder(hObject, eventdata)
+ end
+ end
+
+ % H17 PATIENT LISTBOX CALLBACK
+ function this = patient_listbox_Callback(this, hObject, eventdata)
+ handles = this.handles;
+ if ~isempty(get(hObject,'String'))
+ % enable Import button
+ set(handles.import_button,'Enable','on');
+
+ % handles.filelist:
+ % 1. Filepath
+ % 2. Modality
+ % 3. PatientID
+ % 4. SeriesUID
+ % 5. SeriesNumber
+ % 9. res_x
+ % 10. res_y
+ % 11. res_z
+ % 12. detailed dose description - currently not in use for GUI user
+ patient_listbox = get(handles.patient_listbox,'String');
+ if ~isempty(patient_listbox)
+ selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
+ end
+
+ if get(handles.SeriesUID_radiobutton,'Value') == 1
+ % this gets a list of ct series for this patient
+ set(handles.ctseries_listbox,'Value',1); % set dummy value to one
+
+ set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),4)));
+
+ else
+ set(handles.ctseries_listbox,'Value',1); % set dummy value to one
+ set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),5)));
+ end
+ % for i = 1:numel(this.handles.ctseries_listbox.String)
+ % if i == handles.ctseries_listbox.Value
+ % SeriesInstUID = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),15))
+ % if
+ %
+ % end
+ % end
+ % this gets a list of rtss series for this patient
+ set(handles.rtseries_listbox,'Value',1); % set dummy value to one
+ set(handles.rtseries_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTSTRUCT') & strcmp(handles.fileList(:,3), selected_patient),4));
+ % this gets a list of rt plan series for this patient
+ set(handles.rtplan_listbox,'Value',[]); % set dummy value to none
+ set(handles.rtplan_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTPLAN') & strcmp(handles.fileList(:,3), selected_patient),4));
+ % this gets a list of dose series for this patient
+ set(handles.doseseries_listbox,'Value',[]); % set dummy value to none
+ set(handles.doseseries_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTDOSE') & strcmp(handles.fileList(:,3), selected_patient),4));
+ % selectedDose
+
+ if get(handles.SeriesUID_radiobutton,'Value') == 1
+ % this gets a list of ct series for this patient
+ set(handles.ctseries_listbox,'Value',1); % set dummy value to one
+
+ set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),4)));
+ else
+ set(handles.ctseries_listbox,'Value',1); % set dummy value to one
+ set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),5)));
+ end
+ set(handles.resx_edit,'String',this.importer.importFiles.resx);
+ set(handles.resy_edit,'String',this.importer.importFiles.resy);
+ set(handles.resz_edit,'String',this.importer.importFiles.resz);
+ % Update handles structure
+ % guidata(hObject, handles);
+ this.handles = handles;
+ end
+ end
+
+ % H22 IMPORT BUTTON CALLBACK
+ function this = import_button_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ % case the resolution was changed manually
+ if handles.resx_edit.String ~= this.importer.importFiles.resx
+ this.importer.importFiles.resx = handles.resx_edit.String;
+ end
+ if handles.resy_edit.String ~= this.importer.importFiles.resy
+ this.importer.importFiles.resy = handles.resy_edit.String;
+ end
+ if handles.resz_edit.String ~= this.importer.importFiles.resz
+ this.importer.importFiles.resz = handles.resz_edit.String;
+ end
+
+ % to pass only selected objects to the matRad_importDicom
+ % selected patient
+ if ~isempty(handles.patient_listbox)
+ selected_patient = this.importer.patient{get(handles.patient_listbox,'Value')};
+ this.importer.patient = selected_patient;
+ end
+
+ % selected CT serie
+ allCTSeries = this.importer.importFiles.ct;
+ if ~isempty(handles.ctseries_listbox) && get(handles.SeriesUID_radiobutton,'Value') == 1
+ UIDSelected_ctserie = handles.ctseries_listbox.String{get(handles.ctseries_listbox,'Value'), 1};
+ selectedCTSerie = allCTSeries(strcmp(allCTSeries(:, 4), UIDSelected_ctserie), :);
+ this.importer.importFiles.ct = selectedCTSerie;
+ elseif ~isempty(handles.ctseries_listbox) && get(handles.SeriesNumber_radiobutton,'Value') == 1
+ SeriesNumSelected_ctserie = handles.ctseries_listbox.String{get(handles.ctseries_listbox,'Value'), 1};
+ selectedCTSerie = allCTSeries(strcmp(allCTSeries(:, 5), SeriesNumSelected_ctserie), :);
+ this.importer.importFiles.ct = selectedCTSerie;
+ end
+
+ % selected RTStruct
+ allRtss = this.importer.importFiles.rtss;
+ if ~isempty(allRtss)
+ UIDSelected_rtserie = handles.rtseries_listbox.String{get(handles.rtseries_listbox,'Value'), 1};
+ selectedRTStructSerie = allRtss(strcmp(allRtss(:, 4), UIDSelected_rtserie), :);
+ this.importer.importFiles.rtss = selectedRTStructSerie;
+ end
+
+ % selected RTPlan
+ allRTPlans = this.importer.importFiles.rtplan;
+ if ~isempty(allRTPlans) && ~isempty(handles.rtplan_listbox.Value)
+ UIDSelected_rtplan = handles.rtplan_listbox.String{get(handles.rtplan_listbox,'Value'), 1};
+ selectedRTPlan = allRTPlans(strcmp(allRTPlans(:, 4), UIDSelected_rtplan), :);
+ this.importer.importFiles.rtplan = selectedRTPlan;
+ else
+ this.importer.importFiles.rtplan = [];
+ end
+
+ % selected dose serie
+ allRTDoses = this.importer.importFiles.rtdose;
+ if ~isempty(allRTDoses) && ~isempty(handles.doseseries_listbox.Value)
+ UIDSelected_rtdose = handles.doseseries_listbox.String{get(handles.doseseries_listbox,'Value'), 1};
+ selectedRTDose = allRTDoses(strcmp(allRTDoses(:, 4), UIDSelected_rtdose), :);
+ this.importer.importFiles.rtdose = selectedRTDose;
+ else
+ this.importer.importFiles.rtdose = [];
+ end
+
+ this.importer.matRad_importDicom();
+
+ %% save ct, cst, pln, dose
+ matRad_cfg = MatRad_Config.instance();
+ matRadFileName = fullfile(matRad_cfg.userfolders{1},[this.importer.patient '.mat']); % use default from dicom
+ [FileName,PathName] = uiputfile('*.mat','Save as...',matRadFileName);
+ ct = this.importer.ct;
+ cst = this.importer.cst;
+ pln = this.importer.pln;
+ stf = this.importer.stf;
+ resultGUI = this.importer.resultGUI;
+ if ischar(FileName)
+ variableNames = {'ct','cst','pln','stf','resultGUI'};
+
+ varsToSave = cellfun(@(v) ~isempty(evalin('caller',v)),variableNames);
+
+ % delete unnecessary variables
+
+ save([PathName, FileName],variableNames{varsToSave},'-v7');
+ % save all except FileName and PathName
+
+ end
+
+ this.handles = handles;
+ end
+
+ % H23 CANCEL BUTTON CALLBACK
+ function this = cancel_button_Callback(this, hObject, eventdata)
+
+ close;
+ end
+
+ % RESCAN BUTTON CALLBACK; NICHT IN CREATELAYOUT VORHANDEN
+ function this = rescan_button_Callback(this, hObject, eventdata)
+ end
+
+ % H24 SERIES UID RADIOBUTTON CALLBACK
+ function this = SeriesUID_radiobutton_Callback(this, hObject, eventdata)
+ handles = this.handles;
+ if get(hObject,'Value') == 1
+ set(handles.SeriesNumber_radiobutton,'Value',0);
+ else
+ set(hObject,'Value',1);
+ set(handles.SeriesNumber_radiobutton,'Value',0);
+ end
+ if isfield(handles, 'fileList')
+ patient_listbox = get(handles.patient_listbox,'String');
+ selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
+ set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),4)));
+ set(handles.rtseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'RTSTRUCT') & strcmp(handles.fileList(:,3), selected_patient),4)));
+ set(handles.doseseries_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTDOSE') & strcmp(handles.fileList(:,3), selected_patient),4));
+ set(handles.rtplan_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'RTPLAN') & strcmp(handles.fileList(:,3), selected_patient),4)));
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('No patient loaded, so just switching default display option to SeriesUID. \n');
+ end
+ %guidata(hObject, handles);
+ this.handles = handles;
+ end
+
+ % H25 SERIESNUMBER RADIO BUTTON CALLBACK
+ function this = SeriesNumber_radiobutton_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ if get(hObject,'Value') == 1
+ set(handles.SeriesUID_radiobutton,'Value',0);
+ else
+ set(hObject,'Value',1);
+ set(handles.SeriesUID_radiobutton,'Value',0);
+ end
+ if isfield(handles, 'fileList')
+ patient_listbox = get(handles.patient_listbox,'String');
+ selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
+ % set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),5)));
+ set(handles.ctseries_listbox,'String',unique(cell2mat(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),5))));
+
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('No patient loaded, so just switching default display option to SeriesNumber. \n');
+ end
+ % guidata(hObject, handles);
+ this.handles = handles;
+ end
+
+
+ % H34 DOSESERIES LISTBOX CALLBACK
+ function this = doseseries_listbox_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ if ~isempty(get(hObject,'Value'))
+ set(handles.checkbox3,'Enable','on');
+ else
+ set(handles.checkbox3,'Value',0);
+ set(handles.checkbox3,'Enable','off');
+ % retrieve and display resolution for DICOM ct cube
+ set(handles.resx_edit,'String',this.importer.importFiles.resx);
+ set(handles.resy_edit,'String',this.importer.importFiles.resy);
+ set(handles.resz_edit,'String',this.importer.importFiles.resz);
+
+
+ end
+
+ this.handles = handles;
+ end
+
+ % H35 RTPLAN LISTBOX CALLBACK
+ function this = rtplan_listbox_Callback(this, hObject, eventdata)
+ handles = this.handles;
+
+ contents = cellstr(get(hObject,'String'));
+ if ~isempty(get(hObject,'Value')) && numel(get(hObject,'Value')) == 1
+
+ selectedPlan = contents{get(hObject,'Value')};
+ % point at plan in listbox
+ selectedPlanLoc = strcmp(handles.fileList(:,4),selectedPlan);
+
+ % show only the doses corresponding to the plan
+ corrDoses = [handles.fileList{selectedPlanLoc,13}];
+ numOfDoses = numel(corrDoses);
+ corrDosesLoc = zeros(size(handles.fileList(:,1),1),1);
+ for j = 1:numOfDoses
+ if ~isnan(corrDoses{j})
+ corrDosesLoc = corrDosesLoc | strcmp(handles.fileList(:,4),corrDoses{j});
+ end
+ end
+
+ if sum(corrDosesLoc) == 0
+ this.showWarning('no rt dose file directly associated to plan file. showing all rt dose files.');
+ corrDosesLoc = strcmp(handles.fileList(:,2),'RTDOSE');
+ end
+
+ set(handles.doseseries_listbox,'Value',[]); % set dummy value to one
+ set(handles.doseseries_listbox,'String',handles.fileList(corrDosesLoc,4));
+
+ % disable checkbox for use dose grid is currently checked
+ if get(handles.checkbox3,'Value') == 1
+ set(handles.checkbox3,'Value',0);
+ checkbox3_Callback(handles.checkbox3,[], handles);
+ end
+ set(handles.checkbox3,'Enable','off');
+
+ elseif numel(get(hObject,'Value')) >=2
+ this.showWarning('More than one RTPLAN selected. Unsetting selection ...');
+ patient_listbox_Callback(this, hObject, eventdata);
+ else
+ patient_listbox_Callback(this, hObject, eventdata);
+ end
+
+ this.handles = handles;
+
+ end
+
+ % H36 DIR PATH FIELD CALLBACK
+ function this = dir_path_field_Callback(this, hObject, eventdata)
+ handles = this.handles;
+ patDir = get(handles.dir_path_field,'String');
+ if patDir(end) ~= filesep;
+ patDir = [patDir filesep];
+ set(handles.dir_path_field,'String',patDir);
+ % guidata(hObject, handles);
+ this.handles = handles;
+ end
+ scanForder(hObject, eventdata);
+ end
+
+ % H37 CHECK PATIENTNAME CALLBACK
+ function this = checkPatientName_Callback(this, hObject, eventdata)
+ handles = this.handles;
+ % hObject handle to checkPatientName (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+ %A = get(hObject,'Value');
+
+ % Hint: get(hObject,'Value') returns toggle state of checkPatientName
+ %guidata(hObject, handles);
+ this.handles = handles;
+
+ end
+
+ % H38 CHECKBOX USE RT DOSE GRID CALLBACK
+ function this = checkUseRTdoseGrid_Callback(this, hObject, eventdata)
+ % hObject handle to checkbox3 (see GCBO)
+ % eventdata reserved - to be defined in a future version of MATLAB
+ % handles structure with handles and user data (see GUIDATA)
+
+ % Hint: get(hObject,'Value') returns toggle state of checkbox3
+ handles = this.handles;
+
+ if get(hObject,'Value')
+ set(handles.resx_edit,'Enable', 'off');
+ set(handles.resy_edit,'Enable', 'off');
+ set(handles.resz_edit,'Enable', 'off');
+ % retrieve and display resolution for DICOM dose cube
+ doseFilesInList = get(handles.doseseries_listbox,'String');
+ selectedDoseFiles = get(handles.doseseries_listbox,'Value');
+ if isempty(selectedDoseFiles)
+ set(hObject,'Value',0)
+ this.showError('no dose file selected');
+ return;
+ end
+ for i = 1:numel(selectedDoseFiles)
+ selectedDoseFile = doseFilesInList{selectedDoseFiles(i)};
+ if verLessThan('matlab','9')
+ dicomDoseInfo = dicominfo(handles.fileList{find(strcmp(handles.fileList(:,4),selectedDoseFile)),1});
+ else
+ dicomDoseInfo = dicominfo(handles.fileList{find(strcmp(handles.fileList(:,4),selectedDoseFile)),1},'UseDictionaryVR',true);
+ end
+ res_x{i} = dicomDoseInfo.PixelSpacing(1);
+ res_y{i} = dicomDoseInfo.PixelSpacing(2);
+ res_z{i} = dicomDoseInfo.SliceThickness;
+ end
+
+ if numel(unique(cell2mat(res_x)))*numel(unique(cell2mat(res_y)))*numel(unique(cell2mat(res_z))) ~= 1
+ set(handles.checkbox3,'Value',0);
+ this.showWarning('Different resolutions in dose file(s)');
+ set(handles.resx_edit,'Enable', 'on');
+ set(handles.resy_edit,'Enable', 'on');
+ set(handles.resz_edit,'Enable', 'on');
+ else
+ set(handles.resx_edit,'String',num2str(res_x{1}));
+ set(handles.resy_edit,'String',num2str(res_y{1}));
+ set(handles.resz_edit,'String',num2str(res_z{1}));
+ end
+
+ else
+ set(handles.resx_edit,'Enable', 'on');
+ set(handles.resy_edit,'Enable', 'on');
+ set(handles.resz_edit,'Enable', 'on');
+ % retrieve and display resolution for DICOM ct cube
+ patient_listbox = get(handles.patient_listbox,'String');
+ selected_patient = patient_listbox(get(handles.patient_listbox,'Value'));
+ selectedCtSeriesString = get(handles.ctseries_listbox,'String');
+ if get(handles.SeriesUID_radiobutton,'Value') == 1
+ if ~isempty(selectedCtSeriesString)
+ res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9));
+ res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10));
+ res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11));
+ else
+ res_x = NaN; res_y = NaN; res_z = NaN;
+ end
+ else
+ if ~isempty(selectedCtSeriesString)
+ res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9));
+ res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10));
+ res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11));
+ else
+ res_x = NaN; res_y = NaN; res_z = NaN;
+ end
+ end
+ set(handles.resx_edit,'String',res_x);
+ set(handles.resy_edit,'String',res_y);
+ set(handles.resz_edit,'String',res_z);
+
+ end
+
+ this.handles = handles;
+ end
+
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this,handleParent)
+
+ h1 = this.widgetHandle;
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Create all handles
+ % LOGO
+ h2 = axes(...
+ 'Parent',h1,...
+ 'CameraPosition',[0.5 0.5 9.16025403784439],...
+ 'CameraTarget',[0.5 0.5 0.5],...
+ 'CameraViewAngle',6.60861036031192,...
+ 'PlotBoxAspectRatio',[1 0.204545454545455 0.204545454545455],...
+ 'XTick',[0 0.2 0.4 0.6 0.8 1],...
+ 'XTickLabel',{ '0'; '0.2'; '0.4'; '0.6'; '0.8'; '1' },...
+ 'YTick',[0 0.5 1],...
+ 'YTickLabel',{ '0'; '0.5'; '1' },...
+ 'Position',[0.659070191431176 0.0633333333333333 0.320875113947129 0.13953488372093],...
+ 'ActivePositionProperty','position',...
+ 'LooseInset',[0.21882384176291 0.326703619171829 0.159909730519049 0.222752467617156],...
+ 'FontSize',8,...
+ 'SortMethod','childorder',...
+ 'Tag','axesDKFZLogo');
+
+
+ %MATRAD Logo
+ h7 = axes(...
+ 'Parent',h1,...
+ 'CameraPosition',[0.5 0.5 9.16025403784439],...
+ 'CameraTarget',[0.5 0.5 0.5],...
+ 'CameraViewAngle',6.60861036031192,...
+ 'PlotBoxAspectRatio',[1 0.301587301587302 0.301587301587302],...
+ 'Colormap',[0.2422 0.1504 0.6603;0.250390476190476 0.164995238095238 0.707614285714286;0.257771428571429 0.181780952380952 0.751138095238095;0.264728571428571 0.197757142857143 0.795214285714286;0.270647619047619 0.21467619047619 0.836371428571429;0.275114285714286 0.234238095238095 0.870985714285714;0.2783 0.255871428571429 0.899071428571429;0.280333333333333 0.278233333333333 0.9221;0.281338095238095 0.300595238095238 0.941376190476191;0.281014285714286 0.322757142857143 0.957885714285714;0.279466666666667 0.344671428571429 0.971676190476191;0.275971428571429 0.366680952380952 0.982904761904762;0.269914285714286 0.3892 0.9906;0.260242857142857 0.412328571428571 0.995157142857143;0.244033333333333 0.435833333333333 0.998833333333333;0.220642857142857 0.460257142857143 0.997285714285714;0.196333333333333 0.484719047619048 0.989152380952381;0.183404761904762 0.507371428571429 0.979795238095238;0.178642857142857 0.528857142857143 0.968157142857143;0.176438095238095 0.549904761904762 0.952019047619048;0.168742857142857 0.570261904761905 0.935871428571428;0.154 0.5902 0.9218;0.146028571428571 0.609119047619048 0.907857142857143;0.13802380952381 0.627628571428572 0.897290476190476;0.124814285714286 0.645928571428571 0.888342857142857;0.111252380952381 0.6635 0.876314285714286;0.0952095238095238 0.679828571428571 0.859780952380952;0.0688714285714285 0.694771428571429 0.839357142857143;0.0296666666666667 0.708166666666667 0.816333333333333;0.00357142857142857 0.720266666666667 0.7917;0.00665714285714287 0.731214285714286 0.766014285714286;0.0433285714285715 0.741095238095238 0.739409523809524;0.096395238095238 0.75 0.712038095238095;0.140771428571429 0.7584 0.684157142857143;0.1717 0.766961904761905 0.655442857142857;0.193766666666667 0.775766666666667 0.6251;0.216085714285714 0.7843 0.5923;0.246957142857143 0.791795238095238 0.556742857142857;0.290614285714286 0.797290476190476 0.518828571428572;0.340642857142857 0.8008 0.478857142857143;0.3909 0.802871428571428 0.435447619047619;0.445628571428572 0.802419047619048 0.390919047619048;0.5044 0.7993 0.348;0.561561904761905 0.794233333333333 0.304480952380953;0.617395238095238 0.787619047619048 0.261238095238095;0.671985714285714 0.779271428571429 0.2227;0.7242 0.769842857142857 0.191028571428571;0.773833333333333 0.759804761904762 0.164609523809524;0.820314285714286 0.749814285714286 0.153528571428571;0.863433333333333 0.7406 0.159633333333333;0.903542857142857 0.733028571428571 0.177414285714286;0.939257142857143 0.728785714285714 0.209957142857143;0.972757142857143 0.729771428571429 0.239442857142857;0.995647619047619 0.743371428571429 0.237147619047619;0.996985714285714 0.765857142857143 0.219942857142857;0.995204761904762 0.789252380952381 0.202761904761905;0.9892 0.813566666666667 0.188533333333333;0.978628571428571 0.838628571428572 0.176557142857143;0.967647619047619 0.8639 0.164290476190476;0.961009523809524 0.889019047619048 0.153676190476191;0.959671428571429 0.913457142857143 0.142257142857143;0.962795238095238 0.937338095238095 0.126509523809524;0.969114285714286 0.960628571428571 0.106361904761905;0.9769 0.9839 0.0805],...
+ 'XTick',[0 0.2 0.4 0.6 0.8 1],...
+ 'XTickLabel',{ '0'; '0.2'; '0.4'; '0.6'; '0.8'; '1' },...
+ 'YTick',[0 0.5 1],...
+ 'YTickLabel',{ '0'; '0.5'; '1' },...
+ 'Position',[0.125 0.85 0.229433272394881 0.147058823529412],...
+ 'ActivePositionProperty','position',...
+ 'LooseInset',[0.265885262648529 0.319135999073457 0.194300768858541 0.217592726640994],...
+ 'FontSize',8,...
+ 'SortMethod','childorder',...
+ 'Tag','axesMatRadLogo' );
+
+ % Import text
+ h33 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','DICOM Import',...
+ 'TooltipString', 'Import selected quantites from selected folder',...
+ 'Style','text',...
+ 'Position',[0.375 0.8325 0.609323583180987 0.1708823529411765],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'Children',[],...
+ 'ForegroundColor',[0.0980392156862745 0.305882352941176 0.615686274509804],...
+ 'Tag','text12',...
+ 'FontSize',56,...
+ 'FontName','Century');
+
+ %SelectRTPlan text
+ h12 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','RT Plan (SOPInstanceUID)',...
+ 'TooltipString', 'Select an RT plan',...
+ 'Style','text',...
+ 'Position',[0.319051959890611 0.677961527418892 0.152067622441369 0.0391903531438416],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text11');
+
+ % Text selectRT dose
+ h13 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','RT Dose (SOPInstanceUID)',...
+ 'TooltipString', 'Select an RT dose',...
+ 'Style','text',...
+ 'Position',[0.660893345487694 0.677961527418892 0.226377724372255 0.0391903531438416],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text10' );
+
+ %Text choose directory
+ h14 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Directory',...
+ 'TooltipString', 'Choose the import directory',...
+ 'Style','text',...
+ 'Position',[0.0438756855575868 0.790661764705882 0.0722120658135283 0.0386029411764706],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','directory_label');
+
+ % Browse button
+ h15 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Browse',...
+ 'TooltipString', 'Choose the input directory',...
+ 'Position',[0.913162705667276 0.748382352941176 0.0630712979890311 0.0404411764705882],...
+ 'Callback',@(hObject,event) browse_button_Callback(this,hObject,event),...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','browse_button' );
+
+ %Text Patient ID
+ h16 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','Patient ID',...
+ 'TooltipString', 'Choose a patient',...
+ 'Style','text',...
+ 'Position',[0.0447897623400366 0.654632352941177 0.0557586837294333 0.059917312661499],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','patient_label' );
+
+ % Listbox Choose patient
+ h17 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'Style','listbox',...
+ 'Value',1,...
+ 'Position',[0.0446672743846855 0.491627906976744 0.22971741112124 0.186046511627907],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) patient_listbox_Callback(this,hObject,event),...
+ 'Tag','patient_listbox',...
+ 'TooltipString', 'Choose a patient');
+
+ %Text CT series
+ h18 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','CT ( ',...
+ 'TooltipString', 'Choose CT series',...
+ 'Style','text',...
+ 'Position',[0.318223253501284 0.431622164800459 0.0464075578022706 0.0335917312661499],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','ct_label' );
+
+ %Listbox CT series
+ h19 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'Style','listbox',...
+ 'Value',1,...
+ 'Position',[0.318140382862352 0.247441860465116 0.320875113947128 0.186046511627907],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'TooltipString', 'Choose CT series',...
+ 'Tag','ctseries_listbox');
+
+ % Test RTstructure set
+ h20 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','RT Structure Set (SOPInstanceUID)',...
+ 'TooltipString', 'Choose RT structure set',...
+ 'Style','text',...
+ 'Position',[0.66172205187702 0.426023542922768 0.179000580094473 0.0391903531438415],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','struct_label');
+
+ %Listbox choose RT structure set
+ h21 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'Style','listbox',...
+ 'Value',1,...
+ 'Position',[0.660893345487694 0.245503875968992 0.320875113947129 0.186046511627907],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Children',[],...
+ 'TooltipString', 'Choose RT structure set',...
+ 'Tag','rtseries_listbox');
+
+ % Import Button
+ h22 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Import',...
+ 'Position',[0.181403828623519 0.233875968992248 0.0628988149498633 0.0406976744186047],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) import_button_Callback(this,hObject,event),...
+ 'Enable','off',...
+ 'Tag','import_button');
+
+ % Cancel button
+ h23 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Cancel',...
+ 'Position',[0.181403828623519 0.185736434108527 0.0628988149498633 0.0406976744186047],...
+ 'Callback',@(hObject,event) cancel_button_Callback(this,hObject,event),...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','cancel_button' );
+
+ % radiobutton to choose Series UID
+ h24 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Series Instance UID /',...
+ 'TooltipString', 'Choose a series instance UID',...
+ 'Style','radiobutton',...
+ 'Value',1,...
+ 'Position',[0.339283500455788 0.434421475739305 0.143118422143035 0.0345248349124318],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) SeriesUID_radiobutton_Callback(this,hObject,event),...
+ 'Tag','SeriesUID_radiobutton' );
+
+ %Radiobutton to Choose Series Number
+ h25 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Series Number )',...
+ 'TooltipString', 'Choose a series number',...
+ 'Style','radiobutton',...
+ 'Position',[0.46272826717494 0.432555268446741 0.0999864092152151 0.0345248349124318],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) SeriesNumber_radiobutton_Callback(this,hObject,event),...
+ 'Tag','SeriesNumber_radiobutton' );
+
+ % new panel
+ h26 = uipanel(...
+ 'Parent',h1,...
+ 'Title','Resolution',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','uipanel1',...
+ 'Clipping','off',...
+ 'Position',[0.0446672743846855 0.131162790697674 0.103919781221513 0.174418604651163] );
+
+ %Text Resolution in X
+ txt = sprintf('Resolution of the chosen image series\nAdjust it to import an interpolated cube');
+ h32 = uicontrol(...
+ 'Parent',h26,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','x step:',...
+ 'Style','text',...
+ 'TooltipString', txt,...
+ 'Position',[0.0727272727272727 0.614457831325301 0.381818181818182 0.253012048192771],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text7' );
+
+
+ %Text Resolution in Y
+ txt = sprintf('Resolution of the chosen image series\nAdjust it to import an interpolated cube');
+ h27 = uicontrol(...
+ 'Parent',h26,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','y step:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',[0.0727272727272727 0.337349397590362 0.718181818181818 0.253012048192771],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text8' );
+
+ %Text Resolution in Z
+ txt = sprintf('Resolution of the chosen image series\nAdjust it to import an interpolated cube');
+ h28 = uicontrol(...
+ 'Parent',h26,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'String','z step:',...
+ 'TooltipString',txt,...
+ 'Style','text',...
+ 'Position',[0.0727272727272727 0.0602409638554217 0.718181818181818 0.253012048192771],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Tag','text9' );
+
+ %Edit Resolution of interpolated cube : X
+ txt = sprintf('Resolution of the chosen image series\nAdjust it to import an interpolated cube');
+ h29 = uicontrol(...
+ 'Parent',h26,...
+ 'Units','normalized',...
+ 'Style','edit',...
+ 'Position',[0.463636363636364 0.653846153846154 0.4 0.217948717948718],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'TooltipString',txt,...
+ 'Tag','resx_edit');
+
+ %Edit Resolution of interpolated cube : Y
+ txt = sprintf('Resolution of the chosen image series\nAdjust it to import an interpolated cube');
+ h30 = uicontrol(...
+ 'Parent',h26,...
+ 'Units','normalized',...
+ 'Style','edit',...
+ 'Position',[0.463636363636364 0.385542168674699 0.4 0.216867469879518],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'TooltipString',txt,...
+ 'Tag','resy_edit');
+
+
+ %Edit Resolution of interpolated cube : Z
+ txt = sprintf('Resolution of the chosen image series\nAdjust it to import an interpolated cube');
+ h31 = uicontrol(...
+ 'Parent',h26,...
+ 'Units','normalized',...
+ 'Style','edit',...
+ 'Position',[0.463636363636364 0.120481927710843 0.4 0.216867469879518],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'TooltipString',txt,...
+ 'Tag','resz_edit');
+
+ %Listbox Select RT dose
+ h34 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'Max',32,...
+ 'Style','listbox',...
+ 'Value',1,...
+ 'Position',[0.660893345487694 0.493565891472868 0.320875113947129 0.186046511627907],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) doseseries_listbox_Callback(this,hObject,event),...
+ 'TooltipString', 'Select an RT dose',...
+ 'Tag','doseseries_listbox');
+
+ %Listbox to choose RT plan
+ h35 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'Max',2,...
+ 'Style','listbox',...
+ 'Value',1,...
+ 'Position',[0.318140382862352 0.493565891472868 0.320875113947128 0.186046511627907],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) rtplan_listbox_Callback(this,hObject,event),...
+ 'TooltipString', 'Select an RT plan',...
+ 'Tag','rtplan_listbox');
+
+ %Edit choose patient case directories
+ h36 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'HorizontalAlignment','left',...
+ 'Style','edit',...
+ 'Position',[0.0447897623400366 0.742867647058823 0.857404021937843 0.0477941176470588],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) dir_path_field_Callback(this,hObject,event),...
+ 'TooltipString', 'Choose the input directory',...
+ 'Tag','dir_path_field' );
+
+ %Import Patient name
+ h37 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String',{ 'Import patient name & DICOM meta information'; ' '; ' ' },...
+ 'Style','checkbox',...
+ 'Value',1,...
+ 'Position',[0.0446672743846855 0.395661785816824 0.258805834092981 0.0484496124031008],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) checkPatientName_Callback(this,hObject,event),...
+ 'Tag','checkPatientName' );
+
+ %Checkbox use RT Dose Grid resolution
+ h38 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String',{ 'Use RT Dose grid'; ' '; ' ' },...
+ 'TooltipString', 'Use the resolution of the dose grid',...
+ 'Style','checkbox',...
+ 'Position',[0.0446672743846855 0.349150157909848 0.228805834092981 0.0484496124031008],...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'Callback',@(hObject,event) checkUseRTdoseGrid_Callback(this,hObject,event),...
+ 'Enable','off',...
+ 'Tag','checkbox3' );
+
+ this.createHandles();
+
+ end
+ end
+
+ methods (Access = private)
+ % SCANFOLDER FUNKTION
+ function this = scanFolder(this, hObject, eventdata)
+ handles = this.handles;
+
+ this.importer = matRad_DicomImporter(get(handles.dir_path_field,'String'));
+
+ if iscell(this.importer.patient)
+ handles.fileList = this.importer.allfiles;
+ %handles.patient_listbox.String = patient_listbox;
+ set(handles.patient_listbox,'String',this.importer.patient,'Value',1);
+ % guidata(hObject, handles);
+ this.handles = handles;
+ end
+ end
+ end
+
+
+
+end
+
diff --git a/matRad/gui/widgets/matRad_importWidget.m b/matRad/gui/widgets/matRad_importWidget.m
new file mode 100644
index 000000000..b0fb55d90
--- /dev/null
+++ b/matRad/gui/widgets/matRad_importWidget.m
@@ -0,0 +1,414 @@
+classdef matRad_importWidget < matRad_Widget
+ % matRad_importWidget class to generate GUI widget to import various
+ % formats : dicom, nrrd, etc.
+ %
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ methods
+ function this = matRad_importWidget(handleParent)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 1
+ handleParent = figure(...
+ 'MenuBar','none',...
+ 'Units','characters',...
+ 'Position',[25 10 89.2 18.3846153846154],...
+ 'Color',matRad_cfg.gui.backgroundColor,...
+ 'Name','Import Patient',...
+ 'HandleVisibility','callback',...
+ 'Tag','figure_importDialog',...
+ 'IntegerHandle','off',...
+ 'WindowStyle','normal');
+ end
+ this = this@matRad_Widget(handleParent);
+ end
+
+ %OPENING/INITIALIZE FUNKTION
+ function this = initialize(this)
+ % handles = this.handles;
+ % this.handles = handles;
+ %
+ %handles = guidata(this.widgetHandle);
+ %handles.output = this.widgetHandle;
+ %guidata(this.widgetHandle, handles);
+
+ end
+ end
+
+ methods (Access = protected)
+ function this = createLayout(this)
+ matRad_cfg = MatRad_Config.instance();
+
+ h1 = this.widgetHandle;
+
+
+ h2 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Patient CT file [HU]:',...
+ 'TooltipString','Choose a patient CT',...
+ 'HorizontalAlignment','left',...
+ 'Style','text',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Position',[0.03 0.85 0.4 0.1],...
+ 'Tag','text2');
+
+
+ h3 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'Style','edit',...
+ 'Position',[0.03 0.78 0.73 0.1],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Callback',@(hObject,event) edit_ctPath_Callback(this,hObject, event),...
+ 'Tag','edit_ctPath',...
+ 'TooltipString','Choose the input directory');
+
+
+ h4 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Browse...',...
+ 'TooltipString','Choose the input directory',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Position',[0.78 0.78 0.2 0.1],...
+ 'Callback',@this.pushbutton_ctPath_Callback,...
+ 'Tag','pushbutton_ctPath');
+
+
+ h5 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Binary Masks/Segmentations',...
+ 'TooltipString','Select binary masks/segmentations',...
+ 'HorizontalAlignment','left',...
+ 'Style','text',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Position',[0.03 0.435 0.5 0.1],...
+ 'Tag','text_masks');
+
+
+ h6 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'Max',2,...
+ 'Style','listbox',...
+ 'TooltipString','Select binary masks/segmentations',...
+ 'Position',[0.03 0.15 0.73 0.32],... [1.8 3.38461538461539 66.2 4.69230769230769],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Callback',@this.listbox_maskPaths_Callback,...
+ 'Tag','listbox_maskPaths',...
+ 'KeyPressFcn',@(hObject,eventdata)matRad_importGUI('listbox_maskPaths_KeyPressFcn',hObject,eventdata,guidata(hObject)));
+
+
+ h7 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Add File(s)...',...
+ 'TooltipString','Choose the binary mask files',...
+ 'Style','pushbutton',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Position',[0.78 0.37 0.2 0.1],...
+ 'Callback',@this.pushbutton_addMaskPaths_Callback,...
+ 'Tag','pushbutton_addMaskPaths');
+
+
+ h8 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Import',...
+ 'TooltipString','Import the selected CT and binary masks',...
+ 'Position',[0.78 0.02 0.2 0.1],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Callback',@this.pushbutton_import_Callback,...
+ 'Tag','pushbutton_import');
+
+ h10 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Add Folder...',...
+ 'TooltipString','Choose the folder containing binary mask files',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Position',[0.78 0.22 0.2 0.1],...
+ 'Callback',@this.pushbutton_addMaskFolders_Callback,...
+ 'Tag','pushbutton_addMaskFolders');
+
+
+ h11 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','Convert from HU?',...
+ 'Style','checkbox',...
+ 'BackgroundColor',matRad_cfg.gui.backgroundColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Position',[0.03 0.68 0.4 0.1],...
+ 'Callback',@this.checkbox_huConvert_Callback,...
+ 'TooltipString','If this is checked, the import assumes that this is a CT given in HU and will convert with the given (or the default) HLUT',...
+ 'Tag','checkbox_huConvert');
+
+
+ h12 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','matRad_default.hlut',...
+ 'HorizontalAlignment','left',...
+ 'Style','edit',...
+ 'Enable','off',...
+ 'Position',[0.03 0.57 0.73 0.1],...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Callback',@this.edit_hlut_Callback,...
+ 'Tag','edit_hlut');
+
+
+ h13 = uicontrol(...
+ 'Parent',h1,...
+ 'Units','normalized',...
+ 'String','HLUT file...',...
+ 'TooltipString','Choose the HLUT file',...
+ 'BackgroundColor',matRad_cfg.gui.elementColor,...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'FontSize',matRad_cfg.gui.fontSize,...
+ 'FontWeight',matRad_cfg.gui.fontWeight,...
+ 'FontName',matRad_cfg.gui.fontName,...
+ 'Position',[0.78 0.57 0.2 0.1],... [69.8 10.6923076923077 18.2 1.76923076923077],...
+ 'Callback',@this.pushbutton_hlutFile_Callback,...
+ 'Tag','pushbutton_hlutFile');
+
+ this.createHandles();
+ end
+
+ end
+
+ methods
+
+ % %OUTPUT FUNKTION
+ % function varargout = OutputFcn(hObject, eventdata, handles)
+ % varargout{1} = handles.output;
+ % end
+
+ %CALLBACK FOR H3 EDIT CREATE PATH
+ function this = edit_ctPath_Callback(this, hObject, event)
+ end
+
+ %CALLBACK FOR H4 PUSHBUTTON CREATE PATH
+ function this = pushbutton_ctPath_Callback(this, hObject, event)
+ readers = matRad_supportedBinaryFormats();
+ importFilter = horzcat({readers.fileFilter}',{readers.name}');
+
+ handles = this.handles;
+ [importCTFile,importCTPath,~] = uigetfile(importFilter, 'Choose the CT file...');
+
+ if importCTFile ~= 0
+ set(handles.edit_ctPath,'String',fullfile(importCTPath,importCTFile)); % NON EXISTING FIELD 'edit_ctPath'
+ % Update handles structure
+ this.handles = handles;
+ end
+ end
+
+ %CALLBACK FOR H6 LISTBOX MASK PATHS LEER
+ function this = listbox_maskPaths_Callback(this, hObject, event)
+
+ end
+
+ %CALLBACK FOR PUSHBUTTON ADD MASKPATHS
+ function this = pushbutton_addMaskPaths_Callback(this, hObject, event)
+ handles = this.handles;
+
+ readers = matRad_supportedBinaryFormats();
+ importFilter = horzcat({readers.fileFilter}',{readers.name}');
+
+ [importMaskFile,importMaskPath,~] = uigetfile(importFilter, 'Choose the binary mask files...','MultiSelect','on');
+ if ~isempty(importMaskFile)
+ if ~iscell(importMaskFile)
+ tmpName = importMaskFile;
+ importMaskFile = cell(1);
+ importMaskFile{1} = tmpName;
+ end
+ importMaskFile = cellfun(@(filename) fullfile(importMaskPath,filename),importMaskFile,'UniformOutput',false);
+ entries = get(handles.listbox_maskPaths,'String');
+ newEntries = [entries importMaskFile];
+ set(handles.listbox_maskPaths,'String',newEntries);
+ % Update handles structure
+ this.handles = handles;
+ end
+ end
+
+ function cst = showCheckDialog(this,cst)
+ handles = this.handles;
+ handle = dialog('Position', [100 100 400 250],'WindowStyle','modal','Name','Confirm Segmentations');
+
+ %Create Table
+ hTable = uitable('Parent',handle,'Units','normal','Position',[0.1 0.2 0.8 0.8]);
+ set(hTable,'Data',cst(:,2:3));
+ set(hTable,'ColumnName',{'Name','Type'});
+ set(hTable,'ColumnWidth',{150,'auto'});
+ set(hTable,'RowName',char([]));
+ set(hTable,'ColumnEditable',[true true]);
+ set(hTable,'ColumnFormat',{'char',{'TARGET', 'OAR', 'IGNORED'}});
+
+ %Create Button
+ hButton = uicontrol(handle,'Style','pushbutton','String','Confirm','Units','normal','Position',[0.7 0.05 0.2 0.1],'Callback','uiresume(gcbf)');%{@pushbutton_confirm_vois_callback});
+ try
+ uiwait(handle);
+ tmp = get(hTable,'Data');
+ cst(:,2:3) = tmp(:,:);
+ catch
+ this.showWarning('Closed checkdialog without confirmation! Using default cst information!');
+ end
+ delete(handle);
+ this.handles = handles();
+
+ end
+
+ %CALLBACK FOR H8 PUSHBUTTON IMPORT
+ function this = pushbutton_import_Callback(this, hObject, event)
+ handles = this.handles;
+
+ ctFile = get(handles.edit_ctPath,'String');
+ maskFiles = get(handles.listbox_maskPaths,'String');
+
+ if isempty(ctFile) || isempty(maskFiles)
+ this.showError('Please sepecify a CT and at least one mask!');
+ end
+
+ convertHU = get(handles.checkbox_huConvert,'Value');
+
+ if convertHU
+ [ct,cst] = matRad_importPatient(ctFile,maskFiles,get(handles.edit_hlut,'String'));
+ else
+ [ct,cst] = matRad_importPatient(ctFile,maskFiles);
+ end
+
+ cst = this.showCheckDialog(cst);
+
+ % delete existing workspace - parse variables from base workspace
+ %set(handles.popupDisplayOption,'String','no option available');
+ AllVarNames = evalin('base','who');
+ RefVarNames = {'ct','cst','pln','stf','dij','resultGUI'};
+ ChangedVarNames = {};
+ for i = 1:length(RefVarNames)
+ if sum(ismember(AllVarNames,RefVarNames{i}))>0
+ evalin('base',['clear ', RefVarNames{i}]);
+ end
+ end
+
+ assignin('base', 'ct', ct);
+ assignin('base', 'cst', cst);
+
+ %delete(handles.figure_importDialog);
+
+ this.handles = handles;
+ this.changedWorkspace('ct','cst');
+ %delete(this.widgetHandle);
+
+ end
+
+ % %CALLBACK FOR H9 PUSHBUTTON CANCEL
+ % function this = pushbutton_cancel_Callback(this, hObject, event)
+ % handles = this.handles;
+ % delete(handles.figure_importDialog);
+ % this.handles = handles;
+ % end
+
+ %CALLBACK FOR H10 PUSHBUTTON ADD MASK FOLDERS
+ function this = pushbutton_addMaskFolders_Callback(this, hObject, event)
+ handles = this.handles;
+ importMaskPath = uigetdir('./', 'Choose the folder containing binary mask files...');
+ importMaskPath = [importMaskPath filesep];
+ if ~isempty(importMaskPath)
+ entries = get(handles.listbox_maskPaths,'String');
+ newEntries = [entries cellstr(importMaskPath)];
+ set(handles.listbox_maskPaths,'String',newEntries);
+ % Update handles structure
+ this.handles = handles;
+ end
+ end
+
+ %CALLBACK FOR H11 CHECKBOX HU CONVERT
+ function this = checkbox_huConvert_Callback(this, hObject, event)
+ handles = this.handles;
+ checked = get(hObject,'Value');
+ if checked
+ fieldState = 'on';
+ else
+ fieldState = 'off';
+ end
+
+ set(handles.edit_hlut,'Enable',fieldState);
+ set(handles.pushbutton_hlutFile,'Enable',fieldState);
+ this.handles = handles;
+ end
+
+ %CALLBACK FOR H12 EDIT HLUT
+ function this = edit_hlut_Callback(this, hObject, event)
+ %bleibt leer
+ end
+
+ %CALLBACK FOR H13 PUSHBUTTON HLUT FILE
+ function this = pushbutton_hlutFile_Callback(this, hObject, event)
+ [importHLUTFile,importHLUTPath,~] = uigetfile({'*.hlut', 'matRad HLUT-Files'}, 'Choose the HLUT file...');
+ if importHLUTFile ~= 0
+ set(handles.edit_hlut,'String',fullfile(importHLUTPath,importHLUTFile));
+ % Update handles structure
+ this.handles = handles;
+ end
+ end
+
+
+ end
+end
+
diff --git a/matRad/hluts/matRad_default.hlut b/matRad/hluts/matRad_default.hlut
new file mode 100644
index 000000000..f8103dd5c
--- /dev/null
+++ b/matRad/hluts/matRad_default.hlut
@@ -0,0 +1,12 @@
+# matRad default HU lookup table
+#
+# First column: Hounsfield values (typically -1000...+3000)
+# Second column: Relative electron densities(photons) or stopping power (particles)
+-1024 0.001
+-999 0.001
+-90 0.95
+-45 0.99
+0 1
+100 1.095
+350 1.199
+3000 2.505
\ No newline at end of file
diff --git a/dicom/hlutLibrary/matRad_default.hlut b/matRad/hluts/matRad_default_deprecated.hlut
similarity index 87%
rename from dicom/hlutLibrary/matRad_default.hlut
rename to matRad/hluts/matRad_default_deprecated.hlut
index 2b2c28de3..491b983a9 100644
--- a/dicom/hlutLibrary/matRad_default.hlut
+++ b/matRad/hluts/matRad_default_deprecated.hlut
@@ -2,7 +2,9 @@
#
# First column: Hounsfield values (typically -1000...+3000)
# Second column: Relative electron densities(photons) or stopping power (particles)
--1024.0 0.00324
+-1024.0 0.001
+-999 0.001
+0 1
200.0 1.20000
449.0 1.20001
2000.0 2.49066
diff --git a/matRad_electronDensitiesToHU.m b/matRad/hluts/matRad_electronDensitiesToHU.m
similarity index 90%
rename from matRad_electronDensitiesToHU.m
rename to matRad/hluts/matRad_electronDensitiesToHU.m
index 1c7a4eea8..acd8a3784 100644
--- a/matRad_electronDensitiesToHU.m
+++ b/matRad/hluts/matRad_electronDensitiesToHU.m
@@ -23,7 +23,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -58,5 +58,6 @@
end
else
- fprintf('Reconversion of HU values could not be done because HLUT is not bijective.\n');
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('Reconversion of HU values could not be done because HLUT is not bijective.\n');
end
diff --git a/matRad/matRad_calcDoseForward.m b/matRad/matRad_calcDoseForward.m
new file mode 100644
index 000000000..dbdb95753
--- /dev/null
+++ b/matRad/matRad_calcDoseForward.m
@@ -0,0 +1,57 @@
+function resultGUI = matRad_calcDoseForward(ct,cst,stf,pln,w)
+ % matRad forward dose calculation (no dij)
+ %
+ % call
+ % resultGUI = matRad_calcDoseForward(ct,stf,pln,cst) %If weights stored in stf
+ % resultGUI = matRad_calcDoseForward(ct,stf,pln,cst,w)
+ %
+ % input
+ % ct: ct cube
+ % cst: matRad cst cell array
+ % stf: matRad steering information struct
+ % pln: matRad plan meta information struct
+ % w: optional (if no weights available in stf): bixel weight
+ % vector
+ %
+ % output
+ % resultGUI: matRad result struct
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2015 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Deprecation warnings
+ if ~isfield(stf,'machine')
+ matRad_cfg.dispDeprecationWarning('stf should contain the machine name in the ''machine'' field since matRad 3. Manually adding ''%s'' from pln.',pln.machine);
+ for i=1:numel(stf)
+ stf(i).machine = pln.machine;
+ end
+ end
+
+ engine = DoseEngines.matRad_DoseEngineBase.getEngineFromPln(pln);
+
+ if nargin < 5
+ resultGUI = engine.calcDoseForward(ct,cst,stf);
+ else
+ resultGUI = engine.calcDoseForward(ct,cst,stf,w);
+ end
+
+ end
+
+
+
+
\ No newline at end of file
diff --git a/matRad/matRad_calcDoseInfluence.m b/matRad/matRad_calcDoseInfluence.m
new file mode 100644
index 000000000..e69f53aba
--- /dev/null
+++ b/matRad/matRad_calcDoseInfluence.m
@@ -0,0 +1,50 @@
+function dij = matRad_calcDoseInfluence(ct,cst,stf,pln)
+% matRad dose calculation automaticly creating the appropriate dose engine
+% for the given pln struct and called the associated dose calculation funtion
+%
+% call
+% dij = matRad_calcDoseInfluence(ct,cst,stf,pln)
+%
+% input
+% ct: ct cube
+% cst: matRad cst cell array
+% stf: matRad steering information struct
+% pln: matRad plan meta information struct
+%
+%
+% output
+% dij: matRad dij struct
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+matRad_cfg = MatRad_Config.instance();
+
+%Deprecation warnings
+if ~isfield(stf,'machine')
+ matRad_cfg.dispDeprecationWarning('stf should contain the machine name in the ''machine'' field since matRad 3. Manually adding ''%s'' from pln.',pln.machine);
+ for i=1:numel(stf)
+ stf(i).machine = pln.machine;
+ end
+end
+
+engine = DoseEngines.matRad_DoseEngineBase.getEngineFromPln(pln);
+
+%call the calcDose funktion
+dij = engine.calcDoseInfluence(ct,cst,stf);
+
+end
diff --git a/matRad_directApertureOptimization.m b/matRad/matRad_directApertureOptimization.m
similarity index 91%
rename from matRad_directApertureOptimization.m
rename to matRad/matRad_directApertureOptimization.m
index 12164561c..d71295da9 100644
--- a/matRad_directApertureOptimization.m
+++ b/matRad/matRad_directApertureOptimization.m
@@ -2,7 +2,6 @@
% matRad function to run direct aperture optimization
%
% call
-% [optResult,optimizer] = matRad_directApertureOptimization(dij,cst,apertureInfo,pln)
% [optResult,optimizer] = matRad_directApertureOptimization(dij,cst,apertureInfo,optResult,pln)
%
% input
@@ -11,7 +10,7 @@
% apertureInfo: aperture shape info struct
% optResult: resultGUI struct to which the output data will be added, if
% this field is empty optResult struct will be created
-% (optional)
+%
% pln: matRad pln struct
%
% output
@@ -28,16 +27,16 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
matRad_cfg = MatRad_Config.instance();
+
% adjust overlap priorities
cst = matRad_setOverlapPriorities(cst);
@@ -67,6 +66,16 @@
cst = matRad_resizeCstToGrid(cst,dij.ctGrid.x,dij.ctGrid.y,dij.ctGrid.z,...
dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z);
+
+
+% set optimization options
+options.ixForOpt = 1;
+options.numOfScen = 1;
+options.scenProb = 1;
+options.bioOpt = pln.bioParam.bioOpt;
+options.quantityOpt = pln.bioParam.quantityOpt;
+options.model = pln.bioParam.model;
+
% update aperture info vector
apertureInfo = matRad_OptimizationProblemDAO.matRad_daoVec2ApertureInfo(apertureInfo,apertureInfo.apertureVector);
diff --git a/matRad/matRad_fluenceOptimization.m b/matRad/matRad_fluenceOptimization.m
new file mode 100644
index 000000000..b249d3517
--- /dev/null
+++ b/matRad/matRad_fluenceOptimization.m
@@ -0,0 +1,366 @@
+function [resultGUI,optimizer] = matRad_fluenceOptimization(dij,cst,pln,wInit)
+% matRad inverse planning wrapper function
+%
+% call
+% [resultGUI,optimizer] = matRad_fluenceOptimization(dij,cst,pln)
+% [resultGUI,optimizer] = matRad_fluenceOptimization(dij,cst,pln,wInit)
+%
+% input
+% dij: matRad dij struct
+% cst: matRad cst struct
+% pln: matRad pln struct
+% wInit: (optional) custom weights to initialize problems
+%
+% output
+% resultGUI: struct containing optimized fluence vector, dose, and (for
+% biological optimization) RBE-weighted dose etc.
+% optimizer: Used Optimizer Object
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2016 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+% consider VOI priorities
+cst = matRad_setOverlapPriorities(cst);
+
+% check & adjust objectives and constraints internally for fractionation
+for i = 1:size(cst,1)
+ %Compatibility Layer for old objective format
+ if isstruct(cst{i,6})
+ cst{i,6} = arrayfun(@matRad_DoseOptimizationFunction.convertOldOptimizationStruct,cst{i,6},'UniformOutput',false);
+ end
+ for j = 1:numel(cst{i,6})
+
+ obj = cst{i,6}{j};
+
+ %In case it is a default saved struct, convert to object
+ %Also intrinsically checks that we have a valid optimization
+ %objective or constraint function in the end
+ if ~isa(obj,'matRad_DoseOptimizationFunction')
+ try
+ obj = matRad_DoseOptimizationFunction.createInstanceFromStruct(obj);
+ catch
+ matRad_cfg.dispError('cst{%d,6}{%d} is not a valid Objective/constraint! Remove or Replace and try again!',i,j);
+ end
+ end
+
+ obj = obj.setDoseParameters(obj.getDoseParameters()/pln.numOfFractions);
+
+ cst{i,6}{j} = obj;
+ end
+end
+
+% resizing cst to dose cube resolution
+cst = matRad_resizeCstToGrid(cst,dij.ctGrid.x, dij.ctGrid.y, dij.ctGrid.z,...
+ dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z);
+
+% Get rid of voxels that are not interesting for the optimization problem
+if ~isfield(pln,'propOpt') || ~isfield(pln.propOpt, 'clearUnusedVoxels')
+ pln.propOpt.clearUnusedVoxels = matRad_cfg.defaults.propOpt.clearUnusedVoxels;
+end
+
+if pln.propOpt.clearUnusedVoxels
+ dij = matRad_clearUnusedVoxelsFromDij(cst, dij);
+end
+
+
+% find target indices and described dose(s) for weight vector
+% initialization
+V = [];
+doseTarget = [];
+ixTarget = [];
+
+for i = 1:size(cst,1)
+ if isequal(cst{i,3},'TARGET') && ~isempty(cst{i,6})
+ V = [V;cst{i,4}{1}];
+
+ %Iterate through objectives/constraints
+ fDoses = [];
+ for fObjCell = cst{i,6}
+ dParams = fObjCell{1}.getDoseParameters();
+ %Don't care for Inf constraints
+ dParams = dParams(isfinite(dParams));
+ %Add do dose list
+ fDoses = [fDoses dParams];
+ end
+
+ doseTarget = [doseTarget fDoses];
+ ixTarget = [ixTarget i*ones(1,length(fDoses))];
+ end
+end
+[doseTarget,i] = max(doseTarget);
+ixTarget = ixTarget(i);
+wOnes = ones(dij.totalNumOfBixels,1);
+
+% calculate initial beam intensities wInit
+matRad_cfg.dispInfo('Estimating initial weights... ');
+
+if exist('wInit','var')
+ %do nothing as wInit was passed to the function
+ matRad_cfg.dispInfo('chosen provided wInit!\n');
+
+ % Write ixDose which is needed for the optimizer
+ if pln.bioParam.bioOpt
+ dij.ixDose = dij.bx~=0;
+
+ %pre-calculations
+ dij.gamma = zeros(dij.doseGrid.numOfVoxels,dij.numOfScenarios);
+ dij.gamma(dij.ixDose) = dij.ax(dij.ixDose)./(2*dij.bx(dij.ixDose));
+ end
+
+elseif strcmp(pln.bioParam.model,'constRBE') && strcmp(pln.radiationMode,'protons')
+ % check if a constant RBE is defined - if not use 1.1
+ if ~isfield(dij,'RBE')
+ dij.RBE = 1.1;
+ end
+
+ doseTmp = dij.physicalDose{1}*wOnes;
+ bixelWeight = (doseTarget)/(dij.RBE * mean(doseTmp(V)));
+ wInit = wOnes * bixelWeight;
+ matRad_cfg.dispInfo('chosen uniform weight of %f!\n',bixelWeight);
+
+elseif pln.bioParam.bioOpt
+ % retrieve photon LQM parameter
+ [ax,bx] = matRad_getPhotonLQMParameters(cst,dij.doseGrid.numOfVoxels);
+ checkAxBx = cellfun(@(ax1,bx1,ax2,bx2) isequal(ax1(ax1~=0),ax2(ax1~=0)) && isequal(bx1(bx1~=0),bx2(bx1~=0)),dij.ax,dij.bx,ax,bx);
+ if ~all(checkAxBx)
+ matRad_cfg.dispError('Inconsistent biological parameters in dij.ax and/or dij.bx - please recalculate dose influence matrix before optimization!\n');
+ end
+
+
+
+ for i = 1:size(cst,1)
+
+ for j = 1:size(cst{i,6},2)
+ % check if prescribed doses are in a valid domain
+ if any(cst{i,6}{j}.getDoseParameters() > 5) && isequal(cst{i,3},'TARGET')
+ matRad_cfg.dispError('Reference dose > 5 Gy[RBE] for target. Biological optimization outside the valid domain of the base data. Reduce dose prescription or use more fractions.\n');
+ end
+
+ end
+ end
+
+ for s = 1:numel(dij.bx)
+ dij.ixDose{s} = dij.bx{s}~=0;
+ end
+
+ if isequal(pln.bioParam.quantityOpt,'effect')
+
+ effectTarget = cst{ixTarget,5}.alphaX * doseTarget + cst{ixTarget,5}.betaX * doseTarget^2;
+ aTmp = dij.mAlphaDose{1}*wOnes;
+ bTmp = dij.mSqrtBetaDose{1} * wOnes;
+ p = sum(aTmp(V)) / sum(bTmp(V).^2);
+ q = -(effectTarget * length(V)) / sum(bTmp(V).^2);
+
+ wInit = -(p/2) + sqrt((p^2)/4 -q) * wOnes;
+
+ elseif isequal(pln.bioParam.quantityOpt,'RBExD')
+
+ %pre-calculations
+ for s = 1:numel(dij.ixDose)
+ dij.gamma{s} = zeros(dij.doseGrid.numOfVoxels,dij.numOfScenarios);
+ dij.gamma{s}(dij.ixDose{s}) = dij.ax{s}(dij.ixDose{s})./(2*dij.bx{s}(dij.ixDose{s}));
+ end
+
+
+ % calculate current effect in target
+ aTmp = dij.mAlphaDose{1}*wOnes;
+ bTmp = dij.mSqrtBetaDose{1} * wOnes;
+ doseTmp = dij.physicalDose{1}*wOnes;
+
+ CurrEffectTarget = aTmp(V) + bTmp(V).^2;
+ % ensure a underestimated biological effective dose
+ TolEstBio = 1.2;
+ % calculate maximal RBE in target
+ maxCurrRBE = max(-cst{ixTarget,5}.alphaX + sqrt(cst{ixTarget,5}.alphaX^2 + ...
+ 4*cst{ixTarget,5}.betaX.*CurrEffectTarget)./(2*cst{ixTarget,5}.betaX*doseTmp(V)));
+ wInit = ((doseTarget)/(TolEstBio*maxCurrRBE*max(doseTmp(V))))* wOnes;
+
+ elseif strcmp(pln.bioParam.quantityOpt, 'BED')
+
+ if isfield(dij, 'mAlphaDose') && isfield(dij, 'mSqrtBetaDose')
+ abr = cst{ixTarget,5}.alphaX./cst{ixTarget,5}.betaX;
+ meanBED = mean((dij.mAlphaDose{1}(V,:)*wOnes + (dij.mSqrtBetaDose{1}(V,:)*wOnes).^2)./cst{ixTarget,5}.alphaX);
+ BEDTarget = doseTarget.*(1 + doseTarget./abr);
+ elseif isfield(dij, 'RBE')
+ abr = cst{ixTarget,5}.alphaX./cst{ixTarget,5}.betaX;
+ meanBED = mean(dij.RBE.*dij.physicalDose{1}(V,:)*wOnes.*(1+dij.RBE.*dij.physicalDose{1}(V,:)*wOnes./abr));
+ BEDTarget = dij.RBE.*doseTarget.*(1 + dij.RBE.*doseTarget./abr);
+ else
+ abr = cst{ixTarget,5}.alphaX./cst{ixTarget,5}.betaX;
+ meanBED = mean(dij.physicalDose{1}(V,:)*wOnes.*(1+dij.physicalDose{1}(V,:)*wOnes./abr));
+ BEDTarget = doseTarget.*(1 + doseTarget./abr);
+ end
+
+ bixelWeight = BEDTarget/meanBED;
+ wInit = wOnes * bixelWeight;
+
+ end
+
+ matRad_cfg.dispInfo('chosen weights adapted to biological dose calculation!\n');
+else
+ doseTmp = dij.physicalDose{1}*wOnes;
+ bixelWeight = (doseTarget)/mean(doseTmp(V));
+ wInit = wOnes * bixelWeight;
+ matRad_cfg.dispInfo('chosen uniform weight of %f!\n',bixelWeight);
+end
+
+
+%% calculate probabilistic quantities for probabilistic optimization if at least
+% one robust objective is defined
+
+%Check how to use 4D data
+if isfield(pln,'propOpt') && isfield(pln.propOpt,'scen4D')
+ scen4D = pln.propOpt.scen4D;
+else
+ scen4D = 1; %Use only first 4D scenario for optimization
+end
+
+%If "all" provided, use all scenarios
+if isequal(scen4D,'all')
+ scen4D = 1:size(dij.physicalDose,1);
+end
+
+linIxDIJ = find(~cellfun(@isempty,dij.physicalDose(scen4D,:,:)))';
+
+%Only select the indexes of the nominal ct Scenarios
+linIxDIJ_nominalCT = find(~cellfun(@isempty,dij.physicalDose(scen4D,1,1)))';
+
+FLAG_CALC_PROB = false;
+FLAG_ROB_OPT = false;
+
+
+for i = 1:size(cst,1)
+ for j = 1:numel(cst{i,6})
+ if strcmp(cst{i,6}{j}.robustness,'PROB') && numel(linIxDIJ) > 1
+ FLAG_CALC_PROB = true;
+ end
+ if ~strcmp(cst{i,6}{j}.robustness,'none') && numel(linIxDIJ) > 1
+ FLAG_ROB_OPT = true;
+ end
+ end
+end
+
+if FLAG_CALC_PROB
+ [dij] = matRad_calculateProbabilisticQuantities(dij,cst,pln);
+end
+
+
+% set optimization options
+if ~FLAG_ROB_OPT || FLAG_CALC_PROB % if multiple robust objectives are defined for one structure then remove FLAG_CALC_PROB from the if clause
+ ixForOpt = scen4D;
+else
+ ixForOpt = linIxDIJ;
+end
+
+switch pln.bioParam.quantityOpt
+ case 'effect'
+ backProjection = matRad_EffectProjection;
+ case 'RBExD'
+ %Capture special case of constant RBE
+ if strcmp(pln.bioParam.model,'constRBE')
+ backProjection = matRad_ConstantRBEProjection;
+ else
+ backProjection = matRad_VariableRBEProjection;
+ end
+ case 'BED'
+ backProjection = matRad_BEDProjection;
+ case 'physicalDose'
+ backProjection = matRad_DoseProjection;
+ otherwise
+ warning(['Did not recognize bioloigcal setting ''' pln.probOpt.bioOptimization '''!\nUsing physical dose optimization!']);
+ backProjection = matRad_DoseProjection;
+end
+
+%Give scenarios used for optimization
+backProjection.scenarios = ixForOpt;
+backProjection.scenarioProb = pln.multScen.scenProb;
+backProjection.nominalCtScenarios = linIxDIJ_nominalCT;
+%backProjection.scenDim = pln.multScen
+
+optiProb = matRad_OptimizationProblem(backProjection);
+optiProb.quantityOpt = pln.bioParam.quantityOpt;
+if isfield(pln,'propOpt') && isfield(pln.propOpt,'useLogSumExpForRobOpt')
+ optiProb.useLogSumExpForRobOpt = pln.propOpt.useLogSumExpForRobOpt;
+end
+
+%Get Bounds
+if ~isfield(pln.propOpt,'boundMU')
+ pln.propOpt.boundMU = false;
+end
+
+if pln.propOpt.boundMU
+ if (isfield(dij,'minMU') || isfield(dij,'maxMU')) && ~isfield(dij,'numParticlesPerMU')
+ matRad_cfg.dispWarning('Requested MU bounds but number of particles per MU not set! Bounds will not be enforced and standard [0,Inf] will be used instead!');
+ elseif ~isfield(dij,'minMU') && ~isfield(dij,'maxMU')
+ matRad_cfg.dispWarning('Requested MU bounds but machine bounds not defined in dij.minMU & dij.maxMU! Bounds will not be enforced and standard [0,Inf] will be used instead!');
+ else
+ if isfield(dij,'minMU')
+ optiProb.minimumW = dij.numParticlesPerMU .* dij.minMU / 1e6;
+ matRad_cfg.dispInfo('Using lower MU bounds provided in dij!\n')
+ end
+
+ if isfield(dij,'maxMU')
+ optiProb.maximumW = dij.numParticlesPerMU .* dij.maxMU / 1e6;
+ matRad_cfg.dispInfo('Using upper MU bounds provided in dij!\n')
+ end
+ end
+else
+ matRad_cfg.dispInfo('Using standard MU bounds of [0,Inf]!\n')
+end
+
+if ~isfield(pln.propOpt,'optimizer')
+ pln.propOpt.optimizer = 'IPOPT';
+end
+
+switch pln.propOpt.optimizer
+ case 'IPOPT'
+ optimizer = matRad_OptimizerIPOPT;
+ case 'fmincon'
+ optimizer = matRad_OptimizerFmincon;
+ case 'simulannealbnd'
+ optimizer = matRad_OptimizerSimulannealbnd;
+ otherwise
+ warning(['Optimizer ''' pln.propOpt.optimizer ''' not known! Fallback to IPOPT!']);
+ optimizer = matRad_OptimizerIPOPT;
+end
+
+if ~optimizer.IsAvailable()
+ matRad_cfg.dispError(['Optimizer ''' pln.propOpt.optimizer ''' not available!']);
+end
+
+optimizer = optimizer.optimize(wInit,optiProb,dij,cst);
+
+wOpt = optimizer.wResult;
+info = optimizer.resultInfo;
+
+resultGUI = matRad_calcCubes(wOpt,dij);
+resultGUI.wUnsequenced = wOpt;
+resultGUI.usedOptimizer = optimizer;
+resultGUI.info = info;
+
+%Robust quantities
+if pln.multScen.totNumScen > 1
+ for i = 1:pln.multScen.totNumScen
+ scenSubIx = pln.multScen.linearMask(i,:);
+ resultGUItmp = matRad_calcCubes(wOpt,dij,pln.multScen.sub2scenIx(scenSubIx(1),scenSubIx(2),scenSubIx(3)));
+ resultGUI = matRad_appendResultGUI(resultGUI,resultGUItmp,false,sprintf('scen%d',i));
+ end
+end
+
+% unblock mex files
+clear mex
diff --git a/matRad/matRad_generateStf.m b/matRad/matRad_generateStf.m
new file mode 100644
index 000000000..4e055d179
--- /dev/null
+++ b/matRad/matRad_generateStf.m
@@ -0,0 +1,43 @@
+function stf = matRad_generateStf(ct,cst,pln,visMode)
+% matRad steering information generation
+%
+% call
+% stf = matRad_generateStf(ct,cst,pln)
+%
+% input
+% ct: ct cube
+% cst: matRad cst struct
+% pln: matRad plan meta information struct
+%
+% output
+% stf: matRad steering information struct
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+generator = matRad_StfGeneratorBase.getGeneratorFromPln(pln);
+
+if nargin == 4
+ matRad_cfg.dispDeprecationWarning('The fourth ''visMode'' argument for matRad_generateStf is deprecated and will be removed in a future release. Please use pln.propStf.visMode as a replacement (or the corresponding property in the stf generators))');
+ generator.visMode = visMode;
+end
+
+%call the calcDose funktion
+stf = generator.generate(ct,cst);
+
+end
\ No newline at end of file
diff --git a/matRad/matRad_planAnalysis.m b/matRad/matRad_planAnalysis.m
new file mode 100644
index 000000000..e9f9231ff
--- /dev/null
+++ b/matRad/matRad_planAnalysis.m
@@ -0,0 +1,136 @@
+function resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln,varargin)
+% matRad plan analysis function
+% This function performs analysis on radiation therapy plans, including DVH (Dose-Volume Histogram) and quality indicators.
+% It optionally displays these analyses based on input parameters.
+%
+% input
+% resultGUI: matRad resultGUI struct containing the analysis results
+% ct: matRad ct struct with computed tomography data
+% cst: matRad cst cell array with structure definitions
+% stf: matRad stf struct with beam information
+% pln: matRad pln struct with plan information
+% name / value pairs: Optional parameters for analysis customization
+% refGy: (optional) Dose values for V_XGy calculation (default: [40 50 60])
+% refVol:(optional) Volume percentages for D_X calculation (default: [2 5 95 98])
+%
+% output
+% resultGUI: Updated resultGUI with analysis data
+
+% Initialize input parser for function arguments
+p = inputParser();
+
+% Define required inputs
+p.addRequired('ct',@isstruct);
+p.addRequired('cst',@iscell);
+p.addRequired('stf',@isstruct);
+p.addRequired('pln',@isstruct);
+
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+% Initialize input parser for optional parameters
+p = inputParser();
+
+% Define required inputs again for clarity
+p.addRequired('ct',@isstruct);
+p.addRequired('cst',@iscell);
+p.addRequired('stf',@isstruct);
+p.addRequired('pln',@isstruct);
+
+% Define optional parameters with default values
+p.addParameter('refGy',[40 50 60],@isnumeric); % Reference dose values for V_XGy calculation
+p.addParameter('refVol',[2 5 95 98],@isnumeric); % Reference volume percentages for D_X calculation
+p.addParameter('showDVH',true,@islogical); % Flag to show or hide the DVH plot
+p.addParameter('showQI',true,@islogical); % Flag to show or hide the Quality Indicators plot
+
+% Parse input arguments to extract values
+p.parse(ct,cst,stf,pln,varargin{:});
+
+% Assign parsed values to variables
+ct = p.Results.ct;
+cst = p.Results.cst;
+stf = p.Results.stf;
+pln = p.Results.pln;
+refGy = p.Results.refGy;
+refVol = p.Results.refVol;
+showDVH = p.Results.showDVH;
+showQI = p.Results.showQI;
+
+% Determine which dose cube to use based on resultGUI structure
+if ~isfield(pln,'bioParam')
+ visQ = pln.bioParam.quantityVis;
+elseif isfield(resultGUI,'RBExD')
+ visQ = 'RBExD';
+else
+ visQ = 'physicalDose';
+end
+
+if ~isfield(resultGUI,visQ)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Unknown quantity ''%s'' to analyse!',visQ);
+end
+
+doseCube = resultGUI.(visQ);
+
+% Calculate DVH and quality indicators
+resultGUI.dvh = matRad_calcDVH(cst,doseCube,'cum'); % Calculate cumulative DVH
+resultGUI.qi = matRad_calcQualityIndicators(cst,pln,doseCube,refGy,refVol); % Calculate quality indicators
+
+dvhScen = {};
+if isfield(pln,'multScen') && pln.multScen.totNumScen > 1
+ for i = 1:pln.multScen.totNumScen
+ scenFieldName = sprintf('%s_scen%d',visQ,i);
+ if isfield(resultGUI,scenFieldName)
+ dvhScen{i} = matRad_calcDVH(cst,resultGUI.(scenFieldName),'cum'); % Calculate cumulative scenario DVH
+ end
+ end
+end
+
+
+% Configuration for GUI appearance
+matRad_cfg = MatRad_Config.instance();
+
+% Create figure for plots with background color from configuration
+hF = figure('Color',matRad_cfg.gui.backgroundColor);
+
+colorSpec = {'Color',matRad_cfg.gui.elementColor,...
+ 'XColor',matRad_cfg.gui.textColor,...
+ 'YColor',matRad_cfg.gui.textColor,...
+ 'GridColor',matRad_cfg.gui.textColor,...
+ 'MinorGridColor',matRad_cfg.gui.backgroundColor};
+
+% Determine subplot layout based on flags
+if showDVH && showQI
+ hDVHax = subplot(2,1,1,colorSpec{:}); % DVH plot area
+ hQIax = subplot(2,1,2,colorSpec{:}); % Quality Indicators plot area
+elseif showDVH
+ hDVHax = subplot(1,1,1,colorSpec{:}); % Only DVH plot
+elseif showQI
+ hQIax = subplot(1,1,1,colorSpec{:}); % Only Quality Indicators plot
+end
+
+% Display DVH if enabled
+if showDVH
+ matRad_showDVH(resultGUI.dvh,cst,pln,'axesHandle',hDVHax,'LineWidth',3); % Show DVH plot
+
+ for i = 1:numel(dvhScen)
+ matRad_showDVH(dvhScen{i},cst,pln,'axesHandle',hDVHax,'LineWidth',0.5,'plotLegend',false,'LineStyle','--'); % Show DVH plot
+ end
+end
+
+% Display Quality Indicators if enabled
+if showQI
+ matRad_showQualityIndicators(hQIax,resultGUI.qi); % Show Quality Indicators plot
+end
+
+end
\ No newline at end of file
diff --git a/matRad/matRad_sequencing.m b/matRad/matRad_sequencing.m
new file mode 100644
index 000000000..51207ada2
--- /dev/null
+++ b/matRad/matRad_sequencing.m
@@ -0,0 +1,71 @@
+function resultGUI = matRad_sequencing(resultGUI,stf,dij,pln,visBool)
+% matRad inverse planning wrapper function
+%
+% call
+% resultGUI = matRad_sequencing(resultGUI,stf,dij,pln)
+%
+% input
+% dij: matRad dij struct
+% stf: matRad stf struct
+% pln: matRad pln struct
+% resultGUI: struct containing optimized fluence vector, dose, and (for
+% biological optimization) RBE-weighted dose etc.
+%
+% output
+% resultGUI: struct containing optimized fluence vector, dose, and (for
+% biological optimization) RBE-weighted dose etc.
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2016 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+if nargin < 5
+ visBool = 0;
+end
+
+if ~isfield(pln,'propSeq')
+ pln.propSeq = struct('runSequencing',false);
+end
+
+if strcmp(pln.radiationMode,'photons') && (pln.propSeq.runSequencing || pln.propOpt.runDAO)
+
+ if ~isfield(pln.propSeq, 'sequencer')
+ pln.propSeq.sequencer = 'siochi'; % default: siochi sequencing algorithm
+ matRad_cfg.dispWarning ('pln.propSeq.sequencer not specified. Using siochi leaf sequencing (default).')
+ end
+
+ if ~isfield(pln.propSeq, 'sequencingLevel')
+ pln.propSeq.sequencingLevel = 5;
+ matRad_cfg.dispWarning ('pln.propSeq.sequencingLevel not specified. Using 5 sequencing levels (default).')
+ end
+
+ switch pln.propSeq.sequencer
+ case 'xia'
+ resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,pln.propSeq.sequencingLevel,visBool);
+ case 'engel'
+ resultGUI = matRad_engelLeafSequencing(resultGUI,stf,dij,pln.propSeq.sequencingLevel,visBool);
+ case 'siochi'
+ resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,pln.propSeq.sequencingLevel,visBool);
+ otherwise
+ matRad_cfg.dispError('Could not find specified sequencing algorithm ''%s''',pln.propSeq.sequencer);
+ end
+elseif (pln.propSeq.runSequencing || pln.propOpt.runDAO) && ~strcmp(pln.radiationMode,'photons')
+ matRad_cfg.dispWarning('Sequencing is only specified for pln.radiationMode = "photons". Continuing with out sequencing ... ')
+end
+end
+
+
diff --git a/optimization/+DoseConstraints/matRad_DoseConstraint.m b/matRad/optimization/+DoseConstraints/matRad_DoseConstraint.m
similarity index 74%
rename from optimization/+DoseConstraints/matRad_DoseConstraint.m
rename to matRad/optimization/+DoseConstraints/matRad_DoseConstraint.m
index 08c3b5bdc..df25e57b5 100644
--- a/optimization/+DoseConstraints/matRad_DoseConstraint.m
+++ b/matRad/optimization/+DoseConstraints/matRad_DoseConstraint.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -23,38 +23,44 @@
methods %(Abstract)
%returns the constraint function(s) value(s) for a given dose
%vector. Needs to be implemented in sub-classes.
- function cDose = computeDoseConstraintFunction(obj,dose)
+ function cDose = computeDoseConstraintFunction(this,dose)
error('Function needs to be implemented!');
end
%return the (dose-dependent) constraint function jacobian for a
%given dose vector. Needs to be implemented in sub-classes.
- function cDoseJacob = computeDoseConstraintJacobian(obj,dose)
+ function cDoseJacob = computeDoseConstraintJacobian(this,dose)
error('Function needs to be implemented!');
end
%Returns upper bound(s) / max value(s) for constraint function(s)
%Needs to be implemented in sub-classes.
- function cu = upperBounds(obj,n)
+ function cu = upperBounds(this,n)
error('Function needs to be implemented!');
end
%Returns lower bound(s) / min value(s) for constraint function(s)
%Needs to be implemented in sub-classes.
- function cl = lowerBounds(obj,n)
+ function cl = lowerBounds(this,n)
error('Function needs to be implemented!');
end
end
+ methods (Static)
+ function rob = availableRobustness()
+ rob = {'none','PROB','VWWC','VWWC_INV',}; %By default, no robustness is available
+ end
+ end
+
methods (Access = public)
% default constructor of matRad_DoseConstraint
- function obj = matRad_DoseConstraint(varargin)
+ function this = matRad_DoseConstraint(varargin)
%default initialization from struct (parameters & penalty)
- obj@matRad_DoseOptimizationFunction(varargin{:});
+ this@matRad_DoseOptimizationFunction(varargin{:});
end
- function jStruct = getDoseConstraintJacobianStructure(obj,n)
+ function jStruct = getDoseConstraintJacobianStructure(this,n)
%return the structure of the (dose-dependent) constraint function
%jacobian for a given length n of the dose vector. Returns a
%default of a jStruct
@@ -63,8 +69,8 @@
%Overloads the struct function to add Objective related information
%to output struct
- function s = struct(obj)
- s = struct@matRad_DoseOptimizationFunction(obj);
+ function s = struct(this)
+ s = struct@matRad_DoseOptimizationFunction(this);
end
end
end
diff --git a/matRad/optimization/+DoseConstraints/matRad_DoseConstraintFromObjective.m b/matRad/optimization/+DoseConstraints/matRad_DoseConstraintFromObjective.m
new file mode 100644
index 000000000..ff7f57447
--- /dev/null
+++ b/matRad/optimization/+DoseConstraints/matRad_DoseConstraintFromObjective.m
@@ -0,0 +1,109 @@
+classdef matRad_DoseConstraintFromObjective < DoseConstraints.matRad_DoseConstraint
+ % matRad_ObjectiveConstraint implements a wrapper function that turns
+ % dose objectives into constraints
+ %
+ % use log sum exp approximation, see appendix A in
+ % http://scitation.aip.org/content/aapm/journal/medphys/41/8/10.1118/1.4883837
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties (Constant)
+ name = 'Objective Constraint';
+ parameterTypes = {'objFunc','scalar'};
+ parameterNames = {'f^{max}','slackParameter'};
+ end
+
+ properties
+ objective;
+ parameters = {1e-5, 1e-3};
+ end
+
+ methods (Access = public)
+ function this = matRad_DoseConstraintFromObjective(objective,maxObj,slackParameter)
+
+ %check if objective is a struct and a DoseObjective or Constraint (for init from constraint)
+ if isstruct(objective) && ~isempty(strfind(objective.className,'DoseObjectives'))
+ objective = matRad_DoseOptimizationFunction.createInstanceFromStruct(objective);
+ end
+
+ if nargin == 1 && isstruct(objective)
+ initFromStruct = true;
+ inputStruct = objective;
+ else
+ initFromStruct = false;
+ inputStruct = [];
+ end
+
+ this@DoseConstraints.matRad_DoseConstraint(inputStruct);
+
+
+ if ~initFromStruct
+
+ if nargin == 3 && isscalar(slackParameter)
+ this.parameters{2} = slackParameter;
+ end
+
+ if nargin >= 2 && isscalar(maxObj)
+ this.parameters{1} = maxObj;
+ end
+
+ if nargin >= 1
+ this.objective = objective;
+ end
+
+ end
+ %}
+ end
+
+ function s = struct(this)
+ s = struct@DoseConstraints.matRad_DoseConstraint(this);
+ s.objective = this.objective;
+ end
+
+ function cu = upperBounds(this,n)
+ cu = this.parameters{1}+this.parameters{2};
+ %cu = [Inf; this.parameters{2}];
+ end
+ function cl = lowerBounds(this,n)
+ cl = 0;
+ end
+
+ %% Calculates the Constraint Function value
+ function cDose = computeDoseConstraintFunction(this,dose)
+ cDose = this.objective.computeDoseObjectiveFunction(dose);
+ end
+
+ %% Calculates the Constraint jacobian
+ function cDoseJacob = computeDoseConstraintJacobian(this,dose)
+ cDoseJacob = this.objective.computeDoseObjectiveGradient(dose);
+ end
+
+ function doseParams = getDoseParameters(this)
+ % get only the dose related parameters.
+ ix = cellfun(@(c) isequal('dose',c),this.objective.parameterTypes);
+ doseParams = [this.objective.parameters{ix}];
+ end
+
+ function this = setDoseParameters(this,doseParams)
+ % set only the dose related parameters.
+ ix = cellfun(@(c) isequal('dose',c),this.objective.parameterTypes);
+ this.objective.parameters(ix) = num2cell(doseParams);
+
+ end
+
+
+ end
+
+end
+
+
diff --git a/optimization/+DoseConstraints/matRad_MinMaxDVH.m b/matRad/optimization/+DoseConstraints/matRad_MinMaxDVH.m
similarity index 82%
rename from optimization/+DoseConstraints/matRad_MinMaxDVH.m
rename to matRad/optimization/+DoseConstraints/matRad_MinMaxDVH.m
index 7215f4458..d54c00f4f 100644
--- a/optimization/+DoseConstraints/matRad_MinMaxDVH.m
+++ b/matRad/optimization/+DoseConstraints/matRad_MinMaxDVH.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
- % distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -32,7 +32,7 @@
end
methods
- function obj = matRad_MinMaxDVH(dRef,vMin,vMax)
+ function this = matRad_MinMaxDVH(dRef,vMin,vMax)
%If we have a struct in first argument
if nargin == 1 && isstruct(dRef)
@@ -44,43 +44,43 @@
end
%Call Superclass Constructor (for struct initialization)
- obj@DoseConstraints.matRad_DoseConstraint(inputStruct);
+ this@DoseConstraints.matRad_DoseConstraint(inputStruct);
%now handle initialization from other parameters
if ~initFromStruct
if nargin == 3 && isscalar(vMax)
- obj.parameters{3} = vMax;
+ this.parameters{3} = vMax;
end
if nargin >= 1 && isscalar(dRef)
- obj.parameters{1} = dRef;
+ this.parameters{1} = dRef;
end
if nargin >= 2 && isscalar(vMin)
- obj.parameters{2} = vMin;
+ this.parameters{2} = vMin;
end
end
end
%Overloads the struct function to add constraint specific
%parameters
- function s = struct(obj)
- s = struct@DoseConstraints.matRad_DoseConstraint(obj);
+ function s = struct(this)
+ s = struct@DoseConstraints.matRad_DoseConstraint(this);
s.voxelScalingRatio = 1;
s.referenceScalingVal = 0.01;
end
- function cu = upperBounds(obj,n)
- cu = obj.parameters{3} / 100;
+ function cu = upperBounds(this,n)
+ cu = this.parameters{3} / 100;
end
- function cl = lowerBounds(obj,n)
- cl = obj.parameters{2} / 100;
+ function cl = lowerBounds(this,n)
+ cl = this.parameters{2} / 100;
end
%% Calculates the Constraint Function value
- function cDose = computeDoseConstraintFunction(obj,dose)
+ function cDose = computeDoseConstraintFunction(this,dose)
%Fast DVH point calculation
- cDose = sum(dose >= obj.parameters{1})/numel(dose);
+ cDose = sum(dose >= this.parameters{1})/numel(dose);
%cDose = 100 * cDose; %In Percent
@@ -109,22 +109,22 @@
end
%% Calculates the Constraint jacobian
- function cDoseJacob = computeDoseConstraintJacobian(obj,dose)
+ function cDoseJacob = computeDoseConstraintJacobian(this,dose)
%logistic approximation
%Do we really need to sort two times?
dose_sort = sort(dose);
% calculate scaling
- NoVoxels = max(obj.voxelScalingRatio*numel(dose),10);
- absDiffsort = sort(abs(obj.parameters{1} - dose_sort));
+ NoVoxels = max(this.voxelScalingRatio*numel(dose),10);
+ absDiffsort = sort(abs(this.parameters{1} - dose_sort));
deltaDoseMax = absDiffsort(min(ceil(NoVoxels/2),numel(dose)));
% calclulate DVHC scaling
- DVHCScaling = min((log(1/obj.referenceScalingVal-1))/(2*deltaDoseMax),250);
+ DVHCScaling = min((log(1/this.referenceScalingVal-1))/(2*deltaDoseMax),250);
- d_diff = dose - obj.parameters{1};
+ d_diff = dose - this.parameters{1};
cDoseJacob = (2/numel(dose))*DVHCScaling*exp(2*DVHCScaling*d_diff)./(exp(2*DVHCScaling*d_diff)+1).^2;
diff --git a/optimization/+DoseConstraints/matRad_MinMaxDose.m b/matRad/optimization/+DoseConstraints/matRad_MinMaxDose.m
similarity index 59%
rename from optimization/+DoseConstraints/matRad_MinMaxDose.m
rename to matRad/optimization/+DoseConstraints/matRad_MinMaxDose.m
index d75196dd0..c1b598572 100644
--- a/optimization/+DoseConstraints/matRad_MinMaxDose.m
+++ b/matRad/optimization/+DoseConstraints/matRad_MinMaxDose.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
- % distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -30,7 +30,7 @@
end
methods
- function obj = matRad_MinMaxDose(minDose,maxDose,method)
+ function this = matRad_MinMaxDose(minDose,maxDose,method)
%If we have a struct in first argument
if nargin == 1 && isstruct(minDose)
@@ -42,7 +42,7 @@
end
%Call Superclass Constructor (for struct initialization)
- obj@DoseConstraints.matRad_DoseConstraint(inputStruct);
+ this@DoseConstraints.matRad_DoseConstraint(inputStruct);
%now handle initialization from other parameters
if ~initFromStruct
@@ -51,80 +51,80 @@
method = 'approx';
end
- methodIx = find(strcmp(method,obj.parameterTypes{3}));
+ methodIx = find(strcmp(method,this.parameterTypes{3}));
if isempty(methodIx) || numel(methodIx) > 1
methodIx = 1;
- msg = ['Dose Constraint method can only be ', strjoin(obj.parameterTypes{3},' or '), '! Using method ''', obj.parameterTypes{3}{methodIx}, '''.'];
+ msg = ['Dose Constraint method can only be ', strjoin(this.parameterTypes{3},' or '), '! Using method ''', this.parameterTypes{3}{methodIx}, '''.'];
warning(msg);
end
- obj.parameters{3} = methodIx;
+ this.parameters{3} = methodIx;
if nargin >= 2 && isscalar(maxDose)
- obj.parameters{2} = maxDose;
+ this.parameters{2} = maxDose;
end
if nargin >= 1 && isscalar(minDose)
- obj.parameters{1} = minDose;
+ this.parameters{1} = minDose;
end
end
end
%Overloads the struct function to add constraint specific
%parameters
- function s = struct(obj)
- s = struct@DoseConstraints.matRad_DoseConstraint(obj);
- s.epsilon = obj.epsilon;
+ function s = struct(this)
+ s = struct@DoseConstraints.matRad_DoseConstraint(this);
+ s.epsilon = this.epsilon;
end
- function cu = upperBounds(obj,n)
- switch obj.parameters{3}
+ function cu = upperBounds(this,n)
+ switch this.parameters{3}
case 1 %logsumexp approx
%Validate parameters
- if obj.parameters{1} <= 0 && isinf(obj.parameters{2}) %Constraint doesn't make sense (min = 0 & max = Inf)
+ if this.parameters{1} <= 0 && isinf(this.parameters{2}) %Constraint doesn't make sense (min = 0 & max = Inf)
cu = [];
- elseif obj.parameters{2} == Inf %Only min dose
+ elseif this.parameters{2} == Inf %Only min dose
cu = Inf;
- elseif obj.parameters{1} <= 0 %Only max dose
- cu = obj.parameters{2};
+ elseif this.parameters{1} <= 0 %Only max dose
+ cu = this.parameters{2};
else %both are set sensible
- cu = [Inf; obj.parameters{2}];
+ cu = [Inf; this.parameters{2}];
end
case 2 %voxelwise
- cu = obj.parameters{2}*ones(n,1);
+ cu = this.parameters{2}*ones(n,1);
otherwise
error(['Min/max dose constraint evaluation method not known!']);
end
- %cu = [Inf; obj.parameters{2}];
+ %cu = [Inf; this.parameters{2}];
end
- function cl = lowerBounds(obj,n)
- switch obj.parameters{3}
+ function cl = lowerBounds(this,n)
+ switch this.parameters{3}
case 1 %logsumexp approx
- if obj.parameters{1} <= 0 && isinf(obj.parameters{2})
+ if this.parameters{1} <= 0 && isinf(this.parameters{2})
cl = [];
- elseif obj.parameters{2} == Inf
- cl = obj.parameters{1};
- elseif obj.parameters{1} <= 0
+ elseif this.parameters{2} == Inf
+ cl = this.parameters{1};
+ elseif this.parameters{1} <= 0
cl = 0;
else
- cl = [obj.parameters{1}; 0];
+ cl = [this.parameters{1}; 0];
end
case 2
- cl = obj.parameters{1}*ones(n,1);
+ cl = this.parameters{1}*ones(n,1);
otherwise
matRad_cfg = MatRad_Config.instance();
matRad_cfg.dispError('Min/max dose constraint evaluation method not known!');
end
end
- function jStruct = getDoseConstraintJacobianStructure(obj,n)
- switch obj.parameters{3}
+ function jStruct = getDoseConstraintJacobianStructure(this,n)
+ switch this.parameters{3}
case 1 %logsumexp approx
%Validate parameters
- if obj.parameters{1} <= 0 && isinf(obj.parameters{2}) %Constraint doesn't make sense (min = 0 & max = Inf)
+ if this.parameters{1} <= 0 && isinf(this.parameters{2}) %Constraint doesn't make sense (min = 0 & max = Inf)
jStruct = ones(n,0);
- elseif obj.parameters{1} > 0 && isfinite(obj.parameters{2}) %both are set sensible
+ elseif this.parameters{1} > 0 && isfinite(this.parameters{2}) %both are set sensible
jStruct = ones(n,2);
else %Only min or max dose
jStruct = ones(n,1);
@@ -140,14 +140,14 @@
end
%% Calculates the Constraint Function value
- function cDose = computeDoseConstraintFunction(obj,dose)
- %cDose(2) = dose_max + obj.epsilon * log( sum(exp((dose - dose_max)/obj.epsilon)));
- %cDose(1) = dose_min - obj.epsilon * log( sum(exp((dose_min - dose)/obj.epsilon)));
- switch obj.parameters{3}
+ function cDose = computeDoseConstraintFunction(this,dose)
+ %cDose(2) = dose_max + this.epsilon * log( sum(exp((dose - dose_max)/this.epsilon)));
+ %cDose(1) = dose_min - this.epsilon * log( sum(exp((dose_min - dose)/this.epsilon)));
+ switch this.parameters{3}
case 1 %logsumexp approx
- cDose = obj.computeDoseConstraintFunctionLogSumExp(dose);
+ cDose = this.computeDoseConstraintFunctionLogSumExp(dose);
case 2
- cDose = obj.computeDoseConstraintFunctionVoxelwise(dose);
+ cDose = this.computeDoseConstraintFunctionVoxelwise(dose);
otherwise
matRad_cfg = MatRad_Config.instance();
matRad_cfg.dispError('Min/max dose constraint evaluation method not known!');
@@ -155,12 +155,12 @@
end
%% Calculates the Constraint jacobian
- function cDoseJacob = computeDoseConstraintJacobian(obj,dose)
- switch obj.parameters{3}
+ function cDoseJacob = computeDoseConstraintJacobian(this,dose)
+ switch this.parameters{3}
case 1 %logsumexp approx
- cDoseJacob = obj.computeDoseConstraintJacobianLogSumExp(dose);
+ cDoseJacob = this.computeDoseConstraintJacobianLogSumExp(dose);
case 2
- cDoseJacob = obj.computeDoseConstraintJacobianVoxelwise(dose);
+ cDoseJacob = this.computeDoseConstraintJacobianVoxelwise(dose);
otherwise
matRad_cfg = MatRad_Config.instance();
matRad_cfg.dispError('Min/max dose constraint evaluation method not known!');
@@ -170,38 +170,38 @@
methods (Access = private)
% LogSumExp Approximation
- function cDose = computeDoseConstraintFunctionLogSumExp(obj,dose)
+ function cDose = computeDoseConstraintFunctionLogSumExp(this,dose)
dose_min = min(dose);
dose_max = max(dose);
%Validate parameters
- if obj.parameters{1} <= 0 && isinf(obj.parameters{2}) %Constraint doesn't make sense (min = 0 & max = Inf)
+ if this.parameters{1} <= 0 && isinf(this.parameters{2}) %Constraint doesn't make sense (min = 0 & max = Inf)
cDose = [];
- elseif obj.parameters{2} == Inf %Only min dose
- cDose = dose_min - obj.epsilon * log( sum(exp((dose_min - dose)/obj.epsilon)));
- elseif obj.parameters{1} <= 0 %Only max dose
- cDose = dose_max + obj.epsilon * log( sum(exp((dose - dose_max)/obj.epsilon)));
+ elseif this.parameters{2} == Inf %Only min dose
+ cDose = dose_min - this.epsilon * log( sum(exp((dose_min - dose)/this.epsilon)));
+ elseif this.parameters{1} <= 0 %Only max dose
+ cDose = dose_max + this.epsilon * log( sum(exp((dose - dose_max)/this.epsilon)));
else %both are set sensible
- cDose(2,1) = dose_max + obj.epsilon * log( sum(exp((dose - dose_max)/obj.epsilon)));
- cDose(1,1) = dose_min - obj.epsilon * log( sum(exp((dose_min - dose)/obj.epsilon)));
+ cDose(2,1) = dose_max + this.epsilon * log( sum(exp((dose - dose_max)/this.epsilon)));
+ cDose(1,1) = dose_min - this.epsilon * log( sum(exp((dose_min - dose)/this.epsilon)));
end
end
- function cDoseJacob = computeDoseConstraintJacobianLogSumExp(obj,dose)
+ function cDoseJacob = computeDoseConstraintJacobianLogSumExp(this,dose)
%Validate parameters
- if obj.parameters{1} <= 0 && isinf(obj.parameters{2}) %Constraint doesn't make sense (min = 0 & max = Inf)
+ if this.parameters{1} <= 0 && isinf(this.parameters{2}) %Constraint doesn't make sense (min = 0 & max = Inf)
cDoseJacob = [];
- elseif obj.parameters{2} == Inf %Only min dose
- cDoseJacob(:,1) = exp( (min(dose)-dose)/obj.epsilon );
+ elseif this.parameters{2} == Inf %Only min dose
+ cDoseJacob(:,1) = exp( (min(dose)-dose)/this.epsilon );
cDoseJacob(:,1) = cDoseJacob(:,1)/sum(cDoseJacob(:,1));
- elseif obj.parameters{1} <= 0 %Only max dose
- cDoseJacob(:,1) = exp( (dose-max(dose))/obj.epsilon );
+ elseif this.parameters{1} <= 0 %Only max dose
+ cDoseJacob(:,1) = exp( (dose-max(dose))/this.epsilon );
cDoseJacob(:,1) = cDoseJacob(:,1)/sum(cDoseJacob(:,1));
else %both are set sensible
- cDoseJacob(:,1) = exp( (min(dose)-dose)/obj.epsilon );
+ cDoseJacob(:,1) = exp( (min(dose)-dose)/this.epsilon );
cDoseJacob(:,1) = cDoseJacob(:,1)/sum(cDoseJacob(:,1));
- cDoseJacob(:,2) = exp( (dose-max(dose))/obj.epsilon );
+ cDoseJacob(:,2) = exp( (dose-max(dose))/this.epsilon );
cDoseJacob(:,2) = cDoseJacob(:,2)/sum(cDoseJacob(:,2));
end
@@ -209,10 +209,10 @@
end
%Exact voxel-wise
- function cDose = computeDoseConstraintFunctionVoxelwise(obj,dose)
+ function cDose = computeDoseConstraintFunctionVoxelwise(this,dose)
cDose = dose;
end
- function cDoseJacob = computeDoseConstraintJacobianVoxelwise(obj,dose)
+ function cDoseJacob = computeDoseConstraintJacobianVoxelwise(this,dose)
cDoseJacob = speye(numel(dose),numel(dose));
end
end
diff --git a/optimization/+DoseConstraints/matRad_MinMaxEUD.m b/matRad/optimization/+DoseConstraints/matRad_MinMaxEUD.m
similarity index 75%
rename from optimization/+DoseConstraints/matRad_MinMaxEUD.m
rename to matRad/optimization/+DoseConstraints/matRad_MinMaxEUD.m
index 1f6346294..36669d8a1 100644
--- a/optimization/+DoseConstraints/matRad_MinMaxEUD.m
+++ b/matRad/optimization/+DoseConstraints/matRad_MinMaxEUD.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
- % distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -30,7 +30,7 @@
end
methods
- function obj = matRad_MinMaxEUD(exponent,eudMin,eudMax)
+ function this = matRad_MinMaxEUD(exponent,eudMin,eudMax)
%If we have a struct in first argument
if nargin == 1 && isstruct(exponent)
inputStruct = exponent;
@@ -41,47 +41,47 @@
end
%Call Superclass Constructor (for struct initialization)
- obj@DoseConstraints.matRad_DoseConstraint(inputStruct);
+ this@DoseConstraints.matRad_DoseConstraint(inputStruct);
%now handle initialization from other parameters
if ~initFromStruct
if nargin == 3 && isscalar(eudMax)
- obj.parameters{3} = eudMax;
+ this.parameters{3} = eudMax;
end
if nargin >= 1 && isscalar(exponent)
- obj.parameters{1} = exponent;
+ this.parameters{1} = exponent;
end
if nargin >= 2 && isscalar(eudMin)
- obj.parameters{2} = eudMin;
+ this.parameters{2} = eudMin;
end
end
end
%Overloads the struct function to add constraint specific
%parameters
- function s = struct(obj)
- s = struct@DoseConstraints.matRad_DoseConstraint(obj);
+ function s = struct(this)
+ s = struct@DoseConstraints.matRad_DoseConstraint(this);
%Nothing to do here...
end
- function cu = upperBounds(obj,n)
- cu = obj.parameters{3};
+ function cu = upperBounds(this,n)
+ cu = this.parameters{3};
end
- function cl = lowerBounds(obj,n)
- cl = obj.parameters{2};
+ function cl = lowerBounds(this,n)
+ cl = this.parameters{2};
end
%% Calculates the Constraint Function value
- function cDose = computeDoseConstraintFunction(obj,dose)
- k = obj.parameters{1};
+ function cDose = computeDoseConstraintFunction(this,dose)
+ k = this.parameters{1};
cDose = mean(dose.^k)^(1/k);
end
%% Calculates the Constraint jacobian
- function cDoseJacob = computeDoseConstraintJacobian(obj,dose)
- k = obj.parameters{1};
+ function cDoseJacob = computeDoseConstraintJacobian(this,dose)
+ k = this.parameters{1};
cDoseJacob = nthroot(1/numel(dose),k) * sum(dose.^k)^((1-k)/k) * (dose.^(k-1));
end
end
diff --git a/optimization/+DoseConstraints/matRad_MinMaxMeanDose.m b/matRad/optimization/+DoseConstraints/matRad_MinMaxMeanDose.m
similarity index 77%
rename from optimization/+DoseConstraints/matRad_MinMaxMeanDose.m
rename to matRad/optimization/+DoseConstraints/matRad_MinMaxMeanDose.m
index b9f536782..afd751b07 100644
--- a/optimization/+DoseConstraints/matRad_MinMaxMeanDose.m
+++ b/matRad/optimization/+DoseConstraints/matRad_MinMaxMeanDose.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
- % distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -30,7 +30,7 @@
end
methods
- function obj = matRad_MinMaxMeanDose(minMeanDose,maxMeanDose)
+ function this = matRad_MinMaxMeanDose(minMeanDose,maxMeanDose)
%If we have a struct in first argument
if nargin == 1 && isstruct(minMeanDose)
@@ -42,44 +42,44 @@
end
% Call Superclass Constructor (for struct initialization)
- obj@DoseConstraints.matRad_DoseConstraint(inputStruct);
+ this@DoseConstraints.matRad_DoseConstraint(inputStruct);
% now handle initialization from other parameters
if ~initFromStruct
if nargin == 2 && isscalar(maxMeanDose)
- obj.parameters{2} = maxMeanDose;
+ this.parameters{2} = maxMeanDose;
end
if nargin >= 1 && isscalar(minMeanDose)
- obj.parameters{1} = minMeanDose;
+ this.parameters{1} = minMeanDose;
end
end
end
- function cu = upperBounds(obj,n)
- cu = obj.parameters{2};
+ function cu = upperBounds(this,n)
+ cu = this.parameters{2};
end
- function cl = lowerBounds(obj,n)
- cl = obj.parameters{1};
+ function cl = lowerBounds(this,n)
+ cl = this.parameters{1};
end
%Overloads the struct function to add constraint specific
%parameters
- function s = struct(obj)
- s = struct@DoseConstraints.matRad_DoseConstraint(obj);
+ function s = struct(this)
+ s = struct@DoseConstraints.matRad_DoseConstraint(this);
%Nothing to do here...
end
%% Calculates the Constraint Function value
- function cDose = computeDoseConstraintFunction(obj,dose)
+ function cDose = computeDoseConstraintFunction(this,dose)
cDose = mean(dose);
end
%% Calculates the Constraint jacobian
- function cDoseJacob = computeDoseConstraintJacobian(obj,dose)
+ function cDoseJacob = computeDoseConstraintJacobian(this,dose)
cDoseJacob = ones(numel(dose),1)./numel(dose);
end
end
diff --git a/optimization/+DoseObjectives/matRad_DoseObjective.m b/matRad/optimization/+DoseObjectives/matRad_DoseObjective.m
similarity index 86%
rename from optimization/+DoseObjectives/matRad_DoseObjective.m
rename to matRad/optimization/+DoseObjectives/matRad_DoseObjective.m
index e2fede50d..5270efd6f 100644
--- a/optimization/+DoseObjectives/matRad_DoseObjective.m
+++ b/matRad/optimization/+DoseObjectives/matRad_DoseObjective.m
@@ -12,7 +12,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -20,7 +20,13 @@
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
properties (Abstract, Access = public)
- penalty %Optimization penalty
+ penalty %Optimization penalty
+ end
+
+ methods (Static)
+ function rob = availableRobustness()
+ rob = {'none','STOCH','PROB','VWWC','VWWC_INV','COWC','OWC'}; %By default, no robustness is available
+ end
end
%These should be abstract methods, however Octave can't parse them. As soon
@@ -54,6 +60,7 @@
s = struct@matRad_DoseOptimizationFunction(obj);
s.penalty = obj.penalty;
end
- end
+
+ end
end
diff --git a/optimization/+DoseObjectives/matRad_EUD.m b/matRad/optimization/+DoseObjectives/matRad_EUD.m
similarity index 87%
rename from optimization/+DoseObjectives/matRad_EUD.m
rename to matRad/optimization/+DoseObjectives/matRad_EUD.m
index 15e73e9cf..d7f8501d2 100644
--- a/optimization/+DoseObjectives/matRad_EUD.m
+++ b/matRad/optimization/+DoseObjectives/matRad_EUD.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -73,7 +73,7 @@
%This check is not needed since dose is always positive
%if powersum > 0
- fDose = obj.penalty * (nthroot(powersum/numel(dose),k) - obj.parameters{1})^2;
+ fDose = (nthroot(powersum/numel(dose),k) - obj.parameters{1})^2;
%end
end
@@ -93,10 +93,11 @@
%if powersum > 0
%derivatives = nthroot(1/numel(dose),k) * powersum^((1-k)/k) * (dose.^(k-1));
- fDoseGrad = 2 * obj.penalty * nthroot(1/numel(dose),k) * powersum^((1-k)/k) * (dose.^(k-1)) .* (nthroot(powersum/numel(dose),k) - obj.parameters{1});
+ fDoseGrad = 2 * nthroot(1/numel(dose),k) * powersum^((1-k)/k) * (dose.^(k-1)) .* (nthroot(powersum/numel(dose),k) - obj.parameters{1});
%end
if any(~isfinite(fDoseGrad)) % check for inf and nan for numerical stability
- error(['EUD computation failed. Reduce exponent to resolve numerical problems.']);
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError(['EUD computation failed. Reduce exponent to resolve numerical problems.']);
end
end
end
diff --git a/optimization/+DoseObjectives/matRad_MaxDVH.m b/matRad/optimization/+DoseObjectives/matRad_MaxDVH.m
similarity index 93%
rename from optimization/+DoseObjectives/matRad_MaxDVH.m
rename to matRad/optimization/+DoseObjectives/matRad_MaxDVH.m
index 2d5a2bc8f..3b90a4e96 100644
--- a/optimization/+DoseObjectives/matRad_MaxDVH.m
+++ b/matRad/optimization/+DoseObjectives/matRad_MaxDVH.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -74,7 +74,7 @@
deviation(dose < obj.parameters{1} | dose > d_ref2) = 0;
% claculate objective function
- fDose = (obj.penalty/numel(dose))*(deviation'*deviation);
+ fDose = (1/numel(dose))*(deviation'*deviation);
end
%% Calculates the Objective Function gradient
@@ -91,7 +91,7 @@
deviation(dose < obj.parameters{1} | dose > d_ref2) = 0;
% calculate delta
- fDoseGrad = 2 * (obj.penalty/numel(dose))*deviation;
+ fDoseGrad = (2/numel(dose))*deviation;
end
end
diff --git a/matRad/optimization/+DoseObjectives/matRad_MeanDose.m b/matRad/optimization/+DoseObjectives/matRad_MeanDose.m
new file mode 100644
index 000000000..fa196c411
--- /dev/null
+++ b/matRad/optimization/+DoseObjectives/matRad_MeanDose.m
@@ -0,0 +1,130 @@
+classdef matRad_MeanDose < DoseObjectives.matRad_DoseObjective
+% matRad_MeanDose Implements a penalized MeanDose objective
+% See matRad_DoseObjective for interface description
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2020 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ name = 'Mean Dose';
+ parameterNames = {'d^{ref}','f_{diff}'}; %When optimizing to a reference, one might consider using a quadratic relationship with a non-linear optimizer
+ parameterTypes = {'dose',{'Linear','Quadratic'}};
+ end
+
+ properties
+ parameters = {0,1};
+ penalty = 1;
+ end
+
+ methods
+ function obj = matRad_MeanDose(penalty,dMeanRef,fDiff)
+
+ % if we have a struct in first argument
+ if nargin == 1 && isstruct(penalty)
+ inputStruct = penalty;
+ initFromStruct = true;
+ else
+ initFromStruct = false;
+ inputStruct = [];
+ end
+
+ %Call Superclass Constructor (for struct initialization)
+ obj@DoseObjectives.matRad_DoseObjective(inputStruct);
+
+ if ~initFromStruct
+ if nargin < 3 || ~ischar(fDiff)
+ fDiff = 'Linear';
+ end
+
+ fDiffIx = find(strcmp(fDiff,obj.parameterTypes{2}));
+
+ if isempty(fDiffIx) || numel(fDiffIx) > 1
+ fDiffIx = 1;
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Mean dose difference function can only be %s! Using %s difference.', strjoin(obj.parameterTypes{2},' or '), obj.parameterTypes{2}{fDiffIx});
+ end
+
+ obj.parameters{2} = fDiffIx;
+
+
+ if nargin >= 2 && isscalar(dMeanRef)
+ obj.parameters{1} = dMeanRef;
+ end
+
+ if nargin >= 1 && isscalar(penalty)
+ obj.penalty = penalty;
+ end
+ end
+
+ %% Downwards compatability / set default values
+ %TODO: maybe move into set method for parameters
+ if numel(obj.parameters) < 1
+ obj.parameters{1} = 0;
+ end
+
+ if numel(obj.parameters) < 2
+ obj.parameters{2} = 1;
+ end
+
+ end
+
+ %% Calculates the Objective Function value
+ function fDose = computeDoseObjectiveFunction(obj,dose)
+ switch obj.parameters{2}
+ case 1
+ fDose = obj.objectiveLinearDiff(dose);
+ case 2
+ fDose = obj.objectiveQuadraticDiff(dose);
+ otherwise
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid setting for %s in Mean Dose Objective!',obj.parameterNames{2});
+ end
+ end
+
+ %% Calculates the Objective Function gradient
+ function fDoseGrad = computeDoseObjectiveGradient(obj,dose)
+ switch obj.parameters{2}
+ case 1
+ fDoseGrad = obj.gradientLinearDiff(dose);
+ case 2
+ fDoseGrad = obj.gradientQuadraticDiff(dose);
+ otherwise
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid setting for %s in Mean Dose Objective!',obj.parameterNames{2});
+ end
+ end
+ end
+
+ methods (Access = protected)
+ function fDose = objectiveQuadraticDiff(obj,dose)
+ fDose = (sum(dose(:))/numel(dose) - obj.parameters{1})^2;
+ end
+
+ function fDoseGrad = gradientQuadraticDiff(obj,dose)
+ fDoseGrad = 2*(sum(dose(:))/numel(dose)-obj.parameters{1}) * ones(size(dose(:)))/numel(dose);
+ end
+
+ function fDose = objectiveLinearDiff(obj,dose)
+ fDose = abs(sum(dose(:))/numel(dose) - obj.parameters{1});
+ end
+
+ function fDoseGrad = gradientLinearDiff(obj,dose)
+ fDoseGrad = (1/numel(dose))*sign(dose(:)-obj.parameters{1});
+ end
+ end
+
+end
+
diff --git a/optimization/+DoseObjectives/matRad_MinDVH.m b/matRad/optimization/+DoseObjectives/matRad_MinDVH.m
similarity index 93%
rename from optimization/+DoseObjectives/matRad_MinDVH.m
rename to matRad/optimization/+DoseObjectives/matRad_MinDVH.m
index 2f49dceaf..7935211c9 100644
--- a/optimization/+DoseObjectives/matRad_MinDVH.m
+++ b/matRad/optimization/+DoseObjectives/matRad_MinDVH.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -75,7 +75,7 @@
deviation(dose > obj.parameters{1} | dose < d_ref2) = 0;
% claculate objective function
- fDose = (obj.penalty/numel(dose))*(deviation'*deviation);
+ fDose = (1/numel(dose))*(deviation'*deviation);
end
%% Calculates the Objective Function gradient
@@ -92,7 +92,7 @@
deviation(dose > obj.parameters{1} | dose < d_ref2) = 0;
% calculate delta
- fDoseGrad = 2 * (obj.penalty/numel(dose))*deviation;
+ fDoseGrad = (2/numel(dose))*deviation;
end
end
diff --git a/optimization/+DoseObjectives/matRad_SquaredDeviation.m b/matRad/optimization/+DoseObjectives/matRad_SquaredDeviation.m
similarity index 84%
rename from optimization/+DoseObjectives/matRad_SquaredDeviation.m
rename to matRad/optimization/+DoseObjectives/matRad_SquaredDeviation.m
index 43fc442f4..8ddbe80a0 100644
--- a/optimization/+DoseObjectives/matRad_SquaredDeviation.m
+++ b/matRad/optimization/+DoseObjectives/matRad_SquaredDeviation.m
@@ -13,7 +13,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -30,6 +30,7 @@
parameters = {60};
penalty = 1;
end
+
methods
function obj = matRad_SquaredDeviation(penalty,dRef)
@@ -45,7 +46,7 @@
%Call Superclass Constructor (for struct initialization)
obj@DoseObjectives.matRad_DoseObjective(inputStruct);
-
+
%now handle initialization from other parameters
if ~initFromStruct
if nargin == 2 && isscalar(dRef)
@@ -63,7 +64,7 @@
% deviation : dose minus prefered dose
deviation = dose - obj.parameters{1};
% claculate objective function
- fDose = obj.penalty/numel(dose) * (deviation'*deviation);
+ fDose = 1/numel(dose) * (deviation'*deviation);
end
%% Calculates the Objective Function gradient
@@ -72,7 +73,14 @@
deviation = dose - obj.parameters{1};
% calculate delta
- fDoseGrad = 2 * obj.penalty/numel(dose) * deviation;
+ fDoseGrad = 2 * 1/numel(dose) * deviation;
+ end
+ end
+
+ methods (Static)
+ function rob = availableRobustness()
+ rob = DoseObjectives.matRad_DoseObjective.availableRobustness();
+ rob{end+1} = 'PROB'; %By default, no robustness is available
end
end
diff --git a/optimization/+DoseObjectives/matRad_SquaredOverdosing.m b/matRad/optimization/+DoseObjectives/matRad_SquaredOverdosing.m
similarity index 92%
rename from optimization/+DoseObjectives/matRad_SquaredOverdosing.m
rename to matRad/optimization/+DoseObjectives/matRad_SquaredOverdosing.m
index b2e25e40f..0616fc8e1 100644
--- a/optimization/+DoseObjectives/matRad_SquaredOverdosing.m
+++ b/matRad/optimization/+DoseObjectives/matRad_SquaredOverdosing.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -64,7 +64,7 @@
overdose(overdose<0) = 0;
% claculate objective function
- fDose = obj.penalty/numel(dose) * (overdose'*overdose);
+ fDose = 1/numel(dose) * (overdose'*overdose);
end
%% Calculates the Objective Function gradient
@@ -76,7 +76,7 @@
overdose(overdose<0) = 0;
% calculate delta
- fDoseGrad = 2 * obj.penalty/numel(dose) * overdose;
+ fDoseGrad = 2 * 1/numel(dose) * overdose;
end
end
diff --git a/optimization/+DoseObjectives/matRad_SquaredUnderdosing.m b/matRad/optimization/+DoseObjectives/matRad_SquaredUnderdosing.m
similarity index 92%
rename from optimization/+DoseObjectives/matRad_SquaredUnderdosing.m
rename to matRad/optimization/+DoseObjectives/matRad_SquaredUnderdosing.m
index 5c0a84aa7..ec40612c7 100644
--- a/optimization/+DoseObjectives/matRad_SquaredUnderdosing.m
+++ b/matRad/optimization/+DoseObjectives/matRad_SquaredUnderdosing.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -64,7 +64,7 @@
underdose(underdose>0) = 0;
% claculate objective function
- fDose = obj.penalty/numel(dose) * (underdose'*underdose);
+ fDose = 1/numel(dose) * (underdose'*underdose);
end
%% Calculates the Objective Function gradient
@@ -76,7 +76,7 @@
underdose(underdose>0) = 0;
% calculate delta
- fDoseGrad = 2 * obj.penalty/numel(dose) * underdose;
+ fDoseGrad = 2/numel(dose) * underdose;
end
end
diff --git a/matRad/optimization/@matRad_OptimizationProblem/matRad_OptimizationProblem.m b/matRad/optimization/@matRad_OptimizationProblem/matRad_OptimizationProblem.m
new file mode 100644
index 000000000..ca1b09db2
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblem/matRad_OptimizationProblem.m
@@ -0,0 +1,122 @@
+classdef matRad_OptimizationProblem < handle
+ %matRad_OptimizationProblem Main Class for fluence optimization problems
+ % Describes a standard fluence optimization problem by providing the
+ % implementation of the objective & constraint function/gradient wrappers
+ % and managing the mapping and backprojection of the respective dose-
+ % related quantity
+ %
+ % References
+ % [1] https://doi.org/10.1093/imanum/draa038
+ % [2] https://doi.org/10.1002/mp.14148
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2020 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ BP
+ quantityOpt = '';
+ useMaxApprox = 'logsumexp'; %'pnorm'; %'logsumexp'; %'none';
+ p = 30; %Can be chosen larger (closer to maximum) or smaller (closer to mean). Only tested 20 >= p >= 1
+
+ minimumW = NaN;
+ maximumW = NaN;
+ end
+
+ methods
+ function obj = matRad_OptimizationProblem(backProjection)
+ obj.BP = backProjection;
+ end
+
+ %Objective function declaration
+ fVal = matRad_objectiveFunction(optiProb,w,dij,cst)
+
+ %Objective gradient declaration
+ fGrad = matRad_objectiveGradient(optiProb,w,dij,cst)
+
+ %Constraint function declaration
+ cVal = matRad_constraintFunctions(optiProb,w,dij,cst)
+
+ %Constraint Jacobian declaration
+ cJacob = matRad_constraintJacobian(optiProb,w,dij,cst)
+
+ %Jacobian Structure
+ jacobStruct = matRad_getJacobianStructure(optiProb,w,dij,cst)
+
+ [cl,cu] = matRad_getConstraintBounds(optiProb,cst)
+
+ function lb = lowerBounds(optiProb,w)
+ minW = optiProb.minimumW;
+ if isnan(minW)
+ lb = zeros(size(w));
+ elseif isscalar(minW)
+ lb = minW*ones(size(w));
+ elseif isvector(minW) && all(size(minW) == size(w))
+ lb = minW;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Minimum Bounds for Optimization Problem could not be set!');
+ end
+ end
+
+ function ub = upperBounds(optiProb,w)
+ maxW = optiProb.maximumW;
+ if isnan(maxW)
+ ub = Inf(size(w));
+ elseif isscalar(maxW)
+ ub = maxW*ones(size(w));
+ elseif isvector(maxW) && all(size(maxW) == size(w))
+ ub = maxW;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Maximum Bounds for Optimization Problem could not be set!');
+ end
+ end
+ end
+
+ methods (Access = protected)
+ function [val,grad] = logSumExp(optiProb,fVals)
+ % [1] stable log sum exp trick
+ [fMax,ixMax] = max(fVals(:));
+
+ ix = true(numel(fVals),1);
+ ix(ixMax) = 0;
+
+ tmp = exp(fVals - fMax);
+
+ expSum = sum(tmp(ix));
+ val = fMax + log1p(expSum); %log1p(x) = Matlab's numerically accurate log(1+x)
+
+ grad = tmp ./ (1 + expSum);
+ end
+
+ function [val,grad] = pNorm(optiProb,fVals,n)
+ % Implemented as proposed in [2] including a normalization for stability of the exponent.
+ if nargout < 3
+ n = numel(fVals);
+ end
+
+ p = optiProb.p;
+
+ valMax = max(fVals(:));
+ tmp = fVals./valMax;
+
+ pNormVal = sum(tmp(:).^p)^(1/p);
+
+ fac = (1/n)^(1/p);
+ val = valMax*fac*pNormVal;
+
+ grad = fac * (tmp ./ pNormVal).^(p-1);
+ end
+ end
+end
+
diff --git a/matRad/optimization/@matRad_OptimizationProblem/matRad_constraintFunctions.m b/matRad/optimization/@matRad_OptimizationProblem/matRad_constraintFunctions.m
new file mode 100644
index 000000000..41a7f9da5
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblem/matRad_constraintFunctions.m
@@ -0,0 +1,145 @@
+function c = matRad_constraintFunctions(optiProb,w,dij,cst)
+% matRad IPOPT callback: constraint function for inverse planning
+% supporting max dose constraint, min dose constraint, min mean dose constraint,
+% max mean dose constraint, min EUD constraint, max EUD constraint,
+% max DVH constraint, min DVH constraint
+%
+% call
+% c = matRad_constraintFunctions(optiProb,w,dij,cst)
+%
+% input
+% optiProb: option struct defining the type of optimization
+% w: bixel weight vector
+% dij: dose influence matrix
+% cst: matRad cst struct
+%
+% output
+% c: value of constraints
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2016 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+% get current dose / effect / RBExDose vector
+optiProb.BP.compute(dij,w);
+d = optiProb.BP.GetResult();
+
+% get the used scenarios
+useScen = optiProb.BP.scenarios;
+scenProb = optiProb.BP.scenarioProb;
+
+% retrieve matching 4D scenarios
+fullScen = cell(ndims(d),1);
+[fullScen{:}] = ind2sub(size(d),useScen);
+contourScen = fullScen{1};
+
+% Initializes constraints
+c = [];
+
+% compute objective function for every VOI.
+for i = 1:size(cst,1)
+
+ % Only take OAR or target VOI.
+ if ~isempty(cst{i,4}{1}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )
+
+ % loop over the number of constraints for the current VOI
+ for j = 1:numel(cst{i,6})
+
+ constraint = cst{i,6}{j};
+
+ % only perform computations for constraints
+ if isa(constraint,'DoseConstraints.matRad_DoseConstraint')
+
+ % rescale dose parameters to biological optimization quantity if required
+ constraint = optiProb.BP.setBiologicalDosePrescriptions(constraint,cst{i,5}.alphaX,cst{i,5}.betaX);
+
+ % retrieve the robustness type
+ robustness = constraint.robustness;
+
+ switch robustness
+ case 'none' % if conventional opt: just sum objectives of nominal dose
+ d_i = d{1}(cst{i,4}{1});
+ c = [c; constraint.computeDoseConstraintFunction(d_i)];
+
+ case 'PROB' % if prob opt: sum up expectation value of objectives
+
+ d_i = dExp{1}(cst{i,4}{1});
+ c = [c; constraint.computeDoseConstraintFunction(d_i)];
+
+ case 'VWWC' % voxel-wise worst case - takes minimum dose in TARGET and maximum in OAR
+ contourIx = unique(contourScen);
+ if ~isscalar(contourIx)
+ % voxels need to be tracked through the 4D CT,
+ % not yet implemented
+ matRad_cfg.dispError('4D VWWC optimization is currently not supported');
+ end
+
+ % prepare min/max dose vector
+ if ~exist('d_tmp','var')
+ d_tmp = [d{useScen}];
+ end
+
+ d_Scen = d_tmp(cst{i,4}{contourIx},:);
+
+ d_max = max(d_Scen,[],2);
+ d_min = min(d_Scen,[],2);
+
+ if isequal(cst{i,3},'OAR')
+ d_i = d_max;
+ elseif isequal(cst{i,3},'TARGET')
+ d_i = d_min;
+ end
+
+ c = [c; constraint.computeDoseConstraintFunction(d_i)];
+
+ case 'VWWC_INV' %inverse voxel-wise conformitiy - takes maximum dose in TARGET and minimum in OAR
+ contourIx = unique(contourScen);
+ if ~isscalar(contourIx)
+ % voxels need to be tracked through the 4D CT,
+ % not yet implemented
+ matRad_cfg.dispError('4D inverted VWWC optimization is currently not supported');
+ end
+
+ % prepare min/max dose vector
+ if ~exist('d_tmp','var')
+ d_tmp = [d{:}];
+ end
+
+ d_Scen = d_tmp(cst{i,4}{contourIx},:);
+ d_max = max(d_Scen,[],2);
+ d_min = min(d_Scen,[],2);
+
+ if isequal(cst{i,3},'OAR')
+ d_i = d_min;
+ elseif isequal(cst{i,3},'TARGET')
+ d_i = d_max;
+ end
+
+ c = [c; constraint.computeDoseConstraintFunction(d_i)];
+ otherwise
+ matRad_cfg.dispError('Robustness setting %s not yet supported!',constraint.robustness);
+ end
+
+
+ end
+
+ end % if we are a constraint
+
+ end % over all defined constraints & objectives
+
+end % if structure not empty and oar or target
+
+end % over all structures
diff --git a/matRad/optimization/@matRad_OptimizationProblem/matRad_constraintJacobian.m b/matRad/optimization/@matRad_OptimizationProblem/matRad_constraintJacobian.m
new file mode 100644
index 000000000..71816a8d6
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblem/matRad_constraintJacobian.m
@@ -0,0 +1,224 @@
+function jacob = matRad_constraintJacobian(optiProb,w,dij,cst)
+% matRad IPOPT callback: jacobian function for inverse planning
+% supporting max dose constraint, min dose constraint, min mean dose constraint,
+% max mean dose constraint, min EUD constraint, max EUD constraint, max DVH
+% constraint, min DVH constraint
+%
+% call
+% jacob = matRad_jacobFunc(optiProb,w,dij,cst)
+%
+% input
+% optiProb: option struct defining the type of optimization
+% w: bixel weight vector
+% dij: dose influence matrix
+% cst: matRad cst struct
+%
+% output
+% jacob: jacobian of constraint function
+%
+% References
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2016 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% get current dose / effect / RBExDose vector
+%d = matRad_backProjection(w,dij,optiProb);
+%d = optiProb.matRad_backProjection(w,dij);
+optiProb.BP.compute(dij,w);
+d = optiProb.BP.GetResult();
+
+% initialize jacobian (only single scenario supported in optimization)
+jacob = sparse([]);
+
+% initialize projection matrices and id containers
+DoseProjection{1} = sparse([]);
+mAlphaDoseProjection{1} = sparse([]);
+mSqrtBetaDoseProjection{1} = sparse([]);
+voxelID = [];
+constraintID = [];
+
+% get the used scenarios
+useScen = optiProb.BP.scenarios;
+scenProb = optiProb.BP.scenarioProb;
+
+% retrieve matching 4D scenarios
+fullScen = cell(ndims(d),1);
+[fullScen{:}] = ind2sub(size(d),useScen);
+contourScen = fullScen{1};
+
+% compute objective function for every VOI.
+for i = 1:size(cst,1)
+
+ % Only take OAR or target VOI.
+ if ~isempty(cst{i,4}{1}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )
+
+ % loop over the number of constraints for the current VOI
+ for j = 1:numel(cst{i,6})
+
+ constraint = cst{i,6}{j}; %Get the Optimization Object
+
+ % only perform computations for constraints
+ if isa(constraint,'DoseConstraints.matRad_DoseConstraint')
+
+ % retrieve the robustness type
+ robustness = constraint.robustness;
+
+ % rescale dose parameters to biological optimization quantity if required
+ constraint = optiProb.BP.setBiologicalDosePrescriptions(constraint,cst{i,5}.alphaX,cst{i,5}.betaX);
+
+ switch robustness
+
+ case 'none' % if conventional opt: just sum objectiveectives of nominal dose
+ d_i = d{1}(cst{i,4}{1});
+ jacobSub = constraint.computeDoseConstraintJacobian(d_i);
+
+ case 'PROB' % if prob opt: sum up expectation value of objectives
+
+ d_i = dExp{1}(cst{i,4}{1});
+ jacobSub = constraint.computeDoseConstraintJacobian(d_i);
+
+ case 'VWWC' % voxel-wise worst case - takes minimum dose in TARGET and maximum in OAR
+ contourIx = unique(contourScen);
+ if ~isscalar(contourIx)
+ % voxels need to be tracked through the 4D CT,
+ % not yet implemented
+ matRad_cfg.dispError('4D VWWC optimization is currently not supported');
+ end
+
+ % prepare min/max dose vector
+ if ~exist('d_tmp','var')
+ d_tmp = [d{useScen}];
+ end
+
+ d_Scen = d_tmp(cst{i,4}{contourIx},:);
+
+ d_max = max(d_Scen,[],2);
+ d_min = min(d_Scen,[],2);
+
+ if isequal(cst{i,3},'OAR')
+ d_i = d_max;
+ elseif isequal(cst{i,3},'TARGET')
+ d_i = d_min;
+ end
+ jacobSub = constraint.computeDoseConstraintJacobian(d_i);
+
+ case 'VWWC_INV' %inverse voxel-wise conformitiy - takes maximum dose in TARGET and minimum in OAR
+ contourIx = unique(contourScen);
+ if ~isscalar(contourIx)
+ % voxels need to be tracked through the 4D CT,
+ % not yet implemented
+ matRad_cfg.dispError('4D inverted VWWC optimization is currently not supported');
+ end
+
+ % prepare min/max dose vector
+ if ~exist('d_tmp','var')
+ d_tmp = [d{useScen}];
+ end
+
+ d_Scen = d_tmp(cst{i,4}{contourIx},:);
+
+ d_max = max(d_Scen,[],2);
+ d_min = min(d_Scen,[],2);
+
+ if isequal(cst{i,3},'OAR')
+ d_i = d_min;
+ elseif isequal(cst{i,3},'TARGET')
+ d_i = d_max;
+ end
+ jacobSub = constraint.computeDoseConstraintJacobian(d_i);
+
+ otherwise
+ matRad_cfg.dispError('Robustness setting %s not yet supported!',constraint.robustness);
+ end
+
+ nConst = size(jacobSub,2);
+
+ %Iterate through columns of the sub-jacobian
+ if isa(optiProb.BP,'matRad_DoseProjection') && ~isempty(jacobSub) || isa(optiProb.BP,'matRad_ConstantRBEProjection')
+
+ startIx = size(DoseProjection{1},2) + 1;
+ %First append the Projection matrix with sparse zeros
+ DoseProjection{1} = [DoseProjection{1},sparse(dij.doseGrid.numOfVoxels,nConst)];
+
+ %Now directly write the jacobian in there
+ DoseProjection{1}(cst{i,4}{1},startIx:end) = jacobSub;
+
+ elseif isa(optiProb.BP,'matRad_EffectProjection') && ~isempty(jacobSub)
+
+ if isa(optiProb.BP,'matRad_VariableRBEProjection')
+ scaledEffect = (dij.gamma(cst{i,4}{1}) + d_i);
+ jacobSub = jacobSub./(2*dij.bx(cst{i,4}{1}) .* scaledEffect);
+ end
+
+ startIx = size(mAlphaDoseProjection{1},2) + 1;
+
+ %First append the alphaDose matrix with sparse
+ %zeros then insert
+ mAlphaDoseProjection{1} = [mAlphaDoseProjection{1},sparse(dij.doseGrid.numOfVoxels,nConst)];
+ mAlphaDoseProjection{1}(cst{i,4}{1},startIx:end) = jacobSub;
+
+ %The betadose has a different structure due to the
+ %quadratic transformation, but in principle the
+ %same as above
+ mSqrtBetaDoseProjection{1} = [mSqrtBetaDoseProjection{1}, sparse(repmat(cst{i,4}{1},nConst,1),repmat(1:numel(cst{i,4}{1}),1,nConst),2*reshape(jacobSub',[],1),dij.doseGrid.numOfVoxels,nConst*numel(cst{i,4}{1}))];
+
+ if isempty(constraintID)
+ newID = 1;
+ else
+ newID = constraintID(end)+1;
+ end
+
+ voxelID = [voxelID;repmat(cst{i,4}{1},nConst,1)]; %Keep track of voxels for organizing the sqrt(beta)Dose projection later
+ constraintID = [constraintID, ...
+ reshape(ones(numel(cst{i,4}{1}),1)*[newID:newID+nConst-1],[1 nConst*numel(cst{i,4}{1})])]; %Keep track of constraints for organizing the sqrt(beta)Dose projection later
+ end
+
+
+ end
+
+ end
+
+ end
+
+end
+
+
+scenario = 1;
+% enter if statement also for protons using a constant RBE
+if isa(optiProb.BP,'matRad_DoseProjection')
+
+ if ~isempty(DoseProjection{scenario})
+ jacob = DoseProjection{scenario}' * dij.physicalDose{scenario};
+ end
+
+elseif isa(optiProb.BP,'matRad_ConstantRBEProjection')
+
+ if ~isempty(DoseProjection{scenario})
+ jacob = DoseProjection{scenario}' * dij.RBE * dij.physicalDose{scenario};
+ end
+
+elseif isa(optiProb.BP,'matRad_EffectProjection')
+
+ if ~isempty(mSqrtBetaDoseProjection{scenario}) && ~isempty(mAlphaDoseProjection{scenario})
+ mSqrtBetaDoseProjection{scenario} = mSqrtBetaDoseProjection{scenario}' * dij.mSqrtBetaDose{scenario} * w;
+ mSqrtBetaDoseProjection{scenario} = sparse(voxelID,constraintID,mSqrtBetaDoseProjection{scenario},...
+ size(mAlphaDoseProjection{scenario},1),size(mAlphaDoseProjection{scenario},2));
+
+ jacob = mAlphaDoseProjection{scenario}' * dij.mAlphaDose{scenario} +...
+ mSqrtBetaDoseProjection{scenario}' * dij.mSqrtBetaDose{scenario};
+
+ end
+end
+
diff --git a/optimization/@matRad_OptimizationProblem/matRad_getConstraintBounds.m b/matRad/optimization/@matRad_OptimizationProblem/matRad_getConstraintBounds.m
similarity index 70%
rename from optimization/@matRad_OptimizationProblem/matRad_getConstraintBounds.m
rename to matRad/optimization/@matRad_OptimizationProblem/matRad_getConstraintBounds.m
index 65800722c..0be9ad58c 100644
--- a/optimization/@matRad_OptimizationProblem/matRad_getConstraintBounds.m
+++ b/matRad/optimization/@matRad_OptimizationProblem/matRad_getConstraintBounds.m
@@ -20,7 +20,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -47,19 +47,32 @@
optiFunc = cst{i,6}{j};
% only perform computations for constraints
+%{
+ if ~isempty(strfind(cst{i,6}(j).type,'constraint'))
+
+ if isequal(options.quantityOpt,'effect')
+ param = cst{i,5}.alphaX .* cst{i,6}(j).dose + cst{i,5}.betaX .* cst{i,6}(j).dose.^2;
+ else
+ param = cst{i,6}(j).dose;
+ end
+
+ if strcmp(cst{i,6}(j).robustness,'none') || strcmp(cst{i,6}(j).robustness,'probabilistic') || strcmp(cst{i,6}(j).robustness,'VWWC') ||...
+ strcmp(cst{i,6}(j).robustness,'COWC') || strcmp(cst{i,6}(j).robustness,'VWWC_CONF')|| strcmp(cst{i,6}(j).robustness,'OWC')
+
+
+ [clTmp,cuTmp] = matRad_getConstBounds(cst{i,6}(j),param);
+%}
%if ~isempty(strfind(cst{i,6}{j}.type,'constraint'))
if isa(optiFunc,'DoseConstraints.matRad_DoseConstraint')
-
if isEffectBP
- doses = optiFunc.getDoseParameters();
-
+
+ doses = optiFunc.getDoseParameters();
effect = cst{i,5}.alphaX*doses + cst{i,5}.betaX*doses.^2;
optiFunc = optiFunc.setDoseParameters(effect);
end
-
-
+
cl = [cl;optiFunc.lowerBounds(numel(cst{i,4}{1}))];
cu = [cu;optiFunc.upperBounds(numel(cst{i,4}{1}))];
diff --git a/optimization/@matRad_OptimizationProblem/matRad_getJacobianStructure.m b/matRad/optimization/@matRad_OptimizationProblem/matRad_getJacobianStructure.m
similarity index 96%
rename from optimization/@matRad_OptimizationProblem/matRad_getJacobianStructure.m
rename to matRad/optimization/@matRad_OptimizationProblem/matRad_getJacobianStructure.m
index 131621544..28aec1666 100644
--- a/optimization/@matRad_OptimizationProblem/matRad_getJacobianStructure.m
+++ b/matRad/optimization/@matRad_OptimizationProblem/matRad_getJacobianStructure.m
@@ -25,7 +25,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/optimization/@matRad_OptimizationProblem/matRad_objectiveFunction.m b/matRad/optimization/@matRad_OptimizationProblem/matRad_objectiveFunction.m
new file mode 100644
index 000000000..9d0539d00
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblem/matRad_objectiveFunction.m
@@ -0,0 +1,222 @@
+function f = matRad_objectiveFunction(optiProb,w,dij,cst)
+% matRad IPOPT objective function wrapper
+%
+% call
+% f = matRad_objectiveFuncWrapper(optiProb,w,dij,cst)
+%
+% input
+% optiProb: matRad optimization problem
+% w: beamlet/ pencil beam weight vector
+% dij: matRad dose influence struct
+% cst: matRad cst struct
+% scenario: index of dij scenario to consider (optional: default 1)
+%
+% output
+% f: objective function value
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2016 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+
+% get current dose / effect / RBExDose vector
+optiProb.BP.compute(dij,w);
+d = optiProb.BP.GetResult();
+
+% get probabilistic quantities (nearly no overhead if empty)
+[dExp,dOmega] = optiProb.BP.GetResultProb();
+
+% get the used scenarios
+useScen = optiProb.BP.scenarios;
+scenProb = optiProb.BP.scenarioProb;
+useNominalCtScen = optiProb.BP.nominalCtScenarios;
+
+% retrieve matching 4D scenarios
+fullScen = cell(ndims(d),1);
+[fullScen{:}] = ind2sub(size(d),useScen);
+contourScen = fullScen{1};
+
+% initialize f
+f = 0;
+
+% required for COWC opt
+f_COWC = zeros(numel(useScen),1);
+
+% compute objective function for every VOI.
+for i = 1:size(cst,1)
+
+ % Only take OAR or target VOI.
+ if ~isempty(cst{i,4}{1}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )
+
+ % loop over the number of constraints for the current VOI
+ for j = 1:numel(cst{i,6})
+
+ objective = cst{i,6}{j};
+
+ % only perform gradient computations for objectiveectives
+ if isa(objective,'DoseObjectives.matRad_DoseObjective')
+
+ % rescale dose parameters to biological optimization quantity if required
+ objective = optiProb.BP.setBiologicalDosePrescriptions(objective,cst{i,5}.alphaX,cst{i,5}.betaX);
+
+ % retrieve the robustness type
+ robustness = objective.robustness;
+
+ switch robustness
+ case 'none' % if conventional opt: just sum objectives of nominal dose
+ for ixScen = useNominalCtScen
+ d_i = d{ixScen}(cst{i,4}{useScen(ixScen)});
+ f = f + objective.penalty * objective.computeDoseObjectiveFunction(d_i);
+ end
+
+ case 'STOCH' % if prob opt: sum up expectation value of objectives
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+
+ d_i = d{ixScen}(cst{i,4}{ixContour});
+
+ f = f + scenProb(s) * objective.penalty*objective.computeDoseObjectiveFunction(d_i);
+
+ end
+
+ case 'PROB' % if prob opt: sum up expectation value of objectives
+
+ d_i = dExp{1}(cst{i,4}{1});
+
+ f = f + objective.penalty*objective.computeDoseObjectiveFunction(d_i);
+
+ p = objective.penalty/numel(cst{i,4}{1});
+
+ % only one variance term per VOI
+ if j == 1
+ f = f + p * w' * dOmega{i,1};
+ end
+
+ case 'VWWC' % voxel-wise worst case - takes minimum dose in TARGET and maximum in OAR
+ contourIx = unique(contourScen);
+ if ~isscalar(contourIx)
+ % voxels need to be tracked through the 4D CT,
+ % not yet implemented
+ matRad_cfg.dispError('4D VWWC optimization is currently not supported');
+ end
+
+ % prepare min/max dose vector
+ if ~exist('d_tmp','var')
+ d_tmp = [d{useScen}];
+ end
+
+ d_Scen = d_tmp(cst{i,4}{contourIx},:);
+
+ d_max = max(d_Scen,[],2);
+ d_min = min(d_Scen,[],2);
+
+ if isequal(cst{i,3},'OAR')
+ d_i = d_max;
+ elseif isequal(cst{i,3},'TARGET')
+ d_i = d_min;
+ end
+
+ f = f + objective.penalty*objective.computeDoseObjectiveFunction(d_i);
+
+ case 'VWWC_INV' %inverse voxel-wise conformitiy - consider the maximum and minimum dose in the target and optimize the dose conformity
+ contourIx = unique(contourScen);
+ if ~isscalar(contourIx)
+ % voxels need to be tracked through the 4D CT,
+ % not yet implemented
+ matRad_cfg.dispWarning('4D inverted VWWC optimization is currently not supported');
+ end
+
+ % prepare min/max dose vector
+ if ~exist('d_tmp','var')
+ d_tmp = [d{useScen}];
+ end
+
+ d_Scen = d_tmp(cst{i,4}{contourIx},:);
+ d_max = max(d_Scen,[],2);
+ d_min = min(d_Scen,[],2);
+
+ if isequal(cst{i,3},'OAR')
+ d_i = d_min;
+ elseif isequal(cst{i,3},'TARGET')
+ d_i = d_max;
+ end
+
+ f = f + objective.penalty*objective.computeDoseObjectiveFunction(d_i);
+
+ case 'COWC' % composite worst case consideres ovarall the worst objective function value
+
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+
+ d_i = d{ixScen}(cst{i,4}{ixContour});
+
+ f_COWC(s) = f_COWC(s) + objective.penalty*objective.computeDoseObjectiveFunction(d_i);
+ end
+
+ case 'OWC' % objective-wise worst case considers the worst individual objective function value
+
+ f_OWC = zeros(numel(useScen),1);
+
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+
+ d_i = d{ixScen}(cst{i,4}{ixContour});
+ f_OWC(s) = objective.penalty*objective.computeDoseObjectiveFunction(d_i);
+ end
+
+ % compute the maximum objective function value
+ switch optiProb.useMaxApprox
+ case 'logsumexp'
+ fMax = optiProb.logSumExp(f_OWC);
+ case 'pnorm'
+ fMax = optiProb.pNorm(f_OWC,numel(useScen));
+ case 'none'
+ fMax = max(f_OWC);
+ case 'otherwise'
+ matRad_cfg.dispWarning('Unknown maximum approximation desired. Using ''none'' instead.');
+ fMax = max(f_OWC);
+ end
+ f = f + fMax;
+
+ otherwise
+ matRad_cfg.dispError('Robustness setting %s not supported!',objective.robustness);
+
+ end %robustness type
+ end % objective check
+ end %objective loop
+ end %empty check
+end %cst structure loop
+
+
+%Handling the maximum of the composite worst case part
+fMax = max(f_COWC(:));
+if fMax > 0
+ switch optiProb.useMaxApprox
+ case 'logsumexp'
+ fMax = optiProb.logSumExp(f_COWC);
+ case 'pnorm'
+ fMax = optiProb.pNorm(f_COWC,numel(useScen));
+ case 'none'
+ fMax = max(f_COWC);
+ case 'otherwise'
+ matRad_cfg.dispWarning('Unknown maximum approximation desired. Using ''none'' instead.');
+ fMax = max(f_COWC);
+ end
+end
+%Sum up max of composite worst case part
+f = f + fMax;
diff --git a/matRad/optimization/@matRad_OptimizationProblem/matRad_objectiveGradient.m b/matRad/optimization/@matRad_OptimizationProblem/matRad_objectiveGradient.m
new file mode 100644
index 000000000..0e5c9940e
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblem/matRad_objectiveGradient.m
@@ -0,0 +1,316 @@
+function weightGradient = matRad_objectiveGradient(optiProb,w,dij,cst)
+% matRad IPOPT callback: gradient function for inverse planning
+% supporting mean dose objectives, EUD objectives, squared overdosage,
+% squared underdosage, squared deviation and DVH objectives
+%
+% call
+% g = matRad_gradFuncWrapper(optiProb,w,dij,cst)
+%
+% input
+% optiProb: option struct defining the type of optimization
+% w: bixel weight vector
+% dij: dose influence matrix
+% cst: matRad cst struct
+%
+% output
+% g: gradient of objective function
+%
+% References
+% [1] http://www.sciencedirect.com/science/article/pii/S0958394701000577
+% [2] http://www.sciencedirect.com/science/article/pii/S0360301601025858
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2016 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+% get current dose / effect / RBExDose vector
+optiProb.BP.compute(dij,w);
+d = optiProb.BP.GetResult();
+
+% also get probabilistic quantities (nearly no overhead if empty)
+[dExp,dOmega] = optiProb.BP.GetResultProb();
+
+% get the used scenarios
+useScen = optiProb.BP.scenarios;
+scenProb = optiProb.BP.scenarioProb;
+useNominalCtScen = optiProb.BP.nominalCtScenarios;
+
+% retrieve matching 4D scenarios
+fullScen = cell(ndims(d),1);
+[fullScen{:}] = ind2sub(size(d),useScen);
+contourScen = fullScen{1};
+
+doseGradient = cell(size(dij.physicalDose));
+doseGradient(useScen) = {zeros(dij.doseGrid.numOfVoxels,1)};
+
+%For probabilistic optimization
+vOmega = 0;
+
+%For COWC
+f_COWC = zeros(size(dij.physicalDose));
+
+% compute objective function for every VOI.
+for i = 1:size(cst,1)
+
+ % Only take OAR or target VOI.
+ if ~isempty(cst{i,4}{1}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )
+
+ % loop over the number of constraints and objectives for the current VOI
+ for j = 1:numel(cst{i,6})
+
+ %Get current optimization function
+ objective = cst{i,6}{j};
+
+ % only perform gradient computations for objectives
+ if isa(objective,'DoseObjectives.matRad_DoseObjective')
+
+ % retrieve the robustness type
+ robustness = objective.robustness;
+
+ % rescale dose parameters to biological optimization quantity if required
+ objective = optiProb.BP.setBiologicalDosePrescriptions(objective,cst{i,5}.alphaX,cst{i,5}.betaX);
+
+ switch robustness
+ case 'none' % if conventional opt: just sum objectiveectives of nominal dose
+ for s = useNominalCtScen
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+ d_i = d{ixScen}(cst{i,4}{ixContour});
+ %add to dose gradient
+ doseGradient{ixScen}(cst{i,4}{ixContour}) = doseGradient{ixScen}(cst{i,4}{ixContour}) + objective.penalty*objective.computeDoseObjectiveGradient(d_i);
+ end
+ case 'STOCH' % perform stochastic optimization with weighted / random scenarios
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+
+ d_i = d{ixScen}(cst{i,4}{ixContour});
+
+ doseGradient{ixScen}(cst{i,4}{ixContour}) = doseGradient{ixScen}(cst{i,4}{ixContour}) + ...
+ (objective.penalty*objective.computeDoseObjectiveGradient(d_i) * scenProb(s));
+
+ end
+
+ case 'PROB' % use the expectation value and the integral variance influence matrix
+ %First check the speficic cache for probabilistic
+ if ~exist('doseGradientExp','var')
+ doseGradientExp{1} = zeros(dij.doseGrid.numOfVoxels,1);
+ end
+
+ d_i = dExp{1}(cst{i,4}{1});
+
+ doseGradientExp{1}(cst{i,4}{1}) = doseGradientExp{1}(cst{i,4}{1}) + objective.penalty*objective.computeDoseObjectiveGradient(d_i);
+
+ p = objective.penalty/numel(cst{i,4}{1});
+
+ vOmega = vOmega + p * dOmega{i,1};
+
+ case 'VWWC' % voxel-wise worst case - takes minimum dose in TARGET and maximum in OAR
+ contourIx = unique(contourScen);
+ if ~isscalar(contourIx)
+ % voxels need to be tracked through the 4D CT,
+ % not yet implemented
+ matRad_cfg.dispError('4D VWWC optimization is currently not supported');
+ end
+
+ % prepare min/max dose vector for voxel-wise worst case
+ if ~exist('d_tmp','var')
+ d_tmp = [d{useScen}];
+ end
+
+ d_Scen = d_tmp(cst{i,4}{contourIx},:);
+ [d_max,max_ix] = max(d_Scen,[],2);
+ [d_min,min_ix] = min(d_Scen,[],2);
+
+ if isequal(cst{i,3},'OAR')
+ d_i = d_max;
+ elseif isequal(cst{i,3},'TARGET')
+ d_i = d_min;
+ end
+
+ if any(isnan(d_i))
+ matRad_cfg.dispWarning('%d NaN values in gradient.',numel(isnan(d_i)));
+ end
+
+ deltaTmp = objective.penalty*objective.computeDoseObjectiveGradient(d_i);
+
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+
+ if isequal(cst{i,3},'OAR')
+ currWcIx = double(max_ix == s);
+ elseif isequal(cst{i,3},'TARGET')
+ currWcIx = double(min_ix == s);
+ end
+
+ doseGradient{ixScen}(cst{i,4}{ixContour}) = doseGradient{ixScen}(cst{i,4}{ixContour}) + deltaTmp.*currWcIx;
+ end
+
+ case 'VWWC_INV' % voxel-wise worst case - takes minimum dose in TARGET and maximum in OAR
+ contourIx = unique(contourScen);
+ if ~isscalar(contourIx)
+ % voxels need to be tracked through the 4D CT,
+ % not yet implemented
+ matRad_cfg.dispError('4D VWWC optimization is currently not supported');
+ end
+
+ % prepare min/max dose vector for voxel-wise worst case
+ if ~exist('d_tmp','var')
+ d_tmp = [d{useScen}];
+ end
+
+ d_Scen = d_tmp(cst{i,4}{1},:);
+ [d_max,max_ix] = max(d_Scen,[],2);
+ [d_min,min_ix] = min(d_Scen,[],2);
+
+ if isequal(cst{i,3},'OAR')
+ d_i = d_min;
+ elseif isequal(cst{i,3},'TARGET')
+ d_i = d_max;
+ end
+
+ if any(isnan(d_i))
+ matRad_cfg.dispWarning('%d NaN values in gradFuncWrapper.',numel(isnan(d_i)));
+ end
+
+ deltaTmp = objective.penalty*objective.computeDoseObjectiveGradient(d_i);
+
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+
+ if isequal(cst{i,3},'OAR')
+ currWcIx = double(min_ix == s);
+ elseif isequal(cst{i,3},'TARGET')
+ currWcIx = double(max_ix == s);
+ end
+
+ doseGradient{ixScen}(cst{i,4}{ixContour}) = doseGradient{ixScen}(cst{i,4}{ixContour}) + deltaTmp.*currWcIx;
+ end
+
+ case 'COWC' % composite worst case consideres ovarall the worst objective function value
+ %First check the speficic cache for COWC
+ if ~exist('delta_COWC','var')
+ delta_COWC = cell(size(doseGradient));
+ delta_COWC(useScen) = {zeros(dij.doseGrid.numOfVoxels,1)};
+ end
+
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+
+ d_i = d{ixScen}(cst{i,4}{ixContour});
+
+ f_COWC(ixScen) = f_COWC(ixScen) + objective.penalty*objective.computeDoseObjectiveFunction(d_i);
+ delta_COWC{ixScen}(cst{i,4}{ixContour}) = delta_COWC{ixScen}(cst{i,4}{ixContour}) + objective.penalty*objective.computeDoseObjectiveGradient(d_i);
+ end
+
+ case 'OWC' % objective-wise worst case consideres the worst individual objective function value
+ %First check the speficic cache for COWC
+ f_OWC = zeros(size(doseGradient));
+
+ if ~exist('delta_OWC','var')
+ delta_OWC = cell(size(doseGradient));
+ delta_OWC(useScen) = {zeros(dij.doseGrid.numOfVoxels,1)};
+ end
+
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+
+ d_i = d{ixScen}(cst{i,4}{ixContour});
+
+ f_OWC(ixScen) = objective.penalty*objective.computeDoseObjectiveFunction(d_i);
+
+ delta_OWC{ixScen}(cst{i,4}{ixContour}) = objective.penalty*objective.computeDoseObjectiveGradient(d_i);
+
+ end
+
+ switch optiProb.useMaxApprox
+ case 'logsumexp'
+ [~,fGrad] = optiProb.logSumExp(f_OWC);
+ case 'pnorm'
+ [~,fGrad] = optiProb.pNorm(f_OWC,numel(useScen));
+ case 'none'
+ [~,ix] = max(f_OWC(:));
+ fGrad = zeros(size(f_OWC));
+ fGrad(ix) = 1;
+ case 'otherwise'
+ matRad_cfg.dispWarning('Unknown maximum approximation desired. Using ''none'' instead.');
+ [~,ix] = max(f_OWC(:));
+ fGrad = zeros(size(f_OWC));
+ fGrad(ix) = 1;
+ end
+
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ ixContour = contourScen(s);
+ if fGrad(ixScen ) ~= 0
+ doseGradient{ixScen}(cst{i,4}{ixContour}) = doseGradient{ixScen}(cst{i,4}{ixContour}) + fGrad(ixScen)*delta_OWC{ixScen}(cst{i,4}{ixContour});
+ end
+ end
+
+ otherwise
+ matRad_cfg.dispError('Robustness setting %s not supported!',objective.robustness);
+
+ end %robustness type
+ end % objective check
+ end %objective loop
+ end %empty check
+end %cst structure loop
+
+if exist('delta_COWC','var')
+ switch optiProb.useMaxApprox
+ case 'logsumexp'
+ [~,fGrad] = optiProb.logSumExp(f_COWC);
+ case 'pnorm'
+ [~,fGrad] = optiProb.pNorm(f_COWC,numel(useScen));
+ case 'none'
+ [~,ixCurrWC] = max(f_COWC(:));
+ fGrad = zeros(size(f_COWC));
+ fGrad(ixCurrWC) = 1;
+ case 'otherwise'
+ matRad_cfg.dispWarning('Unknown maximum approximation desired. Using ''none'' instead.');
+ [~,ixCurrWC] = max(f_COWC(:));
+ fGrad = zeros(size(f_COWC));
+ fGrad(ixCurrWC) = 1;
+ end
+
+ for s = 1:numel(useScen)
+ ixScen = useScen(s);
+ if fGrad(ixScen) ~= 0
+ doseGradient{ixScen} = doseGradient{ixScen} + fGrad(ixScen)*delta_COWC{ixScen};
+ end
+ end
+end
+
+weightGradient = zeros(dij.totalNumOfBixels,1);
+
+optiProb.BP.computeGradient(dij,doseGradient,w);
+g = optiProb.BP.GetGradient();
+
+for s = 1:numel(useScen)
+ weightGradient = weightGradient + g{useScen(s)};
+end
+
+if vOmega ~= 0
+ optiProb.BP.computeGradientProb(dij,doseGradientExp,vOmega,w);
+ gProb = optiProb.BP.GetGradientProb();
+
+ %Only implemented for first scenario now
+ weightGradient = weightGradient + gProb{1};
+end
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_OptimizationProblemDAO.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_OptimizationProblemDAO.m
similarity index 95%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_OptimizationProblemDAO.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_OptimizationProblemDAO.m
index 516388e54..66f242ca5 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_OptimizationProblemDAO.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_OptimizationProblemDAO.m
@@ -12,7 +12,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m
similarity index 96%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m
index 652c8beed..66a5bec0f 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m
@@ -25,7 +25,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m
similarity index 98%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m
index 1785fe1bc..30bed9248 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m
@@ -23,7 +23,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m
similarity index 98%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m
index 2f883c6b6..ee8fca784 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m
@@ -26,7 +26,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m
similarity index 98%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m
index 1ada1d337..e7741fa73 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m
@@ -26,7 +26,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m
similarity index 94%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m
index 62d59d980..f1be15e78 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m
@@ -23,7 +23,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m
similarity index 97%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m
index 14506f36f..bf8b0309c 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m
@@ -23,7 +23,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveFunction.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveFunction.m
similarity index 95%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_objectiveFunction.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveFunction.m
index 2e03e0884..8d998c4ed 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveFunction.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveFunction.m
@@ -24,7 +24,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m
similarity index 97%
rename from optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m
rename to matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m
index 8c446adca..e7a9da081 100644
--- a/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m
@@ -24,7 +24,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/optimization/matRad_DoseOptimizationFunction.m b/matRad/optimization/matRad_DoseOptimizationFunction.m
similarity index 90%
rename from optimization/matRad_DoseOptimizationFunction.m
rename to matRad/optimization/matRad_DoseOptimizationFunction.m
index d67bda6ce..44ff75f93 100644
--- a/optimization/matRad_DoseOptimizationFunction.m
+++ b/matRad/optimization/matRad_DoseOptimizationFunction.m
@@ -9,7 +9,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -26,6 +26,10 @@
parameters
end
+ properties
+ robustness = 'none'; %Robustness setting -> may be removed from the DoseObjective class in a future release
+ end
+
methods
function obj = matRad_DoseOptimizationFunction(dataStruct)
if nargin > 0 && ~isempty(dataStruct) && isstruct(dataStruct)
@@ -39,6 +43,15 @@
s.className = class(obj);
s.parameters = obj.parameters;
end
+
+ function obj = set.robustness(obj,robustness)
+ matRad_cfg = MatRad_Config.instance();
+ if ischar(robustness) && any(strcmp(robustness,obj.availableRobustness()))
+ obj.robustness = robustness;
+ else
+ matRad_cfg.dispError('Invalid robustness setting!');
+ end
+ end
end
% Helper methods
@@ -52,9 +65,8 @@
function obj = setDoseParameters(obj,doseParams)
% set only the dose related parameters.
ix = cellfun(@(c) isequal('dose',c),obj.parameterTypes);
- obj.parameters(ix) = num2cell(doseParams);
-
- end
+ obj.parameters(ix) = num2cell(doseParams);
+ end
end
methods (Access = private)
@@ -69,7 +81,7 @@
end
end
end
- end
+ end
methods (Static)
@@ -97,6 +109,10 @@
error(['Could not instantiate Optimization Function: ' ME.message]);
end
end
+
+ function rob = availableRobustness()
+ rob = {'none'}; %By default, no robustness is available
+ end
function s = convertOldOptimizationStruct(old_s)
%Converts old version obejctives to current format
diff --git a/tools/matRad_SFUDoptimization.m b/matRad/optimization/matRad_SFUDoptimization.m
similarity index 62%
rename from tools/matRad_SFUDoptimization.m
rename to matRad/optimization/matRad_SFUDoptimization.m
index e9829dbef..bd304c36c 100644
--- a/tools/matRad_SFUDoptimization.m
+++ b/matRad/optimization/matRad_SFUDoptimization.m
@@ -31,33 +31,59 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% adjust cst for single beams
+matRad_cfg = MatRad_Config.instance();
+
sb_cst = cst;
-for i=1:size(sb_cst,1)
- for j = 1:size(sb_cst{i,6},1)
+
+% check & adjust objectives and constraints internally for fractionation
+% & adjust for single beams
+for i = 1:size(cst,1)
+ %Compatibility Layer for old objective format
+ if isstruct(sb_cst{i,6})
+ sb_cst{i,6} = arrayfun(@matRad_DoseOptimizationFunction.convertOldOptimizationStruct,sb_cst{i,6},'UniformOutput',false);
+ end
+ for j = 1:numel(sb_cst{i,6})
+
+ obj = sb_cst{i,6}{j};
+
+ %In case it is a default saved struct, convert to object
+ %Also intrinsically checks that we have a valid optimization
+ %objective or constraint function in the end
+ if ~isa(obj,'matRad_DoseOptimizationFunction')
+ try
+ obj = matRad_DoseOptimizationFunction.createInstanceFromStruct(obj);
+ catch
+ matRad_cfg.dispError('cst{%d,6}{%d} is not a valid Objective/constraint! Remove or Replace and try again!',i,j);
+ end
+ end
+
% biological dose splitting for carbon
- if strcmp(pln.bioOptimization, 'LEMIV_effect') || ...
- strcmp(pln.bioOptimization, 'LEMIV_RBExD')
- ab = sb_cst{i,5}.alphaX / sb_cst{i,5}.betaX;
+ if strcmp(pln.propOpt.bioOptimization, 'LEMIV_effect') || ...
+ strcmp(pln.propOpt.bioOptimization, 'LEMIV_RBExD')
+
% dose per fraction
- fx_dose = sb_cst{i,6}(j).dose / pln.numOfFractions;
+ fx_dose = obj.getDoseParameters()/pln.numOfFractions;
+
% calculate dose per beam per fraction according to [1]
- fx_dose = -0.5*ab +sqrt( 0.25*ab^2 + ...
- fx_dose/pln.numOfBeams *(fx_dose + ab));
+ ab = sb_cst{i,5}.alphaX / sb_cst{i,5}.betaX;
+ fx_dose = -0.5*ab + sqrt( 0.25*ab^2 + fx_dose./pln.propStf.numOfBeams .* (fx_dose + ab));
+
% calculate pseudo total Dose per Beam
- sb_cst{i,6}(j).dose = fx_dose * pln.numOfFractions;
+ obj.setDoseParameters = fx_dose * pln.numOfFractions;
% physical dose splitting
else
- sb_cst{i,6}(j).dose = sb_cst{i,6}(j).dose/pln.numOfBeams;
+ obj = obj.setDoseParameters(obj.getDoseParameters()/pln.propStf.numOfBeams);
end
+
+ sb_cst{i,6}{j} = obj;
end
end
@@ -67,7 +93,9 @@
% initialise total weight vector
wTot = zeros(dij.totalNumOfBixels,1);
- for i = 1:pln.numOfBeams
+ for i = 1:pln.propStf.numOfBeams
+ matRad_cfg.dispInfo('optimizing beam %d...\n',i);
+
% columns in total dij for single beam
sb_col = find(dij.beamNum == i);
% construct dij for single beam
@@ -95,10 +123,10 @@
% adjust pln to one beam only
sb_pln = pln;
- sb_pln.gantryAngles = pln.gantryAngles(i);
- sb_pln.couchAngles = pln.couchAngles(i);
- sb_pln.numOfBeams = 1;
- sb_pln.isoCenter = pln.isoCenter(i,:);
+ sb_pln.propStf.gantryAngles = pln.propStf.gantryAngles(i);
+ sb_pln.propStf.couchAngles = pln.propStf.couchAngles(i);
+ sb_pln.propStf.numOfBeams = 1;
+ sb_pln.propStf.isoCenter = pln.propStf.isoCenter(i,:);
% optimize single beam
sb_resultGUI = matRad_fluenceOptimization(sb_dij,sb_cst,sb_pln);
@@ -108,7 +136,7 @@
end
% calculate dose
- resultGUI = matRad_calcCubes(wTot,dij,cst,1);
+ resultGUI = matRad_calcCubes(wTot,dij);
else
% calculate SFUD without total dij
@@ -116,17 +144,17 @@
% initialise total weight vector
wTot = [];
- for i = 1:pln.numOfBeams
- fprintf(['optimizing beam ' num2str(i) '...\n']);
+ for i = 1:pln.propStf.numOfBeams
+ matRad_cfg.dispInfo('optimizing beam %d...\n',i);
% single beam stf
sb_stf = stf(i);
% adjust pln to one beam only
sb_pln = pln;
- sb_pln.isoCenter = pln.isoCenter(i,:);
- sb_pln.numOfBeams = 1;
- sb_pln.gantryAngles = pln.gantryAngles(i);
- sb_pln.couchAngles = pln.couchAngles(i);
+ sb_pln.propStf.isoCenter = pln.propStf.isoCenter(i,:);
+ sb_pln.propStf.numOfBeams = 1;
+ sb_pln.propStf.gantryAngles = pln.propStf.gantryAngles(i);
+ sb_pln.propStf.couchAngles = pln.propStf.couchAngles(i);
% calculate single beam dij
sb_dij = matRad_calcParticleDose(ct,sb_stf,sb_pln,sb_cst,false);
@@ -139,9 +167,9 @@
end
- fprintf('Calculate total dose...\n');
+ matRad_cfg.dispInfo('Calculate total dose...\n');
% calculate dose
- resultGUI = matRad_calcDoseDirect(ct,stf,pln,cst,wTot);
+ resultGUI = matRad_calcDoseForward(ct,cst,stf,pln,wTot);
resultGUI.w = wTot;
end
diff --git a/optimization/matRad_calcInversDVH.m b/matRad/optimization/matRad_calcInversDVH.m
similarity index 92%
rename from optimization/matRad_calcInversDVH.m
rename to matRad/optimization/matRad_calcInversDVH.m
index c145b7d0e..8e597d89f 100644
--- a/optimization/matRad_calcInversDVH.m
+++ b/matRad/optimization/matRad_calcInversDVH.m
@@ -20,7 +20,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/optimization/matRad_clearUnusedVoxelsFromDij.m b/matRad/optimization/matRad_clearUnusedVoxelsFromDij.m
new file mode 100644
index 000000000..c9cd0ecf5
--- /dev/null
+++ b/matRad/optimization/matRad_clearUnusedVoxelsFromDij.m
@@ -0,0 +1,85 @@
+function [dij, mask] = matRad_clearUnusedVoxelsFromDij(cstOnDoseGrid, dij, scenarios)
+% matRad function to set the voxels in dij that are not used for
+% optimization.
+%
+% call
+% [dij] = matRad_clearUnusedVoxelsFromDij(cst, dij)
+% [dij] = matRad_clearUnusedVoxelsFromDij(cst, dij, scenarios)
+% [dij, mask] = matRad_clearUnusedVoxelsFromDij(cst, dij)
+% [dij, mask] = matRad_clearUnusedVoxelsFromDij(cst, dij, scenarios)
+%
+% input
+% cstInDoseGrid: cst (on dose grid)
+% dij: dij struct
+% scenarios (optional): explicitly define the scenario indexes that need to be cleared
+%
+%
+% output
+%
+% dij: cleared dij struct
+% mask (optional): cell array containig the mask that has been applied to every ct scenario
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2023 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('Clearing unused voxels in dij... ');
+
+ % If no scenarios are specified, clear them all
+ if ~exist('scenarios', 'var')
+ scenarios = find(~cellfun(@isempty, dij.physicalDose));
+ end
+
+ %convert selected scenario indexes to subscripts
+ scenIndexes = cell(ndims(dij.physicalDose),1);
+ [scenIndexes{:}] = ind2sub(size(dij.physicalDose),scenarios);
+
+
+ % get voxel mask with voxels on those structures that have objectives/constraints
+ includeMask = matRad_selectVoxelsFromCst(cstOnDoseGrid,dij.doseGrid,'objectivesOnly');
+
+ ctScenarios = unique(scenIndexes{1});
+
+ for ctIdx=ctScenarios'
+
+ idx = find(scenIndexes{1}==ctIdx);
+
+ %get linear indexes of the scenarios in the current ct phase
+ scenInPhase = sub2ind(size(dij.physicalDose), scenIndexes{1}(idx), scenIndexes{2}(idx), scenIndexes{3}(idx));
+
+ %clear the dij voxels
+ for scenIdx=scenInPhase'
+
+ dij.physicalDose{scenIdx} = dij.physicalDose{scenIdx}.*includeMask{ctIdx}(:);
+ end
+
+
+ if isfield(dij, 'mAlphaDose')
+ dij.mAlphaDose{scenIdx} = dij.mAlphaDose{scenIdx}.*includeMask{ctIdx};
+ end
+
+ if isfield(dij, 'mSqrtBetaDose')
+ dij.mSqrtBetaDose{scenIdx} = dij.mSqrtBetaDose{scenIdx}.*includeMask{ctIdx};
+ end
+
+ if isfield(dij,'mLETDose')
+ dij.mSqrtBetaDose{scenIdx} = dij.mLETDose{scenIdx}.*includeMask{ctIdx};
+ end
+ end
+
+ if nargout>1
+ mask = includeMask;
+ end
+ matRad_cfg.dispInfo('done.\n');
+end
\ No newline at end of file
diff --git a/optimization/matRad_collapseDij.m b/matRad/optimization/matRad_collapseDij.m
similarity index 94%
rename from optimization/matRad_collapseDij.m
rename to matRad/optimization/matRad_collapseDij.m
index 637eb5c26..03c66895e 100644
--- a/optimization/matRad_collapseDij.m
+++ b/matRad/optimization/matRad_collapseDij.m
@@ -21,7 +21,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/optimization/matRad_getObjectivesAndConstraints.m b/matRad/optimization/matRad_getObjectivesAndConstraints.m
new file mode 100644
index 000000000..a8d07aed3
--- /dev/null
+++ b/matRad/optimization/matRad_getObjectivesAndConstraints.m
@@ -0,0 +1,96 @@
+function classNames = matRad_getObjectivesAndConstraints()
+% matRad steering information generation
+%
+% call
+% classNames = matRad_getObjectivesAndConstraints()
+%
+%
+% output
+% classNames: contains class names (row 1) and display descriptions
+% (row 2) of all available objectives
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+env = matRad_getEnvironment();
+
+if strcmp(env,'MATLAB') %use the package methodology for Matlab (stable)
+ mpkgObjectives = meta.package.fromName('DoseObjectives');
+ mpkgConstraints = meta.package.fromName('DoseConstraints');
+ classList = [mpkgObjectives.ClassList; mpkgConstraints.ClassList];
+
+ classList = classList(not([classList.Abstract]));
+
+ %Now get the "internal" name from the properties
+ classNames = cell(2,numel(classList));
+ for clIx = 1:numel(classList)
+ cl = classList(clIx);
+ pList = cl.PropertyList; %Get List of all properties
+ pNameIx = arrayfun(@(p) strcmp(p.Name,'name'),pList); %get index of the "name" property
+ p = pList(pNameIx); %select name property
+ pName = p.DefaultValue; % get value / name
+ classNames(:,clIx) = {cl.Name; pName}; %Store class name and display name
+ end
+else
+ currDir = fileparts(mfilename('fullpath'));
+ objectiveDir = [currDir filesep '+DoseObjectives'];
+ constraintDir = [currDir filesep '+DoseConstraints'];
+
+ objFiles = dir(objectiveDir);
+ for i=1:numel(objFiles)
+ objFiles(i).pkgName = 'DoseObjectives';
+ end
+ constrFiles = dir(constraintDir);
+ for i=1:numel(objFiles)
+ constrFiles(i).pkgName = 'DoseConstraints';
+ end
+
+ allFiles = [objFiles; constrFiles];
+
+ [defNames,dispNames] = arrayfun(@testForClass,allFiles,'UniformOutput',false);
+
+ classNames(1,:) = defNames;
+ classNames(2,:) = dispNames;
+
+ selectIx = cellfun(@isempty,classNames);
+ classNames = classNames(:,~selectIx(1,:));
+end
+end
+
+function [defName,dispName] = testForClass(potentialClassFile)
+
+
+ try
+% fPath = potentialClassFile.folder;
+ [~,fName,~] = fileparts(potentialClassFile.name);
+ fType = potentialClassFile.pkgName;
+
+ clTmp = meta.class.fromName([fType '.' fName]);
+ defName = clTmp.Name;
+
+ pList = clTmp.PropertyList;
+
+ pNameIx = cellfun(@(p) strcmp(p.Name,'name'),pList);
+ p = pList(pNameIx);
+ dispName = p{1}.DefaultValue ;
+
+
+ catch
+ defName = '';
+ dispName = '';
+ end
+end
+
diff --git a/optimization/optimizer/matRad_Optimizer.m b/matRad/optimization/optimizer/matRad_Optimizer.m
similarity index 71%
rename from optimization/optimizer/matRad_Optimizer.m
rename to matRad/optimization/optimizer/matRad_Optimizer.m
index ced614c2d..a9520321b 100644
--- a/optimization/optimizer/matRad_Optimizer.m
+++ b/matRad/optimization/optimizer/matRad_Optimizer.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -21,6 +21,9 @@
properties (Abstract)
options %options struct
+ end
+
+ properties (Abstract,SetAccess = protected)
wResult
resultInfo
end
@@ -30,17 +33,17 @@
%as Octave is able to do this, they should be made abstract again
methods %(Abstract)
function obj = optimize(obj,w0,optiProb,dij,cst)
- error('Function needs to be implemented!');
+ throw(MException('MATLAB:class:AbstractMember','Abstract function optimize needs to be implemented!'));
end
function [msg,statusflag] = GetStatus(obj)
- error('Function needs to be implemented!');
+ throw(MException('MATLAB:class:AbstractMember','Abstract function GetStatus needs to be implemented!'));
end
end
methods (Static)
function available = IsAvailable(obj)
- error('Function needs to be implemented!');
+ throw(MException('MATLAB:class:AbstractMember','Abstract function IsAvailable needs to be implemented!'));
end
end
end
diff --git a/optimization/optimizer/matRad_OptimizerFmincon.m b/matRad/optimization/optimizer/matRad_OptimizerFmincon.m
similarity index 76%
rename from optimization/optimizer/matRad_OptimizerFmincon.m
rename to matRad/optimization/optimizer/matRad_OptimizerFmincon.m
index ad13738a9..a8fda61cb 100644
--- a/optimization/optimizer/matRad_OptimizerFmincon.m
+++ b/matRad/optimization/optimizer/matRad_OptimizerFmincon.m
@@ -11,7 +11,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -21,6 +21,9 @@
properties
options %the optimoptions for fmincon
+ end
+
+ properties (SetAccess = protected)
wResult %last optimization result
resultInfo %info struct about last results
end
@@ -30,26 +33,47 @@
%matRad_OptimizerFmincon
% Construct an instance of the fmincon optimizer from the Optimization Toolbox
matRad_cfg = MatRad_Config.instance();
+
+ if ~matRad_OptimizerFmincon.IsAvailable()
+ matRad_cfg.dipsError('matRad_OptimizerFmincon can not be constructed as fmincon is not available!');
+ end
obj.wResult = [];
obj.resultInfo = [];
+ optDiag = 'off';
+
+ if matRad_cfg.logLevel >= 4
+ optDisplay = 'iter-detailed';
+ optDiag = 'on';
+ elseif matRad_cfg.logLevel == 3
+ optDisplay = 'iter';
+ optDiag = 'on';
+ elseif matRad_cfg.logLevel == 2
+ optDisplay = 'notify-detailed';
+ else
+ optDisplay = 'off';
+ end
+
%createDefaultOptimizerOptions Constructs a set of default
%options for the optimizer to use
obj.options = optimoptions('fmincon',...
'Algorithm','interior-point',...
- 'Display','iter-detailed',...
+ 'Display',optDisplay,...
'SpecifyObjectiveGradient',true,...
'SpecifyConstraintGradient',true,...
'AlwaysHonorConstraints', 'bounds',...
- 'MaxIterations',matRad_cfg.propOpt.defaultMaxIter,...
+ 'MaxIterations',matRad_cfg.defaults.propOpt.maxIter,...
'MaxFunctionEvaluations',3000,...
'CheckGradients',false,...
- 'HessianApproximation',{'lbfgs',6},...
+ 'HessianApproximation',{'lbfgs',50},...
'UseParallel',true,...
- 'Diagnostics','on',...
- 'ScaleProblem',true,...
- 'PlotFcn',{@optimplotfval,@optimplotx,@optimplotfunccount,@optimplotconstrviolation,@optimplotstepsize,@optimplotfirstorderopt});
+ 'Diagnostics',optDiag,...
+ 'ScaleProblem',true);
+
+ if ~matRad_cfg.disableGUI
+ obj.options.PlotFcn = {@optimplotfval,@optimplotx,@optimplotfunccount,@optimplotconstrviolation,@optimplotstepsize,@optimplotfirstorderopt};
+ end
end
function obj = optimize(obj,w0,optiProb,dij,cst)
@@ -62,6 +86,13 @@
% Informing user to press q to terminate optimization
%fprintf('\nOptimzation initiating...\n');
%fprintf('Press q to terminate the optimization...\n');
+
+ matRad_cfg = MatRad_Config.instance();
+ if matRad_cfg.isMatlab && str2double(matRad_cfg.envVersion) <= 9.13 && strcmp(obj.options.Diagnostics,'on')
+ matRad_cfg.dispWarning('Diagnostics in fmincon will be turned off due to a bug when using lbfgs with specified number of histories!');
+ obj.options.Diagnostics = 'off';
+ end
+
% Run fmincon.
[obj.wResult,fVal,exitflag,info] = fmincon(@(x) obj.fmincon_objAndGradWrapper(x,optiProb,dij,cst),...
@@ -141,7 +172,7 @@
methods (Static)
function available = IsAvailable()
%'fmincon' is a p-code file in the optimization toolbox
- available = exist('fmincon') == 6;
+ available = exist('fmincon') ~= 0;
end
end
end
\ No newline at end of file
diff --git a/optimization/optimizer/matRad_OptimizerIPOPT.m b/matRad/optimization/optimizer/matRad_OptimizerIPOPT.m
similarity index 82%
rename from optimization/optimizer/matRad_OptimizerIPOPT.m
rename to matRad/optimization/optimizer/matRad_OptimizerIPOPT.m
index f4a00b483..b37c0a3af 100644
--- a/optimization/optimizer/matRad_OptimizerIPOPT.m
+++ b/matRad/optimization/optimizer/matRad_OptimizerIPOPT.m
@@ -1,32 +1,35 @@
classdef matRad_OptimizerIPOPT < matRad_Optimizer
% matRad_OptimizerIPOPT implements the interface for ipopt
-%
+%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% References
% -
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+
properties
options
- wResult
- resultInfo
env
%Visualization
showPlot = true;
end
-
+
+ properties (SetAccess = protected)
+ wResult
+ resultInfo
+ end
+
properties (Access = private)
allObjectiveFunctionValues
axesHandle
@@ -34,21 +37,21 @@
abortRequested
plotFailed
end
-
+
methods
function obj = matRad_OptimizerIPOPT
%matRad_OptimizerIPOPT
% Construct an instance of the IPOPT optimizer (mex
% interface)
-
+
matRad_cfg = MatRad_Config.instance();
-
+
obj.wResult = [];
obj.resultInfo = [];
obj.axesHandle = [];
obj.allObjectiveFunctionValues = [];
obj.abortRequested = false;
-
+
%Set Default Options
if matRad_cfg.logLevel <= 1
lvl = 0;
@@ -56,56 +59,56 @@
lvl = 2;
elseif matRad_cfg.logLevel <= 3
lvl = 5;
- else
+ else
%There seems to be a problem with higher log levels in
%IPOPT!
lvl = 5;
end
-
+
obj.options.print_level = lvl;
obj.options.print_user_options = 'no';
obj.options.print_options_documentation = 'no';
-
+
% Termination (C.2)
- obj.options.tol = 1e-8; % (Opt1)
- obj.options.dual_inf_tol = 1; % (Opt2)
+ obj.options.tol = 1e-10; % (Opt1)
+ obj.options.dual_inf_tol = 1e-4; % (Opt2)
obj.options.constr_viol_tol = 1e-4; % (Opt3)
obj.options.compl_inf_tol = 1e-4; % (Opt4), Optimal Solution Found if (Opt1),...,(Opt4) fullfiled
-
- obj.options.acceptable_iter = 3; % (Acc1)
+
+ obj.options.acceptable_iter = 5; % (Acc1)
obj.options.acceptable_tol = 1e10; % (Acc2)
- obj.options.acceptable_constr_viol_tol = 1e10; % (Acc3)
+ obj.options.acceptable_constr_viol_tol = 1e-2; % (Acc3)
obj.options.acceptable_dual_inf_tol = 1e10; % (Acc4)
obj.options.acceptable_compl_inf_tol = 1e10; % (Acc5)
- obj.options.acceptable_obj_change_tol = 1e-3; % (Acc6), Solved To Acceptable Level if (Acc1),...,(Acc6) fullfiled
-
- obj.options.max_iter = matRad_cfg.propOpt.defaultMaxIter;
- obj.options.max_cpu_time = 3000;
-
+ obj.options.acceptable_obj_change_tol = 1e-4; % (Acc6), Solved To Acceptable Level if (Acc1),...,(Acc6) fullfiled
+
+ obj.options.max_iter = matRad_cfg.defaults.propOpt.maxIter;
+ obj.options.max_cpu_time = 7200;
+
% Barrier Parameter (C.6)
obj.options.mu_strategy = 'adaptive';
-
+
% Line Sarch (C.8)
%obj.options.accept_every_trial_step = 'yes';
%obj.options.line_search_method = 'cg-penalty';
-
+
% Restoration Phase (C.10)
%obj.options.soft_resto_pderror_reduction_factor = 100;
%obj.options.required_infeasibility_reduction = 0.9999;
-
+
% Quasi-Newton (C.13)
obj.options.hessian_approximation = 'limited-memory';
- obj.options.limited_memory_max_history = 6;
+ obj.options.limited_memory_max_history = 50;
obj.options.limited_memory_initialization = 'scalar2';
-
+
% determine once if Matlab or Octave
obj.env = matRad_getEnvironment();
-
+
% for derivate checking
% obj.options.derivative_test = 'first-order'; % none / first-order / second-order / only-second-order
% obj.options.derivative_test_perturbation = 1e-6; % default 1e-8
- % obj.options.derivative_test_tol = 1e-6;
-
+ % obj.options.derivative_test_tol = 1e-6;
+
if ~matRad_checkMexFileExists('ipopt')
matRad_cfg.dispError('IPOPT mex interface not available for %s!',obj.env);
end
@@ -115,40 +118,40 @@
end
end
-
+
function obj = optimize(obj,w0,optiProb,dij,cst)
matRad_cfg = MatRad_Config.instance();
-
- % set optimization options
-
+
+ % set optimization options
+
%Set up ipopt structure
ipoptStruct = struct;
-
+
%optimizer options
ipoptStruct.ipopt = obj.options;
-
+
%variable bounds
ipoptStruct.lb = optiProb.lowerBounds(w0);
ipoptStruct.ub = optiProb.upperBounds(w0);
-
+
%constraint bounds;
[ipoptStruct.cl,ipoptStruct.cu] = optiProb.matRad_getConstraintBounds(cst);
-
+
% set callback functions.
-
- funcs.objective = @(x) optiProb.matRad_objectiveFunction(x,dij,cst);
- funcs.constraints = @(x) optiProb.matRad_constraintFunctions(x,dij,cst);
- funcs.gradient = @(x) optiProb.matRad_objectiveGradient(x,dij,cst);
- funcs.jacobian = @(x) optiProb.matRad_constraintJacobian(x,dij,cst);
+
+ funcs.objective = @(x) double(optiProb.matRad_objectiveFunction(x,dij,cst));
+ funcs.constraints = @(x) double(optiProb.matRad_constraintFunctions(x,dij,cst));
+ funcs.gradient = @(x) double(optiProb.matRad_objectiveGradient(x,dij,cst));
+ funcs.jacobian = @(x) double(optiProb.matRad_constraintJacobian(x,dij,cst));
funcs.jacobianstructure = @( ) optiProb.matRad_getJacobianStructure(w0,dij,cst);
funcs.iterfunc = @(iter,objective,paramter) obj.iterFunc(iter,objective,paramter,ipoptStruct.ipopt.max_iter);
-
+
% Informing user to press q to terminate optimization
matRad_cfg.dispInfo('\nOptimzation initiating...\n');
-
+
% set Callback
qCallbackSet = false;
- if ~isdeployed % only if _not_ running as standalone
+ if ~isdeployed % only if _not_ running as standalone
switch obj.env
case 'MATLAB'
try
@@ -157,39 +160,39 @@
cw = mde.getClient('Command Window');
xCmdWndView = cw.getComponent(0).getViewport.getComponent(0);
h_cw = handle(xCmdWndView,'CallbackProperties');
-
+
% set Key Pressed Callback of Matlab command window
set(h_cw, 'KeyPressedCallback', @(h,event) obj.abortCallbackKey(h,event));
- fprintf('Press q to terminate the optimization...\n');
+ matRad_cfg.dispInfo('Press q to terminate the optimization...\n');
qCallbackSet = true;
catch
matRad_cfg.dispInfo('Manual termination with q not possible due to failing callback setup.\n');
end
- end
+ end
end
-
+
%ipoptStruct.options = obj.options;
obj.abortRequested = false;
obj.plotFailed = false;
-
+
% Run IPOPT.
try
- [obj.wResult, obj.resultInfo] = ipopt(w0,funcs,ipoptStruct);
+ [obj.wResult, obj.resultInfo] = ipopt(double(w0),funcs,ipoptStruct);
catch ME
errorString = [ME.message '\nThis error was thrown by the MEX-interface of IPOPT.\nMex interfaces can raise compatability issues which may be resolved by compiling them by hand directly on your particular system.'];
matRad_cfg.dispError(errorString);
end
-
+
% unset Key Pressed Callback of Matlab command window
if qCallbackSet
set(h_cw, 'KeyPressedCallback',' ');
end
-
+
obj.abortRequested = false;
% Empty the array of stored function values
obj.allObjectiveFunctionValues = [];
end
-
+
function [statusmsg,statusflag] = GetStatus(obj)
try
switch obj.resultInfo.status
@@ -232,7 +235,7 @@
otherwise
statusmsg = 'IPOPT returned unknown status';
end
-
+
if obj.resultInfo.status == 0
statusflag = 0;
elseif obj.resultInfo.status > 0
@@ -245,39 +248,41 @@
statusflag = -1;
end
end
-
+
function flag = iterFunc(obj,iter,objective,~,~)
-
+
obj.allObjectiveFunctionValues(iter + 1) = objective;
%We don't want the optimization to crash because of drawing
%errors
if obj.showPlot && ~obj.plotFailed
- try
+ try
obj.plotFunction();
catch ME
matRad_cfg = MatRad_Config.instance();
%Put a warning at iteration 1 that plotting failed
matRad_cfg.dispWarning('Objective Function plotting failed and thus disabled. Message:\n%s',ME.message);
obj.plotFailed = true;
- end
+ end
end
flag = ~obj.abortRequested;
end
-
+
function plotFunction(obj)
% plot objective function output
y = obj.allObjectiveFunctionValues;
x = 1:numel(y);
-
- if isempty(obj.axesHandle) || ~isgraphics(obj.axesHandle,'axes')
+
+ if isempty(obj.axesHandle)
%Create new Fiure and store axes handle
- hFig = figure('Name','Progress of IPOPT Optimization','NumberTitle','off','Color',[.5 .5 .5]);
- hAx = axes(hFig);
+ matRad_cfg = MatRad_Config.instance();
+ hFig = figure('Name','Progress of IPOPT Optimization','NumberTitle','off','Color',matRad_cfg.gui.backgroundColor);
+ hAx = axes(hFig,'Color',matRad_cfg.gui.elementColor,'XColor',matRad_cfg.gui.textColor,'YColor',matRad_cfg.gui.textColor,'GridColor',matRad_cfg.gui.textColor,'MinorGridColor',matRad_cfg.gui.backgroundColor);
+
hold(hAx,'on');
grid(hAx,'on');
grid(hAx,'minor');
set(hAx,'YScale','log');
-
+
%Add a Stop button with callback to change abort flag
c = uicontrol;
cPos = get(c,'Position');
@@ -285,55 +290,59 @@ function plotFunction(obj)
cPos(2) = 5;
set(c, 'String','Stop',...
'Position',cPos,...
- 'Callback',@(~,~) abortCallbackButton(obj));
-
+ 'Callback',@(~,~) abortCallbackButton(obj));
+
%Set up the axes scaling & labels
defaultFontSize = 14;
set(hAx,'YScale','log');
- title(hAx,'Progress of Optimization','LineWidth',defaultFontSize);
+ title(hAx,'Progress of Optimization','LineWidth',defaultFontSize,'Color',matRad_cfg.gui.highlightColor);
xlabel(hAx,'# iterations','Fontsize',defaultFontSize),ylabel(hAx,'objective function value','Fontsize',defaultFontSize);
-
+
%Create plot handle and link to data for faster update
- hPlot = plot(hAx,x,y,'xb','LineWidth',1.5,'XDataSource','x','YDataSource','y');
+ hPlot = plot(hAx,x,y,'x','MarkerEdgeColor',matRad_cfg.gui.highlightColor,'MarkerFaceColor',matRad_cfg.gui.elementColor,'LineWidth',1.5,'XDataSource','x','YDataSource','y');
obj.plotHandle = hPlot;
obj.axesHandle = hAx;
-
+
else %Figure already exists, retreive from axes handle
hFig = get(obj.axesHandle,'Parent');
hAx = obj.axesHandle;
hPlot = obj.plotHandle;
end
-
- % draw updated axes by refreshing data of the plot handle (which is linked to y and y)
+
+ % draw updated axes by refreshing data of the plot handle (which is linked to y and y)
% in the caller workspace. Octave needs and works on figure handles, which
% is substantially (factor 10) slower, thus we check explicitly
switch obj.env
case 'OCTAVE'
- refreshdata(hFig,'caller');
+ if ishghandle(hFig)
+ refreshdata(hFig,'caller');
+ end
otherwise
refreshdata(hPlot,'caller');
end
drawnow;
-
+
% ensure to bring optimization window to front
- figure(hFig);
+ if ishghandle(hFig)
+ figure(hFig);
+ end
end
-
+
function abortCallbackKey(obj,~,KeyEvent)
% check if user pressed q
if get(KeyEvent,'keyCode') == 81
obj.abortRequested = true;
end
end
-
+
function abortCallbackButton(obj,~,~,~)
obj.abortRequested = true;
- end
+ end
end
-
+
methods (Static)
function available = IsAvailable()
- available = matRad_checkMexFileExists('ipopt');
+ available = matRad_checkMexFileExists('ipopt');
end
end
end
diff --git a/matRad/optimization/optimizer/matRad_OptimizerSimulannealbnd.m b/matRad/optimization/optimizer/matRad_OptimizerSimulannealbnd.m
new file mode 100644
index 000000000..a8248a31a
--- /dev/null
+++ b/matRad/optimization/optimizer/matRad_OptimizerSimulannealbnd.m
@@ -0,0 +1,118 @@
+classdef matRad_OptimizerSimulannealbnd < matRad_Optimizer
+ % matRad_OptimizerSimulannealbnd implements the interface for the Simulated Annealing optimizer
+ % of the MATLAB Global Optimization toolbox
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2024 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ options % the optimoptions for simulannealbnd
+ end
+
+ properties (SetAccess = protected)
+ wResult % last optimization result
+ resultInfo % info struct about last results
+ end
+
+ methods
+ function obj = matRad_OptimizerSimulannealbnd
+ % matRad_OptimizerSimulannealbnd Constructor
+ matRad_cfg = MatRad_Config.instance();
+
+ if ~matRad_OptimizerSimulannealbnd.IsAvailable()
+ matRad_cfg.dipsError('matRad_OptimizerSimulannealbnd cannot be constructed as simulannealbnd is not available!');
+ end
+
+ obj.wResult = [];
+ obj.resultInfo = [];
+
+ if matRad_cfg.logLevel >= 4
+ optDisplay = 'diagnose';
+ elseif matRad_cfg.logLevel == 3
+ optDisplay = 'iter';
+ elseif matRad_cfg.logLevel == 2
+ optDisplay = 'final';
+ else
+ optDisplay = 'off';
+ end
+
+ if matRad_cfg.disableGUI
+ pltFcns = {[]};
+ else
+ pltFcns = {@saplotbestf,@saplotbestx, @saplotf,@saplotx,@saplotstopping,@saplottemperature};
+ end
+
+ % Create default optimizer options
+ obj.options = optimoptions('simulannealbnd', ...
+ 'InitialTemperature', 0.7, ...
+ 'TemperatureFcn',@temperatureboltz, ...
+ 'Display', optDisplay, ...
+ 'MaxIterations', matRad_cfg.defaults.propOpt.maxIter, ...
+ 'MaxFunctionEvaluations', 120000, ...
+ 'PlotFcn',pltFcns);
+ end
+
+ function obj = optimize(obj, w0, optiProb, dij, cst)
+ matRad_cfg = MatRad_Config.instance();
+ % optimize Carries out the optimization
+
+ % Obtain lower and upper variable bounds
+ lb = optiProb.lowerBounds(w0);
+ ub = optiProb.upperBounds(w0);
+
+ % Informing user to press q to terminate optimization
+ matRad_cfg.dispInfo('Optimization initiating...\n');
+ matRad_cfg.dispInfo('Press q to terminate the optimization...\n');
+
+ if matRad_cfg.isMatlab && str2double(matRad_cfg.envVersion) <= 9.13 && strcmp(obj.options.Diagnostics, 'on')
+ matRad_cfg.dispWarning('Diagnostics in simulannealbnd will be turned off due to a bug when using lbfgs with specified number of histories!');
+ obj.options.Diagnostics = 'off';
+ end
+
+ % Define the objective function
+ objectiveFunction = @(x) optiProb.matRad_objectiveFunction(x, dij, cst);
+
+ % Run simulated annealing optimization
+ [obj.wResult, fVal, exitflag, info] = simulannealbnd(objectiveFunction, w0, lb, ub, obj.options);
+
+ obj.resultInfo = info;
+ obj.resultInfo.fVal = fVal;
+ obj.resultInfo.exitflag = exitflag;
+ end
+
+ function [statusmsg, statusflag] = GetStatus(obj)
+ try
+ statusmsg = obj.resultInfo.message;
+ if obj.resultInfo.exitflag == 0
+ statusflag = 0;
+ elseif obj.resultInfo.exitflag > 0
+ statusflag = 1;
+ else
+ statusflag = -1;
+ end
+ catch
+ statusmsg = 'No Last Optimizer Status Available!';
+ statusflag = -1;
+ end
+ end
+ end
+
+ methods (Static)
+ function available = IsAvailable()
+ available = exist('simulannealbnd', 'file') ~= 0;
+ end
+ end
+end
diff --git a/matRad/optimization/projections/matRad_BEDProjection.m b/matRad/optimization/projections/matRad_BEDProjection.m
new file mode 100644
index 000000000..e6cc3b604
--- /dev/null
+++ b/matRad/optimization/projections/matRad_BEDProjection.m
@@ -0,0 +1,50 @@
+classdef matRad_BEDProjection < matRad_EffectProjection
+% matRad_BEDProjection class for BED-based optimization
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ methods
+ function obj = matRad_BEDProjection()
+ end
+ end
+
+ methods
+ function BED = computeSingleScenario(obj,dij,scen,w)
+ %Get corresponding ct scenario
+ [ctScen,~] = ind2sub(size(dij.physicalDose),scen);
+
+ effect = computeSingleScenario@matRad_EffectProjection(obj,dij,scen,w);
+
+ BED = zeros(dij.doseGrid.numOfVoxels,1);
+ BED(dij.ixDose{ctScen}) = effect(dij.ixDose{ctScen})./ dij.ax{ctScen}(dij.ixDose{ctScen});
+ % photon equivalent BED = n * effect / alphax
+ end
+
+ function wGrad = projectSingleScenarioGradient(obj,dij,doseGrad,scen,w)
+ [ctScen,~] = ind2sub(size(dij.physicalDose),scen);
+
+ doseGradtmp{scen} = zeros(size(doseGrad{scen}));
+ doseGradtmp{scen}(dij.ixDose{ctScen}) = doseGrad{scen}(dij.ixDose{ctScen})./dij.ax{scen}(dij.ixDose{ctScen});
+ wGradEffect = projectSingleScenarioGradient@matRad_EffectProjection(obj,dij,doseGradtmp,scen,w);
+ wGrad = wGradEffect;
+ end
+ end
+ methods (Static)
+ function optiFunc = setBiologicalDosePrescriptions(optiFunc,alphaX,betaX)
+ doses = optiFunc.getDoseParameters();
+ BED = doses*(1 + doses/(alphaX/betaX));
+ optiFunc = optiFunc.setDoseParameters(BED);
+ end
+ end
+end
diff --git a/matRad/optimization/projections/matRad_BackProjection.m b/matRad/optimization/projections/matRad_BackProjection.m
new file mode 100644
index 000000000..f46140e51
--- /dev/null
+++ b/matRad/optimization/projections/matRad_BackProjection.m
@@ -0,0 +1,144 @@
+classdef matRad_BackProjection < handle
+% matRad_BackProjection superclass for all backprojection algorithms
+% used within matRad optimzation processes
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (SetAccess = protected)
+ wCache
+ wGradCache %different cache for optimal performance (if multiple evaluations of objective but not gradient are required)
+ wGradCacheProb
+ d
+ wGrad
+ wGradProb
+ dExp
+ dOmegaV
+ end
+
+ properties
+ dij %reference to matRad dij struct (to enable local changes)
+ scenarios = 1 %Scenario indices to evaluate (used for 4D & robust/stochastic optimization)
+ scenarioProb = 1 %Probability associated with scenario (for stochastic optimization)
+ nominalCtScenarios = 1; %nominal ct scenario (no shift, no range error) indices to evaluate (used for 4D & robust/stochastic optimization, when at least one cst structure does not have robustness)
+ end
+
+
+ methods
+ function obj = matRad_BackProjection()
+ obj.wCache = [];
+ obj.wGradCache = [];
+ obj.wGradCacheProb = [];
+ obj.d = [];
+ obj.dExp = [];
+ obj.dOmegaV = [];
+ obj.wGrad = [];
+ obj.wGradProb = [];
+ end
+
+ function obj = compute(obj,dij,w)
+ if ~isequal(obj.wCache,w)
+ obj.d = obj.computeResult(dij,w);
+ [obj.dExp,obj.dOmegaV] = obj.computeResultProb(dij,w);
+ obj.wCache = w;
+ end
+ end
+
+ function obj = computeGradient(obj,dij,doseGrad,w)
+ if ~isequal(obj.wGradCache,w)
+ obj.wGrad = obj.projectGradient(dij,doseGrad,w);
+ obj.wGradCache = w;
+ end
+ end
+
+ function obj = computeGradientProb(obj,dij,doseGrad,vOmegaGrad,w)
+ if ~isequal(obj.wGradCacheProb,w)
+ obj.wGradProb = obj.projectGradientProb(dij,doseGrad,vOmegaGrad,w);
+ obj.wGradCacheProb = w;
+ end
+ end
+
+ function d = GetResult(obj)
+ d = obj.d;
+ end
+
+ function [dExp,dOmegaV] = GetResultProb(obj)
+ dExp = obj.dExp;
+ dOmegaV = obj.dOmegaV;
+ end
+
+ function wGrad = GetGradient(obj)
+ wGrad = obj.wGrad;
+ end
+
+ function wGrad = GetGradientProb(obj)
+ wGrad = obj.wGradProb;
+ end
+
+ function d = computeResult(obj,dij,w)
+ d = cell(size(dij.physicalDose));
+ d(obj.scenarios) = arrayfun(@(scen) computeSingleScenario(obj,dij,scen,w),obj.scenarios,'UniformOutput',false);
+ end
+
+ function [dExp,dOmegaV] = computeResultProb(obj,dij,w)
+ if isfield(dij,'physicalDoseExp')
+ dExp = cell(size(dij.physicalDoseExp));
+ [dExp(obj.scenarios),dOmegaVTmp] = arrayfun(@(scen) computeSingleScenarioProb(obj,dij,scen,w),obj.scenarios,'UniformOutput',false);
+ dOmegaV = cell(size(dij.physicalDoseOmega));
+ dOmegaV(:,obj.scenarios) = dOmegaVTmp{:};
+ else
+ dExp = [];
+ dOmegaV = [];
+ end
+ end
+
+ function wGrad = projectGradient(obj,dij,doseGrad,w)
+ wGrad = cell(size(dij.physicalDose));
+ wGrad(obj.scenarios) = arrayfun(@(scen) projectSingleScenarioGradient(obj,dij,doseGrad,scen,w),obj.scenarios,'UniformOutput',false);
+ end
+
+ function wGrad = projectGradientProb(obj,dij,dExpGrad,dOmegaVgrad,w)
+ wGrad = cell(size(dij.physicalDose));
+ wGrad(obj.scenarios) = arrayfun(@(scen) projectSingleScenarioGradientProb(obj,dij,dExpGrad,dOmegaVgrad,scen,w),obj.scenarios,'UniformOutput',false);
+ end
+ end
+
+
+ %These should be abstract methods, however Octave can't parse them. As soon
+ %as Octave is able to do this, they should be made abstract again
+ methods %(Abstract)
+ function d = computeSingleScenario(obj,dij,scen,w)
+ error('Function needs to be implemented');
+ end
+
+ function wGrad = projectSingleScenarioGradient(obj,dij,doseGrad,scen,w)
+ error('Function needs to be implemented');
+ end
+
+ function [dExp,dOmegaV] = computeSingleScenarioProb(obj,dij,scen,w)
+ %warning('');
+ end
+
+ function [dExp,dOmegaV] = projectSingleScenarioGradientProb(obj,dij,dExpGrad,dOmegaVgrad,scen,w)
+ %warning('');
+ end
+ end
+
+ methods (Static)
+ function optiFunc = setBiologicalDosePrescriptions(optiFunc,alphaX,betaX)
+ %Does nothing in a usual normal setting but return the original
+ %optiFunc
+ end
+ end
+end
+
diff --git a/optimization/projections/matRad_ConstantRBEProjection.m b/matRad/optimization/projections/matRad_ConstantRBEProjection.m
similarity index 60%
rename from optimization/projections/matRad_ConstantRBEProjection.m
rename to matRad/optimization/projections/matRad_ConstantRBEProjection.m
index b3b0c35da..77675c726 100644
--- a/optimization/projections/matRad_ConstantRBEProjection.m
+++ b/matRad/optimization/projections/matRad_ConstantRBEProjection.m
@@ -7,7 +7,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -30,6 +30,19 @@
end
end
+ function [dExp,dOmegaV] = computeSingleScenarioProb(~,dij,scen,w)
+ if ~isempty(dij.physicalDoseExp{scen})
+ dExp = dij.physicalDoseExp{scen}*(dij.RBE * w);
+
+ for i = 1:size(dij.physicalDoseOmega,1)
+ dOmegaV{i,scen} = dij.physicalDoseOmega{i,scen} * (dij.RBE * w);
+ end
+ else
+ dExp = [];
+ dOmegaV = [];
+ end
+ end
+
function wGrad = projectSingleScenarioGradient(~,dij,doseGrad,scen,~)
if ~isempty(dij.physicalDose{scen})
wGrad = ((dij.RBE * doseGrad{scen})' * dij.physicalDose{scen})';
@@ -39,6 +52,17 @@
matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
end
end
+
+ function wGrad = projectSingleScenarioGradientProb(~,dij,dExpGrad,dOmegaVgrad,scen,~)
+ if ~isempty(dij.physicalDoseExp{scen})
+ wGrad = ((dij.RBE * dExpGrad{scen})' * dij.physicalDoseExp{scen})';
+ wGrad = wGrad + 2 * dOmegaVgrad;
+ else
+ wGrad = [];
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
+ end
+ end
end
diff --git a/optimization/projections/matRad_DoseProjection.m b/matRad/optimization/projections/matRad_DoseProjection.m
similarity index 60%
rename from optimization/projections/matRad_DoseProjection.m
rename to matRad/optimization/projections/matRad_DoseProjection.m
index 7b70b8fb1..e3001973c 100644
--- a/optimization/projections/matRad_DoseProjection.m
+++ b/matRad/optimization/projections/matRad_DoseProjection.m
@@ -7,7 +7,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -31,6 +31,19 @@
end
end
+ function [dExp,dOmegaV] = computeSingleScenarioProb(~,dij,scen,w)
+ if ~isempty(dij.physicalDoseExp{scen})
+ dExp = dij.physicalDoseExp{scen}*w;
+
+ for i = 1:size(dij.physicalDoseOmega,2)
+ dOmegaV{scen,i} = dij.physicalDoseOmega{scen,i} * w;
+ end
+ else
+ dExp = [];
+ dOmegaV = [];
+ end
+ end
+
function wGrad = projectSingleScenarioGradient(~,dij,doseGrad,scen,~)
if ~isempty(dij.physicalDose{scen})
wGrad = (doseGrad{scen}' * dij.physicalDose{scen})';
@@ -40,6 +53,17 @@
matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
end
end
+
+ function wGrad = projectSingleScenarioGradientProb(~,dij,dExpGrad,dOmegaVgrad,scen,~)
+ if ~isempty(dij.physicalDoseExp{scen})
+ wGrad = (dExpGrad{scen}' * dij.physicalDoseExp{scen})';
+ wGrad = wGrad + 2 * dOmegaVgrad;
+ else
+ wGrad = [];
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
+ end
+ end
end
end
diff --git a/matRad/optimization/projections/matRad_EffectProjection.m b/matRad/optimization/projections/matRad_EffectProjection.m
new file mode 100644
index 000000000..70810ab8b
--- /dev/null
+++ b/matRad/optimization/projections/matRad_EffectProjection.m
@@ -0,0 +1,108 @@
+classdef matRad_EffectProjection < matRad_BackProjection
+% matRad_EffectProjection class for effect-based optimization
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ methods
+ function obj = matRad_EffectProjection()
+ end
+ end
+
+ methods
+ function effect = computeSingleScenario(~,dij,scen,w)
+ if isfield(dij,'mAlphaDose') && isfield(dij,'mSqrtBetaDose')
+ if isempty(dij.mAlphaDose{scen}) || isempty(dij.mSqrtBetaDose{scen})
+ effect = [];
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Empty mAlphaDose scenario in optimization detected! This should not happen...\n');
+ else
+ effect = dij.mAlphaDose{scen}*w + (dij.mSqrtBetaDose{scen}*w).^2;
+ end
+ else
+ if isempty(dij.ax{scen}) || isempty(dij.bx{scen})
+ effect = [];
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Empty dij.ax scenario in optimization detected! This should not happen...\n');
+ else
+ effect = dij.ax{scen} .* (dij.physicalDose{scen} * w) + dij.bx{scen} .* (dij.physicalDose{scen}*w).^2;
+ end
+ end
+ end
+
+ function wGrad = projectSingleScenarioGradient(~,dij,doseGrad,scen,w)
+ if isfield(dij,'mAlphaDose') && isfield(dij,'mSqrtBetaDose')
+ if isempty(dij.mAlphaDose{scen}) || isempty(dij.mSqrtBetaDose{scen})
+ wGrad = [];
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Empty mAlphaDose scenario in optimization detected! This should not happen...\n');
+ else
+ vBias = (doseGrad{scen}' * dij.mAlphaDose{scen})';
+ quadTerm = dij.mSqrtBetaDose{scen} * w;
+ mPsi = (2*(doseGrad{scen}.*quadTerm)' * dij.mSqrtBetaDose{scen})';
+ wGrad = vBias + mPsi;
+ end
+ else
+ if isempty(dij.ax{scen}) || isempty(dij.bx{scen})
+ wGrad = [];
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Empty dij.ax/dij.bx scenario in optimization detected! This should not happen...\n');
+ else
+ vBias = ((doseGrad{scen}.*dij.ax{scen})' * dij.physicalDose{scen})';
+ quadTerm = dij.physicalDose{scen} * w;
+ mPsi = (2*(doseGrad{scen}.*quadTerm.*dij.bx{scen})' * dij.physicalDose{scen})';
+ wGrad = vBias + mPsi;
+ end
+ end
+ end
+
+ function [eExp,dOmegaV] = computeSingleScenarioProb(~,dij,scen,w)
+ if isempty(dij.mAlphaDoseExp{scen}) || isempty(dij.mSqrtBetaDoseExp{scen})
+ eExp = [];
+ dOmegaV = [];
+ %matRad_cfg = MatRad_Config.instance();
+ %matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Probabilistic Backprojection uses inaccurate approximation for effect computation...\n');
+ %eExpLinTerm = dij.mAlphaDose{scen}*w;
+ %eExpSqTerm = dij.mSqrtBetaDose{scen}*w;
+ eExp = dij.mAlphaDose{scen}*w + (dij.mSqrtBetaDose{scen}*w).^2;
+
+ for i = 1:size(dij.physicalDoseOmega,2)
+ dOmegaV{scen,i} = dij.mAlphaDoseOmega{scen,i} * w;
+ end
+ end
+ end
+
+ function wGrad = projectSingleScenarioGradientProb(~,dij,dExpGrad,dOmegaVgrad,scen,~)
+ if isempty(dij.mAlphaDoseExp{scen}) || isempty(dij.mSqrtBetaDoseExp{scen})
+ wGrad = [];
+ else
+ vBias = (dExpGrad{scen}' * dij.mAlphaDoseExp{scen})';
+ quadTerm = dij.mSqrtBetaDoseExp{scen} * w;
+ mPsi = (2*(dExpGrad{scen}.*quadTerm)' * dij.mSqrtBetaDoseExp{scen})';
+ wGrad = vBias + mPsi;
+ wGrad = wGrad + 2 * dOmegaVgrad;
+ end
+ end
+ end
+
+ methods (Static)
+ function optiFunc = setBiologicalDosePrescriptions(optiFunc,alphaX,betaX)
+ doses = optiFunc.getDoseParameters();
+ effect = alphaX*doses + betaX*doses.^2;
+ optiFunc = optiFunc.setDoseParameters(effect);
+ end
+ end
+end
diff --git a/matRad/optimization/projections/matRad_VariableRBEProjection.m b/matRad/optimization/projections/matRad_VariableRBEProjection.m
new file mode 100644
index 000000000..b15ae999e
--- /dev/null
+++ b/matRad/optimization/projections/matRad_VariableRBEProjection.m
@@ -0,0 +1,111 @@
+classdef matRad_VariableRBEProjection < matRad_EffectProjection
+% matRad_VariableRBEProjection class for RBE-weighted dose optimization
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ methods
+ function obj = matRad_VariableRBEProjection()
+ end
+
+ function RBExD = computeSingleScenario(obj,dij,scen,w)
+ effect = computeSingleScenario@matRad_EffectProjection(obj,dij,scen,w); %First compute effect
+ RBExD = zeros(dij.doseGrid.numOfVoxels,1);
+ [ctScen,~] = ind2sub(size(dij.physicalDose),scen);
+ RBExD(dij.ixDose{ctScen}) = sqrt((effect(dij.ixDose{ctScen})./dij.bx{ctScen}(dij.ixDose{ctScen}))+(dij.gamma{ctScen}(dij.ixDose{ctScen}).^2)) - dij.gamma{ctScen}(dij.ixDose{ctScen});
+ end
+
+ function wGrad = projectSingleScenarioGradient(obj,dij,doseGrad,scen,w)
+ if isempty(dij.mAlphaDose{scen}) || isempty(dij.mSqrtBetaDose{scen})
+ wGrad = [];
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
+ else
+ %While the dose cache should be up to date here, we ask for
+ %a computation (will skip if weights are equal to cache)
+ obj = obj.compute(dij,w);
+
+ %Get corresponding ct scenario
+ [ctScen,~] = ind2sub(size(dij.physicalDose),scen);
+
+ %Scaling vor variable RBExD
+ scaledEffect = obj.d{scen} + dij.gamma{ctScen};
+ doseGradTmp = zeros(dij.doseGrid.numOfVoxels,1);
+ doseGradTmp(dij.ixDose{ctScen}) = doseGrad{scen}(dij.ixDose{ctScen}) ./ (2*dij.bx{ctScen}(dij.ixDose{ctScen}).*scaledEffect(dij.ixDose{ctScen}));
+
+ %Now modify the effect computation
+ vBias = (doseGradTmp' * dij.mAlphaDose{scen})';
+ quadTerm = dij.mSqrtBetaDose{scen} * w;
+ mPsi = (2*(doseGradTmp.*quadTerm)' * dij.mSqrtBetaDose{scen})';
+ wGrad = vBias + mPsi;
+ end
+ end
+
+ function [RBExDexp,dOmegaV] = computeSingleScenarioProb(~,dij,scen,w)
+ if isempty(dij.mAlphaDoseExp{scen}) || isempty(dij.mSqrtBetaDoseExp{scen})
+ RBExDexp = [];
+ dOmegaV = [];
+ %matRad_cfg = MatRad_Config.instance();
+ %matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Probabilistic Backprojection uses inaccurate approximation for Variable RBE computation...\n');
+ eExpLinTerm = dij.mAlphaDose{scen}*w;
+ eExpSqTerm = dij.mSqrtBetaDose{scen}*w;
+ eExp = eExpLinTerm + eExpSqTerm.^2;
+
+ %Get corresponding ct scenario
+ [ctScen,~] = ind2sub(size(dij.physicalDose),scen);
+
+ RBExDexp = zeros(dij.doseGrid.numOfVoxels,1);
+ RBExDexp(dij.ixDose{ctScen}) = sqrt((eExp(dij.ixDose{ctScen})./dij.bx{ctScen}(dij.ixDose{ctScen}))+(dij.gamma{ctScen}(dij.ixDose{ctScen}).^2)) - dij.gamma{ctScen}(dij.ixDose{ctScen});
+
+ for i = 1:size(dij.physicalDoseOmega,2)
+ dOmegaV{scen,i} = dij.mAlphaDoseOmega{scen,i} * w;
+ end
+ end
+ end
+
+ function wGrad = projectSingleScenarioGradientProb(obj,dij,dExpGrad,dOmegaVgrad,scen,~)
+ if isempty(dij.mAlphaDoseExp{scen}) || isempty(dij.mSqrtBetaDoseExp{scen})
+ wGrad = [];
+ else
+ %While the dose cache should be up to date here, we ask for
+ %a computation (will skip if weights are equal to cache)
+ obj = obj.compute(dij,w);
+
+ %Get corresponding ct scenario
+ [ctScen,~]= ind2sub(size(dij.physicalDose),scen);
+
+ %Scaling vor variable RBExD
+ scaledEffect = obj.dExp{scen} + dij.gamma{ctScen};
+ doseGradTmp = zeros(dij.doseGrid.numOfVoxels,1);
+ doseGradTmp(dij.ixDose{ctScen}) = dExpGrad{scen}(dij.ixDose{ctScen}) ./ (2*dij.bx{ctScen}(dij.ixDose{ctScen}).*scaledEffect(dij.ixDose{ctScen}));
+
+ %Now modify the effect computation
+ vBias = (doseGradTmp' * dij.mAlphaDoseExp{scen})';
+ quadTerm = dij.mSqrtBetaDose{scen} * w;
+ mPsi = (2*(doseGradTmp.*quadTerm)' * dij.mSqrtBetaDoseExp{scen})';
+ wGrad = vBias + mPsi + 2 * dOmegaVgrad;
+ end
+ end
+ end
+
+ methods (Static)
+ function optiFunc = setBiologicalDosePrescriptions(optiFunc,~,~)
+ %Do nothing here to overwrite the behavior of the Effect
+ %projection, since we have unit GyRBE here
+ end
+ end
+end
+
diff --git a/phantoms/BOXPHANTOM.mat b/matRad/phantoms/BOXPHANTOM.mat
similarity index 100%
rename from phantoms/BOXPHANTOM.mat
rename to matRad/phantoms/BOXPHANTOM.mat
diff --git a/phantoms/HEAD_AND_NECK.mat b/matRad/phantoms/HEAD_AND_NECK.mat
similarity index 100%
rename from phantoms/HEAD_AND_NECK.mat
rename to matRad/phantoms/HEAD_AND_NECK.mat
diff --git a/phantoms/LIVER.mat b/matRad/phantoms/LIVER.mat
similarity index 97%
rename from phantoms/LIVER.mat
rename to matRad/phantoms/LIVER.mat
index 7f06beec4..f56cf30c2 100644
Binary files a/phantoms/LIVER.mat and b/matRad/phantoms/LIVER.mat differ
diff --git a/phantoms/PROSTATE.mat b/matRad/phantoms/PROSTATE.mat
similarity index 100%
rename from phantoms/PROSTATE.mat
rename to matRad/phantoms/PROSTATE.mat
diff --git a/phantoms/TG119.mat b/matRad/phantoms/TG119.mat
similarity index 100%
rename from phantoms/TG119.mat
rename to matRad/phantoms/TG119.mat
diff --git a/matRad/phantoms/builder/matRad_PhantomBuilder.m b/matRad/phantoms/builder/matRad_PhantomBuilder.m
new file mode 100644
index 000000000..46a1ab0e7
--- /dev/null
+++ b/matRad/phantoms/builder/matRad_PhantomBuilder.m
@@ -0,0 +1,143 @@
+classdef matRad_PhantomBuilder < handle
+ % matRad_PhantomBuilder
+ % Class that helps to create radiotherapy phantoms
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2023 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Access = public)
+ volumes = {};
+ end
+
+ properties (Access = private)
+ ct;
+ cst = {};
+ end
+
+ methods (Access = public)
+ function obj = matRad_PhantomBuilder(ctDim,ctResolution,numOfCtScen)
+ obj.ct = struct();
+ obj.ct.cubeDim = [ctDim(2),ctDim(1),ctDim(3)];
+ obj.ct.resolution.x = ctResolution(1);
+ obj.ct.resolution.y = ctResolution(2);
+ obj.ct.resolution.z = ctResolution(3);
+ obj.ct.numOfCtScen = numOfCtScen;
+ obj.ct.cubeHU{1} = ones(obj.ct.cubeDim) * -1000;
+ end
+
+ %functions to create Targets %TODO: Option to extend volumes
+ function addBoxTarget(obj,name,dimensions,varargin)
+ % Adds a box target
+ %
+ % input:
+ % name: Name of VOI as string
+ % dimensions: Dimensions of the box as [x,y,z] array
+ %
+ % Name-Value pairs:
+ % 'offset': The offset of the VOI with respect to the
+ % center of the geometry as [x,y,z] array
+ % 'objectives': Either a single objective or a cell array
+ % of objectives
+ % 'HU': Houndsfield unit of the volume
+
+ obj.volumes(end+1) = {matRad_PhantomVOIBox(name,'TARGET',dimensions,varargin{:})};
+ obj.updatecst();
+ end
+
+ function addSphericalTarget(obj,name,radius,varargin)
+ % Adds a spherical target
+ %
+ % input:
+ % name: Name of VOI as string
+ % radius: Radius of the sphere
+ %
+ % Name-Value pairs:
+ % 'offset': The offset of the VOI with respect to the
+ % center of the geometry as [x,y,z] array
+ % 'objectives': Either a single objective or a cell array
+ % of objectives
+ % 'HU': Houndsfield unit of the volume
+
+ obj.volumes(end+1) = {matRad_PhantomVOISphere(name,'TARGET',radius,varargin{:})};
+ obj.updatecst();
+ end
+
+
+ function addBoxOAR(obj,name,dimensions,varargin)
+ % Adds a box OAR
+ %
+ % input:
+ % name: Name of VOI as string
+ % dimensions: Dimensions of the box as [x,y,z] array
+ %
+ % Name-Value pairs:
+ % 'offset': The offset of the VOI with respect to the
+ % center of the geometry as [x,y,z] array
+ % 'objectives': Either a single objective or a cell array
+ % of objectives
+ % 'HU': Houndsfield unit of the volume
+
+ obj.volumes(end+1) = {matRad_PhantomVOIBox(name,'OAR',dimensions,varargin{:})};
+ obj.updatecst();
+ end
+
+ function addSphericalOAR(obj,name,radius,varargin)
+ % Adds a spherical OAR
+ %
+ % input:
+ % name: Name of VOI as string
+ % radius: Radius of the sphere
+ %
+ % Name-Value pairs:
+ % 'offset': The offset of the VOI with respect to the
+ % center of the geometry as [x,y,z] array
+ % 'objectives': Either a single objective or a cell array
+ % of objectives
+ % 'HU': Houndsfield unit of the volume
+
+ obj.volumes(end+1) ={matRad_PhantomVOISphere(name,'OAR',radius,varargin{:})};
+ obj.updatecst();
+ end
+
+
+ function [ct,cst] = getctcst(obj)
+ % Returns the ct and struct. The function also initializes
+ % the HUs in reverse order of defintion
+ %
+ % output
+ % ct: matRad ct struct
+ % cst: matRad cst struct
+
+ %initialize the HU in reverse order of definition (objectives
+ %defined at the start will have the highest priority in case of
+ %overlaps)
+
+ for i = 1:size(obj.cst,1)
+ vIxVOI = obj.cst{end-i+1,4}{1};
+ obj.ct.cubeHU{1}(vIxVOI) = obj.volumes{1,end-i+1}.HU; % assign HU
+ end
+
+ ct = obj.ct;
+ cst = obj.cst;
+ end
+
+ end
+
+ methods (Access = private)
+
+ function updatecst(obj)
+ obj.cst = obj.volumes{end}.initializeParameters(obj.ct,obj.cst);
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/matRad/phantoms/builder/matRad_PhantomVOIBox.m b/matRad/phantoms/builder/matRad_PhantomVOIBox.m
new file mode 100644
index 000000000..e66c7b1fa
--- /dev/null
+++ b/matRad/phantoms/builder/matRad_PhantomVOIBox.m
@@ -0,0 +1,73 @@
+classdef matRad_PhantomVOIBox < matRad_PhantomVOIVolume
+ % matRad_PhantomVOIBox implements a class that helps to create box VOIs
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2022 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties %additional property of cubic objects
+ boxDimensions;
+ end
+
+ methods (Access = public)
+
+ function obj = matRad_PhantomVOIBox(name,type,boxDimensions,varargin)
+ p = inputParser;
+ addParameter(p,'objectives',{});
+ addParameter(p,'offset',[0,0,0]);
+ addParameter(p,'HU',0);
+ parse(p,varargin{:});
+
+ obj@matRad_PhantomVOIVolume(name,type,p); %call superclass constructor
+ obj.boxDimensions = boxDimensions;
+ end
+
+ function [cst] = initializeParameters(obj,ct,cst)
+ %add this objective to the phantomBuilders cst
+
+ cst = initializeParameters@matRad_PhantomVOIVolume(obj,cst);
+ center = round(ct.cubeDim/2);
+ VOIHelper = zeros(ct.cubeDim);
+ offsets = obj.offset;
+ dims = obj.boxDimensions;
+
+ xMinMax = center(2)+offsets(1) + round(dims(1)/2)*[-1,1];
+ yMinMax = center(1)+offsets(2) + round(dims(2)/2)*[-1,1];
+ zMinMax = center(3)+offsets(3) + round(dims(3)/2)*[-1,1];
+
+ %Correct if out of bounds
+ xMinMax(xMinMax < 1) = 1;
+ yMinMax(yMinMax < 1) = 1;
+ zMinMax(zMinMax < 1) = 1;
+
+ xMinMax(xMinMax > ct.cubeDim(2)) = ct.cubeDim(2);
+ yMinMax(yMinMax > ct.cubeDim(1)) = ct.cubeDim(1);
+ zMinMax(zMinMax > ct.cubeDim(3)) = ct.cubeDim(3);
+
+ for x = xMinMax(1):1:xMinMax(2)
+ for y = yMinMax(1):1:yMinMax(2)
+ for z = zMinMax(1):1:zMinMax(2)
+ VOIHelper(y,x,z) = 1;
+ end
+ end
+ end
+
+ cst{end,4}{1} = find(VOIHelper);
+
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/matRad/phantoms/builder/matRad_PhantomVOISphere.m b/matRad/phantoms/builder/matRad_PhantomVOISphere.m
new file mode 100644
index 000000000..c0e457f62
--- /dev/null
+++ b/matRad/phantoms/builder/matRad_PhantomVOISphere.m
@@ -0,0 +1,60 @@
+classdef matRad_PhantomVOISphere < matRad_PhantomVOIVolume
+ % matRad_PhantomVOISphere implements a class that helps to create spheric VOIs
+ %
+ % References
+ % -
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2022 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+ radius;
+ end
+
+ methods (Access = public)
+ function obj = matRad_PhantomVOISphere(name,type,radius,varargin)
+ p = inputParser;
+ addParameter(p,'objectives',{});
+ addParameter(p,'offset',[0,0,0]);
+ addParameter(p,'HU',0);
+ parse(p,varargin{:});
+
+ obj@matRad_PhantomVOIVolume(name,type,p); %call superclass constructor
+ obj.radius = radius;
+ end
+
+ function [cst] = initializeParameters(obj,ct,cst)
+ %add this VOI to the phantomBuilders cst
+
+ cst = initializeParameters@matRad_PhantomVOIVolume(obj,cst);
+ center = round([ct.cubeDim/2]);
+ VOIHelper = zeros(ct.cubeDim);
+ offsets = obj.offset;
+
+ for x = 1:ct.cubeDim(2)
+ for y = 1:ct.cubeDim(1)
+ for z = 1:ct.cubeDim(3)
+ currPost = [y x z] + offsets - center;
+ if (sqrt(sum(currPost.^2)) < obj.radius)
+ VOIHelper(y,x,z) = 1;
+ end
+ end
+ end
+ end
+
+ cst{end,4}{1} = find(VOIHelper);
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/matRad/phantoms/builder/matRad_PhantomVOIVolume.m b/matRad/phantoms/builder/matRad_PhantomVOIVolume.m
new file mode 100644
index 000000000..8f35db910
--- /dev/null
+++ b/matRad/phantoms/builder/matRad_PhantomVOIVolume.m
@@ -0,0 +1,84 @@
+classdef (Abstract) matRad_PhantomVOIVolume < handle
+% matRad_PhantomVOIVolume: Interface for VOI Volumes
+% This abstract base class provides the structure of VOI Volumes.
+% So far implemented: Box and spherical objectives
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2023 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ name;
+ type;
+ TissueClass = 1;
+ alphaX = 0.1000;
+ betaX = 0.0500;
+ Priority = 1;
+ Visible = 1;
+ visibleColor = [0 0 0];
+ HU = 0;
+ offset = [0,0,0]; %center of objective
+ objectives = {};
+ colors = [[1,0,0];[0,1,0];[0,0,1];[1,1,0];[1,0,1];[0,1,1];[1,1,1]];
+ end
+
+ methods
+ function obj = matRad_PhantomVOIVolume(name,type,p)
+ %p is the input parser used in the child classes to check for additional variables
+
+ obj.name = name;
+ obj.type = type;
+ obj.offset = p.Results.offset;
+ obj.HU = p.Results.HU;
+
+
+ %idea is that DoseObjectiveFunction can be either a single objective or an
+ %array of objectives. If it is a single objective store it as a cell array
+ if iscell(p.Results.objectives)
+ obj.objectives = p.Results.objectives;
+ else
+ obj.objectives = {p.Results.objectives};
+ end
+ %}
+ end
+
+ function cst = initializeParameters(obj,cst)
+ %initialize entry for this VOI in cst
+ nxIdx = size(cst,1)+1;
+ cst{nxIdx,1} = nxIdx-1;
+ cst{nxIdx,2} = obj.name;
+ cst{nxIdx,3} = obj.type;
+ cst{nxIdx,5}.TissueClass = obj.TissueClass;
+ cst{nxIdx,5}.alphaX = obj.alphaX;
+ cst{nxIdx,5}.betaX = obj.betaX;
+ cst{nxIdx,5}.Priority = nxIdx;
+ cst{nxIdx,5}.Visible = obj.Visible;
+
+ if nxIdx <= size(obj.colors,1)
+ obj.visibleColor = obj.colors(nxIdx,:);
+ end
+ cst{nxIdx,5}.visibleColor = obj.visibleColor;
+
+ if ~iscell(obj.objectives) %should be redundant
+ DoseObjectives = {obj.objectives};
+ else
+ DoseObjectives = obj.objectives;
+ end
+ for i = 1:numel(DoseObjectives)
+ cst{nxIdx,6} {i}= DoseObjectives{i};
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/matRad/planAnalysis/matRad_EQD2accumulation.m b/matRad/planAnalysis/matRad_EQD2accumulation.m
new file mode 100644
index 000000000..ff4133745
--- /dev/null
+++ b/matRad/planAnalysis/matRad_EQD2accumulation.m
@@ -0,0 +1,163 @@
+function result = matRad_EQD2accumulation(pln1,ct1,cst1,dose1,prescribedDose1, ...
+ pln2,ct2,cst2,dose2,prescribedDose2)
+
+% matRad function to accumulate and compare dose and EQD2 for two treatment
+% plans
+%
+% call
+% result = matRad_EQD2accumulation(pln1,ct1,cst1,dose1,prescribedDose1, ...
+% pln2,ct2,cst2,dose2,prescribedDose2)
+%
+% input
+% pln1/2: matRad pln struct
+% ct1/2: matRad ct struct
+% cst1/2: matRad cst struct
+% dose1/2: 3D (RBE-weighted) dose cubes
+% prescribedDose1/2: prescribed doses of the respective dose cubes
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+% parameters that can be changed to fit to the RT plans, also alpha/beta
+% for the EQD_2 calculation can be changed
+
+checkImageRegis = true; % true or false, shows pictures of the particle and photon CT to check if the image registration went wrong (true shows pictures)
+
+alphaBetaRatio = 2; % alpha/beta value
+
+numOfFractions1 = pln1.numOfFractions; % number of photon fractions
+numOfFractions2 = pln2.numOfFractions; % number of proton fractions
+
+%% image registration and recalculation of the photon dose cube to fit in the particle dose cube
+% Rfixed/Rmoving are helping the optimizer
+fixed = ct2.cubeHU{1};
+Rfixed = imref3d(ct2.cubeDim,ct2.resolution.y,ct2.resolution.x,ct2.resolution.z);
+moving = ct1.cubeHU{1};
+Rmoving = imref3d(ct1.cubeDim,ct1.resolution.y,ct1.resolution.x,ct1.resolution.z);
+
+% choice of the optimizer for the image registration, for this ct
+% registration monomodal is highly recommended
+% below are possible changes for the optimization, values are the default
+% values; only change is done in the maximum iterations (default 100)
+
+% [optimizer, metric] = imregconfig('multimodal');
+[optimizer, metric] = imregconfig('monomodal');
+
+% optimizer parameters for multimodal
+% optimizer.GrowthFactor = 1.05;
+% optimizer.Epsilon = 1.5e-6;
+% optimizer.InitialRadius = 0.0063;
+% optimizer.MaximumIterations = 100;
+
+% optimizer changes for monomodal
+% optimizer.GradientMagnitudeTolerance = 1.0e-4;
+% optimizer.MinimumStepLength = 1.0e-5;
+% optimizer.MaximumStepLength = 0.0625;
+optimizer.MaximumIterations = 300;
+% optimizer.RelaxationFactor = 0.5;
+
+geomtform = imregtform(moving,Rmoving, fixed,Rfixed, 'affine', optimizer, metric,'DisplayOptimization',false);
+warpDose1 = imwarp(dose1,Rmoving,geomtform,'OutputView',Rfixed);
+
+%% add photon target to particle cst, deleting structs from the cst without volume information (e.g. reference points)
+% cst is the cell where the changes are done and that is use at the end for
+% the DVH and the quality indicators
+
+% deleting empty structs in reference cst
+cst2(cellfun(@isempty,cst2(:,4)),:) = [];
+
+% adding target structs with the help of the geometrical information
+% from the image registration
+for i = 1 : size(cst1)
+
+ if strcmp(cst1{i,3} ,'TARGET')
+
+ cube1 = zeros(ct1.cubeDim);
+
+ structIndices = cst1{i,4}{1};
+ cube1(structIndices) = 1;
+ newPhotonCube = imwarp(cube1,Rmoving,geomtform,'OutputView',Rfixed);
+
+ % ist das hier zul�ssig? vergr�ssern wir hier durch interpolation
+ % bei imwarp nicht die volumina?
+ newStructIndices = find(newPhotonCube > 0);
+
+ cst2{end+1,1} = size(cst2) + 1;
+ cst2{end ,2} = [cst1{i,2} '_Photon'];
+ cst2{end ,3} = 'TARGET';
+ cst2{end ,4}{1} = newStructIndices;
+ cst2{end ,5} = cst1{i,5};
+ cst2{end ,6} = cst1{i,6};
+
+ end
+end
+
+%% EQD_2 calculations (calculates RBExD added dose, photon EQD_2, particle EQD_2, added EQD_2 and RBExD added divided by EQD_2 added [and invers ^-1])
+
+result.totalDose = numOfFractions1 * warpDose1 + numOfFractions2 * dose2;
+
+result.EQD2_1 = warpDose1 * numOfFractions1 .* ((warpDose1 + alphaBetaRatio) / (2 + alphaBetaRatio));
+result.EQD2_2 = dose2 * numOfFractions2 .* ((dose2 + alphaBetaRatio) / (2 + alphaBetaRatio));
+result.totalEQD2 = result.EQD2_1 + result.EQD2_2;
+
+result.EQD2ratio = result.totalEQD2 ./ result.totalDose;
+result.EQD2ratioInvers = result.totalDose ./ result.totalEQD2;
+
+
+%% DVH calculation
+
+aimedDose = numOfFractions1 * prescribedDose1 + numOfFractions2 * prescribedDose2;
+aimedEQD2 = numOfFractions1 * prescribedDose1 .* ((prescribedDose1 + alphaBetaRatio) / (2 + alphaBetaRatio)) + numOfFractions2 * prescribedDose2 .* ((prescribedDose2 + alphaBetaRatio) / (2 + alphaBetaRatio));
+
+dvh_dose = matRad_calcDVH(cst2,result.totalDose ./ aimedDose);
+dvh_EQD2 = matRad_calcDVH(cst2,result.totalEQD2 ./ aimedEQD2);
+dvh_EQD2ratio = matRad_calcDVH(cst2,result.EQD2ratio);
+
+figure;
+matRad_showDVH(dvh_dose,cst2,pln2,'plotLegend',false,'axesHandle',gca);
+hold on
+matRad_showDVH(dvh_EQD2,cst2,pln2,'plotLegend',false,'axesHandle',gca,'lineStyle','--');
+title(['Added dose cubes divided by aimed dose, straight line RBExD added [aimed: ' num2str(aimedDose) 'Gy], dashed line EQD_2 added [aimed : ' num2str(aimedEQD2) 'Gy]']);
+xlabel('relative Dose [%]');
+legend(cst2{:,2});
+hold off
+
+
+figure;
+matRad_showDVH(dvh_EQD2ratio,cst2,pln2,'plotLegend',false,'axesHandle',gca);
+title('Added EQD_2 divided by added RBExD');
+xlabel('relative Dose [%]');
+
+%% Qualitiy indicators
+fieldNamesResult = fieldnames(result);
+
+for resultGUInumber = 1 : numel(fieldNamesResult)
+ qualityIndicators.(fieldNamesResult{resultGUInumber}) = matRad_calcQualityIndicators(cst2,pln2,result.(fieldNamesResult{resultGUInumber}));
+end
+
+
+%% image registration check (shows ct pictures)
+if checkImageRegis == true
+ movedCT = imwarp(moving,Rmoving,geomtform,'OutputView',Rfixed);
+
+ figure,imshowpair(squeeze( fixed(:,50,:)),squeeze( moving(:,50,:)),'Scaling','joint');
+ title('fixed vs moving');
+ figure,imshowpair(squeeze( fixed(:,50,:)),squeeze( movedCT(:,50,:)),'scaling','joint');
+ title('fixed vs moved');
+
+ figure,imshowpair(fixed(:,:,50),moving(:,:,50),'Scaling','joint');
+ title('fixed vs moving');
+ figure,imshowpair( fixed(:,:,50),movedCT(:,:,50),'scaling','joint');
+ title('fixed vs moved');
+end
\ No newline at end of file
diff --git a/matRad_calcDVH.m b/matRad/planAnalysis/matRad_calcDVH.m
similarity index 97%
rename from matRad_calcDVH.m
rename to matRad/planAnalysis/matRad_calcDVH.m
index 6ae755950..9b566dae4 100644
--- a/matRad_calcDVH.m
+++ b/matRad/planAnalysis/matRad_calcDVH.m
@@ -28,7 +28,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -85,4 +85,3 @@
end
dvh = dvh ./ numOfVoxels * 100;
end %eof getDVHPoints
-
diff --git a/matRad_calcQualityIndicators.m b/matRad/planAnalysis/matRad_calcQualityIndicators.m
similarity index 97%
rename from matRad_calcQualityIndicators.m
rename to matRad/planAnalysis/matRad_calcQualityIndicators.m
index 58fc92b61..8088b7ee3 100644
--- a/matRad_calcQualityIndicators.m
+++ b/matRad/planAnalysis/matRad_calcQualityIndicators.m
@@ -29,7 +29,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -54,7 +54,7 @@
indices = cst{runVoi,4}{1};
numOfVoxels = numel(indices);
- voiPrint = sprintf('%3d %20s',cst{runVoi,1},cst{runVoi,2}); %String that will print quality indicators
+ voiPrint = sprintf('%3d %20s',cst{runVoi,1},cst{runVoi,2}); %String that will print quality indicators
% get Dose, dose is sorted to simplify calculations
doseInVoi = sort(doseCube(indices));
diff --git a/tools/matRad_compareDose.m b/matRad/planAnalysis/matRad_compareDose.m
similarity index 54%
rename from tools/matRad_compareDose.m
rename to matRad/planAnalysis/matRad_compareDose.m
index bcbcc8b11..501244233 100644
--- a/tools/matRad_compareDose.m
+++ b/matRad/planAnalysis/matRad_compareDose.m
@@ -48,7 +48,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -57,6 +57,12 @@
matRad_cfg = MatRad_Config.instance();
+colorSpec = {'Color',matRad_cfg.gui.elementColor,...
+ 'XColor',matRad_cfg.gui.textColor,...
+ 'YColor',matRad_cfg.gui.textColor,...
+ 'GridColor',matRad_cfg.gui.textColor,...
+ 'MinorGridColor',matRad_cfg.gui.backgroundColor};
+
%% check if cubes consistent
if ~isequal(size(cube1),size(cube2))
matRad_cfg.dispError('dose cubes must be the same size\n');
@@ -104,25 +110,24 @@
%% Calculate iso-center slices and resolution
if isempty(cst)
- [~,s(1)] = max(sum(sum(cube1,1),3));
- [~,s(2)] = max(sum(sum(cube1,2),3));
- [~,s(3)] = max(sum(sum(cube1,1),2));
- isoCenter = [ct.resolution.y*s(1) ct.resolution.x*s(2) ct.resolution.z*s(3)];
-else
- isoCenter = matRad_getIsoCenter(cst,ct,0);
+ isoCenterIx = round(ct.cubeDim./2);
+else
+ isoCenterIx = matRad_world2cubeIndex( matRad_getIsoCenter(cst,ct,0),ct);
end
resolution = [ct.resolution.x ct.resolution.y ct.resolution.z];
-slicename = {round(isoCenter(2)./resolution(2)),round(isoCenter(1)./resolution(1)),round(isoCenter(3)./resolution(3))};
-doseWindow = [0 max([cube1(:); cube2(:)])];
-planename = {'coronal','sagittal','axial'};
+sliceName = {isoCenterIx(1),isoCenterIx(2),isoCenterIx(3)};
+doseWindow = [0 max([cube1(:); cube2(:)])*1.001];
+planeName = {'coronal','sagittal','axial'};
%% Integral Energy Output
-intEnergy1 = matRad_calcIntEnergy(cube1,ct,pln);
-intEnergy2 = matRad_calcIntEnergy(cube2,ct,pln);
-
-matRad_cfg.dispInfo('Integral energy comparison: Cube 1 = %1.4g MeV, Cube 2 = %1.4g MeV, difference = %1.4g Mev\n',intEnergy1,intEnergy2,intEnergy1-intEnergy2);
+if ~isempty(pln)
+ intEnergy1 = matRad_calcIntEnergy(cube1,ct,pln);
+ intEnergy2 = matRad_calcIntEnergy(cube2,ct,pln);
+
+ matRad_cfg.dispInfo('Integral energy comparison: Cube 1 = %1.4g MeV, Cube 2 = %1.4g MeV, difference = %1.4g Mev\n',intEnergy1,intEnergy2,intEnergy1-intEnergy2);
+end
%% Colorwash images
if enable(1) == 1
@@ -142,7 +147,7 @@
% Calculate absolute difference cube and dose windows for plots
differenceCube = cube1-cube2;
- doseDiffWindow = [-max(differenceCube(:)) max(differenceCube(:))];
+ doseDiffWindow = [-max(abs(differenceCube(:))) max(abs(differenceCube(:)))];
%doseGammaWindow = [0 max(gammaCube(:))];
doseGammaWindow = [0 2]; %We choose 2 as maximum value since the gamma colormap has a sharp cut in the middle
@@ -156,58 +161,57 @@
end
for plane = 1:3
- disp(['Plotting ',planename{plane},' plane...']);
+ matRad_cfg.dispInfo('Plotting %s plane...\n',planeName{plane});
% Initialize Figure
- hfig.(planename{plane}).('fig') = figure('Renderer', 'painters', 'Position', [10 50 800 800]);
- set(gcf,'Color',[1 1 1]);
+ hfig.(planeName{plane}).('fig') = figure('Position', [10 50 800 800],'Color',matRad_cfg.gui.backgroundColor);
% Plot Dose 1
- hfig.(planename{plane}).('cube1').Axes = subplot(2,2,1);
- [hfig.(planename{plane}).('cube1').CMap,...
- hfig.(planename{plane}).('cube1').Dose,...
- hfig.(planename{plane}).('cube1').Ct,...
- hfig.(planename{plane}).('cube1').Contour,...
- hfig.(planename{plane}).('cube1').IsoDose] = ...
- matRad_plotSliceWrapper(gca,ct,cstHandle,1,cube1,plane,slicename{plane},[],[],colorcube,jet,doseWindow,[],100);
+ hfig.(planeName{plane}).('cube1').Axes = subplot(2,2,1,colorSpec{:});
+ [hfig.(planeName{plane}).('cube1').CMap,...
+ hfig.(planeName{plane}).('cube1').Dose,...
+ hfig.(planeName{plane}).('cube1').Ct,...
+ hfig.(planeName{plane}).('cube1').Contour,...
+ hfig.(planeName{plane}).('cube1').IsoDose] = ...
+ matRad_plotSliceWrapper(gca,ct,cstHandle,1,cube1,plane,sliceName{plane},[],[],colorcube,jet,doseWindow,[],100);
% Plot Dose 2
- hfig.(planename{plane}).('cube2').Axes = subplot(2,2,2);
- [hfig.(planename{plane}).('cube2').CMap,...
- hfig.(planename{plane}).('cube2').Dose,...
- hfig.(planename{plane}).('cube2').Ct,...
- hfig.(planename{plane}).('cube2').Contour,...
- hfig.(planename{plane}).('cube2').IsoDose] = ...
- matRad_plotSliceWrapper(gca,ct,cstHandle,1,cube2,plane,slicename{plane},[],[],colorcube,jet,doseWindow,[],100);
+ hfig.(planeName{plane}).('cube2').Axes = subplot(2,2,2,colorSpec{:});
+ [hfig.(planeName{plane}).('cube2').CMap,...
+ hfig.(planeName{plane}).('cube2').Dose,...
+ hfig.(planeName{plane}).('cube2').Ct,...
+ hfig.(planeName{plane}).('cube2').Contour,...
+ hfig.(planeName{plane}).('cube2').IsoDose] = ...
+ matRad_plotSliceWrapper(gca,ct,cstHandle,1,cube2,plane,sliceName{plane},[],[],colorcube,jet,doseWindow,[],100);
% Plot absolute difference
- hfig.(planename{plane}).('diff').Axes = subplot(2,2,3);
- [hfig.(planename{plane}).('diff').CMap,...
- hfig.(planename{plane}).('diff').Dose,...
- hfig.(planename{plane}).('diff').Ct,...
- hfig.(planename{plane}).('diff').Contour,...
- hfig.(planename{plane}).('diff').IsoDose] = ...
- matRad_plotSliceWrapper(gca,ct,cstHandle,1,differenceCube,plane,slicename{plane},[],[],colorcube,diffCMap,doseDiffWindow,[],100);
+ hfig.(planeName{plane}).('diff').Axes = subplot(2,2,3,colorSpec{:});
+ [hfig.(planeName{plane}).('diff').CMap,...
+ hfig.(planeName{plane}).('diff').Dose,...
+ hfig.(planeName{plane}).('diff').Ct,...
+ hfig.(planeName{plane}).('diff').Contour,...
+ hfig.(planeName{plane}).('diff').IsoDose] = ...
+ matRad_plotSliceWrapper(gca,ct,cstHandle,1,differenceCube,plane,sliceName{plane},[],[],colorcube,diffCMap,doseDiffWindow,[],100);
% Plot gamma analysis
- hfig.(planename{plane}).('gamma').Axes = subplot(2,2,4);
+ hfig.(planeName{plane}).('gamma').Axes = subplot(2,2,4,colorSpec{:});
gammaCMap = matRad_getColormap('gammaIndex');
- [hfig.(planename{plane}).('gamma').CMap,...
- hfig.(planename{plane}).('gamma').Dose,...
- hfig.(planename{plane}).('gamma').Ct,...
- hfig.(planename{plane}).('gamma').Contour,...
- hfig.(planename{plane}).('gamma').IsoDose]=...
- matRad_plotSliceWrapper(gca,ct,cstHandle,1,gammaCube,plane,slicename{plane},[],[],colorcube,gammaCMap,doseGammaWindow,[],100);
+ [hfig.(planeName{plane}).('gamma').CMap,...
+ hfig.(planeName{plane}).('gamma').Dose,...
+ hfig.(planeName{plane}).('gamma').Ct,...
+ hfig.(planeName{plane}).('gamma').Contour,...
+ hfig.(planeName{plane}).('gamma').IsoDose]=...
+ matRad_plotSliceWrapper(gca,ct,cstHandle,1,gammaCube,plane,sliceName{plane},[],[],colorcube,gammaCMap,doseGammaWindow,[],100);
% Adjusting axes
- matRad_plotAxisLabels(hfig.(planename{plane}).('cube1').Axes,ct,plane,slicename{plane},[],100);
- set(get(hfig.(planename{plane}).('cube1').Axes, 'title'), 'string', 'Dose 1');
- matRad_plotAxisLabels(hfig.(planename{plane}).('cube2').Axes,ct,plane,slicename{plane},[],100);
- set(get(hfig.(planename{plane}).('cube2').Axes, 'title'), 'string', 'Dose 2');
- matRad_plotAxisLabels(hfig.(planename{plane}).('diff').Axes,ct,plane,slicename{plane},[],100);
- set(get(hfig.(planename{plane}).('diff').Axes, 'title'), 'string', 'Absolute difference');
- matRad_plotAxisLabels(hfig.(planename{plane}).('gamma').Axes,ct,plane,slicename{plane},[],100);
- set(get(hfig.(planename{plane}).('gamma').Axes, 'title'), 'string', {[num2str(gammaPassRate{1,2},5) '% of points > ' num2str(relDoseThreshold) '% pass gamma criterion (' num2str(relDoseThreshold) '% / ' num2str(dist2AgreeMm) 'mm)']; ['with ' num2str(2^n-1) ' interpolation points']});
+ matRad_plotAxisLabels(hfig.(planeName{plane}).('cube1').Axes,ct,plane,sliceName{plane},[],100);
+ set(get(hfig.(planeName{plane}).('cube1').Axes, 'title'), 'string', 'Dose 1');
+ matRad_plotAxisLabels(hfig.(planeName{plane}).('cube2').Axes,ct,plane,sliceName{plane},[],100);
+ set(get(hfig.(planeName{plane}).('cube2').Axes, 'title'), 'string', 'Dose 2');
+ matRad_plotAxisLabels(hfig.(planeName{plane}).('diff').Axes,ct,plane,sliceName{plane},[],100);
+ set(get(hfig.(planeName{plane}).('diff').Axes, 'title'), 'string', 'Absolute difference');
+ matRad_plotAxisLabels(hfig.(planeName{plane}).('gamma').Axes,ct,plane,sliceName{plane},[],100);
+ set(get(hfig.(planeName{plane}).('gamma').Axes, 'title'), 'string', {[num2str(gammaPassRate{1,2},5) '% of points > ' num2str(relDoseThreshold) '% pass gamma criterion (' num2str(relDoseThreshold) '% / ' num2str(dist2AgreeMm) 'mm)']; ['with ' num2str(2^n-1) ' interpolation points']});
end
end
@@ -217,25 +221,25 @@
if enable(2) == 1
matRad_cfg.dispInfo('Plotting profiles...\n');
fontsize = 12;
- profilex{1} = squeeze(cube1(slicename{1},:,slicename{3}));
- profiley{1} = squeeze(cube1(:,slicename{2},slicename{3}));
- profilez{1} = squeeze(cube1(slicename{1},slicename{2},:));
+ profilex{1} = squeeze(cube1(sliceName{1},:,sliceName{3}));
+ profiley{1} = squeeze(cube1(:,sliceName{2},sliceName{3}));
+ profilez{1} = squeeze(cube1(sliceName{1},sliceName{2},:));
- profilex{2} = squeeze(cube2(slicename{1},:,slicename{3}));
- profiley{2} = squeeze(cube2(:,slicename{2},slicename{3}));
- profilez{2} = squeeze(cube2(slicename{1},slicename{2},:));
+ profilex{2} = squeeze(cube2(sliceName{1},:,sliceName{3}));
+ profiley{2} = squeeze(cube2(:,sliceName{2},sliceName{3}));
+ profilez{2} = squeeze(cube2(sliceName{1},sliceName{2},:));
posX = resolution(1)*(1:length(profilex{1}));
posY = resolution(2)*(1:length(profiley{1}));
posZ = resolution(3)*(1:length(profilez{1}));
if centerAtIsocenter
- posX = posX - isoCenter(1);
- posY = posY - isoCenter(2);
- posZ = posZ - isoCenter(3);
+ posX = posX - isoCenterIx(1);
+ posY = posY - isoCenterIx(2);
+ posZ = posZ - isoCenterIx(3);
end
if exist('pln','var') && ~isempty(pln)
- if strcmp(pln.propOpt.bioOptimization,'none')
+ if strcmp(pln.bioParam.quantityVis,'physicalDose')
yLabelString = 'Dose [Gy]';
else
yLabelString = 'RBE x Dose [Gy(RBE)]';
@@ -244,40 +248,18 @@
yLabelString = 'Dose [Gy]';
end
- hfig.profiles.fig = figure('Renderer', 'painters', 'Position', [10 50 800 800]);
- set(gcf,'Color',[1 1 1]);
-
- hfig.profiles.x = subplot(2,2,1);
- plot(posX,profilex{1},'r')
- hold on
- plot(posX,profilex{2},'r--')
- xlabel('X [mm]','FontSize',fontsize)
- ylabel(yLabelString,'FontSize',fontsize);
- title('x-Profiles');
- legend({'Dose 1','Dose 2'},'Location','southeast')
- legend boxoff
-
- hfig.profiles.y = subplot(2,2,2);
- plot(posY,profiley{1},'r')
- hold on
- plot(posY,profiley{2},'r--')
- xlabel('Y [mm]','FontSize',fontsize)
- ylabel(yLabelString,'FontSize',fontsize);
- title('y-Profiles');
- legend({'Dose 1','Dose 2'},'Location','southeast')
- legend boxoff
+ hfig.profiles.fig = figure('Position', [10 50 800 800],'Color',matRad_cfg.gui.backgroundColor);
- hfig.profiles.z = subplot(2,2,3);
- plot(posZ,profilez{1},'r')
- hold on
- plot(posZ,profilez{2},'r--')
- xlabel('Z [mm]','FontSize',fontsize)
- ylabel(yLabelString,'FontSize',fontsize);
- title('z-Profiles');
- legend({'Dose 1','Dose 2'},'Location','southeast')
- legend boxoff
+ hfig.profiles.x = subplot(2,2,1,colorSpec{:});
+ profilePlot(hfig.profiles.x,posX,profilex{1},profilex{2},'x-Profiles','Dose 1','Dose 2', 'x [mm]','dose [Gy]',matRad_cfg.gui);
+
+ hfig.profiles.y = subplot(2,2,2,colorSpec{:});
+ profilePlot(hfig.profiles.y,posY,profiley{1},profiley{2},'y-Profiles','Dose 1','Dose 2', 'x [mm]','dose [Gy]',matRad_cfg.gui);
+
+ hfig.profiles.z = subplot(2,2,3,colorSpec{:});
+ profilePlot(hfig.profiles.z,posZ,profilez{1},profilez{2},'z-Profiles','Dose 1','Dose 2', 'x [mm]','dose [Gy]',matRad_cfg.gui);
- set(hfig.profiles.fig,'name',['Profiles:, x=',num2str(slicename{1}),'mm, y=',num2str(slicename{2}),'mm, z=',num2str(slicename{3}),'mm']);
+ set(hfig.profiles.fig,'name',['Profiles:, x=',num2str(sliceName{1}),'mm, y=',num2str(sliceName{2}),'mm, z=',num2str(sliceName{3}),'mm']);
end
@@ -288,17 +270,28 @@
dvh2 = matRad_calcDVH(cst,cube2);
dvhWindow = max([dvh1(1).doseGrid dvh2(1).doseGrid]);
% Plot DVH
- disp('Plotting DVH...');
+ matRad_cfg.dispInfo('Plotting DVH...');
- hfig.dvh.fig = figure('Renderer', 'painters', 'Position', [10 100 1000 700]);
- set(gcf,'Color',[1 1 1]);
- matRad_showDVH(dvh1,cst,pln);
+ hfig.dvh.fig = figure('Position', [10 100 1000 700],'Color',matRad_cfg.gui.backgroundColor);
+ matRad_showDVH(dvh1,cst,pln,'axesHandle',axes(hfig.dvh.fig,colorSpec{:}));
hold on
- matRad_showDVH(dvh2,cst,pln,2);
+ matRad_showDVH(dvh2,cst,pln,'axesHandle',gca,'LineStyle','--');
xlim([0 dvhWindow*1.2])
- title('Dose Volume Histrogram, Dose 1: solid, Dose 2: dashed')
+ title('Dose Volume Histrogram, Dose 1: solid, Dose 2: dashed','Color',matRad_cfg.gui.highlightColor)
end
%%
matRad_cfg.dispInfo('Done!\n');
+end
+
+function profilePlot(hAx,x,y1,y2,titleTxt,nameProfile1,nameProfile2, xLabelTxt,yLabelTxt,guiSettings)
+ hold(hAx,'on');
+ grid(hAx,'on');
+ grid(hAx,'minor');
+ plot(hAx,x,y1,'r')
+ plot(hAx,x,y2,'r--')
+ xlabel(xLabelTxt,'FontSize',guiSettings.fontSize)
+ ylabel(yLabelTxt,'FontSize',guiSettings.fontSize);
+ title(titleTxt,'Color',guiSettings.highlightColor);
+ legend({nameProfile1,nameProfile2},'Location','best','TextColor',guiSettings.textColor,'Box','off');
end
\ No newline at end of file
diff --git a/tools/matRad_gammaIndex.m b/matRad/planAnalysis/matRad_gammaIndex.m
similarity index 98%
rename from tools/matRad_gammaIndex.m
rename to matRad/planAnalysis/matRad_gammaIndex.m
index cb4a17731..dfcbdbdce 100644
--- a/tools/matRad_gammaIndex.m
+++ b/matRad/planAnalysis/matRad_gammaIndex.m
@@ -42,7 +42,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad_indicatorWrapper.m b/matRad/planAnalysis/matRad_indicatorWrapper.m
similarity index 56%
rename from matRad_indicatorWrapper.m
rename to matRad/planAnalysis/matRad_indicatorWrapper.m
index 0bd00f64e..9e3bf18f2 100644
--- a/matRad_indicatorWrapper.m
+++ b/matRad/planAnalysis/matRad_indicatorWrapper.m
@@ -29,37 +29,42 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-if isfield(resultGUI,'RBExDose')
- doseCube = resultGUI.RBExDose;
-else
- doseCube = resultGUI.physicalDose;
-end
+% Initialize the matRad configuration instance
+matRad_cfg = MatRad_Config.instance();
+% Display a deprecation warning for the current function
+matRad_cfg.dispDeprecationWarning('The matRad_indicatorWrapper function will be deprecated soon!\nPlan analysis is now handled by matRad_planAnalysis!');
-if ~exist('refVol', 'var')
- refVol = [];
+% Initialize an empty cell array for optional arguments to translate the into key-value pairs
+args = {};
+% Check if 'refVol' variable exists and add it to the arguments if it does
+if exist('refVol', 'var')
+ args{end+1,end+2} = {'refVol',refVol};
end
-if ~exist('refGy', 'var')
- refGy = [];
+% Check if 'refGy' variable exists and add it to the arguments if it does
+if exist('refGy', 'var')
+ args{end+1,end+2} = {'refGy',refGy};
end
-dvh = matRad_calcDVH(cst,doseCube,'cum');
-qi = matRad_calcQualityIndicators(cst,pln,doseCube,refGy,refVol);
+% Initialize empty structures for ct and stf, required for matRad_planAnalysis
+ct = struct();
+stf = struct();
+
+% Call the matRad_planAnalysis function with the prepared arguments
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln,args{:});
-figure,set(gcf,'Color',[1 1 1]);
-subplot(2,1,1)
-matRad_showDVH(dvh,cst,pln);
-subplot(2,1,2)
-ixVoi = cellfun(@(c) c.Visible == 1,cst(:,5));
-qi = qi(ixVoi);
-matRad_showQualityIndicators(qi);
+%Get the return arguments
+dvh = resultGUI.dvh;
+qi = resultGUI.qi;
+
+end
diff --git a/matRad/planAnalysis/matRad_sampling.m b/matRad/planAnalysis/matRad_sampling.m
new file mode 100644
index 000000000..b6b45f280
--- /dev/null
+++ b/matRad/planAnalysis/matRad_sampling.m
@@ -0,0 +1,215 @@
+function [caSampRes, mSampDose, pln, resultGUInomScen] = matRad_sampling(ct,stf,cst,pln,w,structSel,multScen)
+% matRad_randomSampling enables sampling multiple treatment scenarios
+%
+% call
+% [cst,pln] = matRad_setPlanUncertainties(ct,cst,pln)
+%
+% input
+% ct: ct cube
+% stf: matRad steering information struct
+% pln: matRad plan meta information struct
+% cst: matRad cst struct
+% w: optional (if no weights available in stf): bixel weight
+% vector
+% output
+% caSampRes: cell array of sampling results depicting plan parameter
+% mSampDose: matrix holding the sampled doses, each row corresponds to
+% one dose sample
+% pln: matRad pln struct containing sampling information
+% resultGUInomScen: resultGUI struct of the nominal scenario
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+% save nonSampling pln for nominal scenario calculation and add dummy fields
+plnNominal = pln;
+% create nominal scenario
+plnNominal.multScen = matRad_multScen(ct,'nomScen');
+
+% check for different ct scenarios
+ctSamp = ct;
+if ct.numOfCtScen > 1
+ matRad_cfg.dispWarning('Sampling for different ct scenarios is not implemented \n');
+ ctSamp.numOfCtScen = 1;
+end
+
+% either use existing multScen struct or create new one
+if exist('multScen','var') && ~isempty(multScen)
+ pln.multScen = multScen;
+else
+ % create random scenarios for sampling
+ pln.multScen = matRad_RandomScenarios(ctSamp);
+ pln.multScen.nSamples = matRad_cfg.defaults.samplingScenarios;
+end
+
+matRad_cfg.dispInfo('Using %d samples in total \n',pln.multScen.totNumScen);
+
+V = [];
+% define voxels for sampling
+if ~exist('structSel', 'var') || sum(size(structSel)) == 0
+ V = [cst{:,4}];
+else
+ for i=1:size(cst,1)
+ for j = 1:numel(structSel)
+ if strcmp(structSel{j}, cst{i,2})
+ V = [V cst{i,4}{1}];
+ end
+ end
+ end
+end
+
+% final voxel subset for sampling
+subIx = unique(vertcat(V{:}));
+
+% disable structures for DVH plotting which are not completely in subIx
+for i = 1:size(cst,1)
+ if ~all(ismember(cst{i,4}{1}, subIx))
+ cst{i,5}.Visible = false;
+ end
+end
+
+% define variable for storing scenario doses
+mSampDose = single(zeros(numel(subIx),pln.multScen.totNumScen,1));
+StorageInfo = whos('mSampDose');
+matRad_cfg.dispInfo('matRad: Realizations variable will need: %f GB \n',StorageInfo.bytes/1e9);
+
+% check if parallel toolbox is installed and license can be checked out
+try
+ [FlagParallToolBoxLicensed,msg] = license('checkout','Distrib_Computing_Toolbox');
+ if ~FlagParallToolBoxLicensed
+ matRad_cfg.dispWarning('Could not check out parallel computing toolbox. \n');
+ end
+ % Create parallel pool on cluster
+ p = gcp(); % If no pool, create new one.
+
+ if isempty(p)
+ matRad_cfg.dispError('matRad: Could not start valid parallel pool. Please check your parallel computing toolbox installation. \n');
+ end
+catch
+ FlagParallToolBoxLicensed = false;
+end
+
+%% calculate nominal scenario
+nomScenTimer = tic;
+resultGUInomScen = matRad_calcDoseDirect(ct,stf,plnNominal,cst,w);
+nomScenTime = toc(nomScenTimer);
+matRad_cfg.dispInfo('Finished nominal Scenario Calculation. Computation time: %f h \n',round(nomScenTime / 3600));
+
+refVol = [2 5 50 95 98];
+refGy = linspace(0,max(resultGUInomScen.(pln.bioParam.quantityVis)(:)),6);
+
+resultGUInomScen.dvh = matRad_calcDVH(cst,resultGUInomScen.(pln.bioParam.quantityVis),'cum');
+dvhPoints = resultGUInomScen.dvh(1).doseGrid;
+nomQi = matRad_calcQualityIndicators(cst,pln,resultGUInomScen.(pln.bioParam.quantityVis),refGy,refVol);
+
+resultGUInomScen.qi = nomQi;
+resultGUInomScen.cst = cst;
+
+%% perform parallel sampling
+if FlagParallToolBoxLicensed
+
+ if isempty(p)
+ poolSize = 1;
+ else
+ poolSize = p.NumWorkers;
+ end
+
+ %TODO: find a way to manage matRad_Config on a parallel pool
+ logLevel = matRad_cfg.logLevel;
+
+ % rough estimate of total computation time
+ totCompTime = ceil(size(pln.multScen.scenForProb,1) / poolSize) * nomScenTime * 1.35;
+ matRad_cfg.dispInfo(['Approximate Total calculation time: ', num2str(round(totCompTime / 3600)), ...
+ 'h. Estimated finish: ', datestr(datetime('now') + seconds(totCompTime)), '\n']);
+
+ if exist('parfor_progress', 'file') == 2 & logLevel > 2
+ FlagParforProgressDisp = true;
+ parfor_progress(pln.multScen.totNumScen); % http://de.mathworks.com/matlabcentral/fileexchange/32101-progress-monitor--progress-bar--that-works-with-parfor
+ else
+ matRad_cfg.dispInfo('matRad: Consider downloading parfor_progress function from the matlab central fileexchange to get feedback from parfor loop.\n');
+ FlagParforProgressDisp = false;
+ end
+
+
+
+ parfor i = 1:pln.multScen.totNumScen
+ %TODO: find a way to manage matRad_Config on a parallel pool more easily
+ matRad_cfg_worker = MatRad_Config.instance();
+ matRad_cfg_worker.logLevel = logLevel;
+
+ % create nominal scenario
+ plnSamp = pln;
+ plnSamp.multScen = pln.multScen.extractSingleScenario(i);
+
+ resultSamp = matRad_calcDoseForward(ct,cst,stf,plnSamp,w);
+ sampledDose = resultSamp.(pln.bioParam.quantityVis)(subIx);
+ mSampDose(:,i) = single(reshape(sampledDose,[],1));
+ caSampRes(i).bioParam = pln.bioParam;
+ caSampRes(i).relRangeShift = plnSamp.multScen.relRangeShift;
+ caSampRes(i).absRangeShift = plnSamp.multScen.absRangeShift;
+ caSampRes(i).isoShift = plnSamp.multScen.isoShift;
+
+ caSampRes(i).dvh = matRad_calcDVH(cst,resultSamp.(pln.bioParam.quantityVis),'cum',dvhPoints);
+ caSampRes(i).qi = matRad_calcQualityIndicators(cst,pln,resultSamp.(pln.bioParam.quantityVis),refGy,refVol);
+
+ if FlagParforProgressDisp & logLevel > 2
+ parfor_progress;
+ end
+ end
+
+ if FlagParforProgressDisp & logLevel > 2
+ parfor_progress(0);
+ end
+
+else
+ %% perform seriel sampling
+ % rough estimate of total computation time
+ totCompTime = size(pln.multScen.scenForProb,1) * nomScenTime * 1.1;
+ try
+ matRad_cfg.dispInfo(['Approximate Total calculation time: ', num2str(round(totCompTime / 3600)), ...
+ 'h. Estimated finish: ', datestr(datetime('now') + seconds(totCompTime)), '\n']);
+ catch
+ matRad_cfg.dispInfo(['Approximate Total calculation time: ', num2str(round(totCompTime / 3600)), '\n']);
+ end
+
+ for i = 1:pln.multScen.totNumScen
+
+ % create nominal scenario
+ plnSamp = pln;
+ plnSamp.multScen = pln.multScen.extractSingleScenario(i);
+
+ resultSamp = matRad_calcDoseDirect(ct,stf,plnSamp,cst,w);
+ sampledDose = resultSamp.(pln.bioParam.quantityVis)(subIx);
+ mSampDose(:,i) = single(reshape(sampledDose,[],1));
+ caSampRes(i).bioParam = pln.bioParam;
+ caSampRes(i).relRangeShift = plnSamp.multScen.relRangeShift;
+ caSampRes(i).absRangeShift = plnSamp.multScen.absRangeShift;
+ caSampRes(i).isoShift = plnSamp.multScen.isoShift;
+
+ caSampRes(i).dvh = matRad_calcDVH(cst,resultSamp.(pln.bioParam.quantityVis),'cum',dvhPoints);
+ caSampRes(i).qi = matRad_calcQualityIndicators(cst,pln,resultSamp.(pln.bioParam.quantityVis),refGy,refVol);
+
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(i, pln.multScen.totNumScen);
+ end
+ end
+end
+
+%% add subindices
+pln.subIx = subIx;
+
+end
diff --git a/matRad/planAnalysis/matRad_showDVH.m b/matRad/planAnalysis/matRad_showDVH.m
new file mode 100644
index 000000000..5b62398b8
--- /dev/null
+++ b/matRad/planAnalysis/matRad_showDVH.m
@@ -0,0 +1,168 @@
+function matRad_showDVH(dvh,cst,varargin)
+% matRad dvh visualizaion
+%
+% call
+% matRad_showDVH(dvh,cst)
+% matRad_showDVH(dvh,cst,pln)
+% matRad_showDVH(dvh,cst,Name,Value)
+% matRad_showDVH(dvh,cst,pln,Name,Value)
+%
+% input
+% dvh: result struct from fluence optimization/sequencing
+% cst: matRad cst struct
+% pln: (now optional) matRad pln struct,
+% standard uses Dose [Gy]
+% lineStyleIndicator: (optional) integer (1,2,3,4) to indicate the current linestyle
+% (hint: use different lineStyles to overlay
+% different dvhs)
+%
+% output
+% graphical display of DVH
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+%% Parse input
+p = inputParser;
+
+p.addRequired('dvh',@isstruct);
+p.addRequired('cst',@iscell);
+p.addOptional('pln',[],@(x) isstruct(x) || isempty(x));
+p.addParameter('axesHandle',[],@isgraphics);
+p.addParameter('plotLegend',true,@(x) isscalar(x) && islogical(x));
+%p.addParameter('plotObjectives',false,@(x) isscalar(x) && islogical(x));
+p.addParameter('LineWidth',2.5,@(x) isscalar(x) && x > 0);
+p.CaseSensitive = false;
+p.KeepUnmatched = true;
+
+p.parse(dvh,cst,varargin{:});
+
+axesHandle = p.Results.axesHandle;
+dvh = p.Results.dvh;
+cst = p.Results.cst;
+pln = p.Results.pln;
+
+plotLegend = p.Results.plotLegend;
+%plotObjectives = p.Results.plotObjectives;
+
+%{
+if plotObjectives && isempty(pln)
+ matRad_cfg.dispWarning('Plotting objectives requries pln struct! Disabling.');
+ plotObjectives = false;
+end
+%}
+
+lineWidth = p.Results.LineWidth;
+
+%Get unmatched arguments
+fields = fieldnames(p.Unmatched);
+values = struct2cell(p.Unmatched);
+unmatchedPlotArguments = [fields';values'];
+
+
+%% Preprocessing
+if isempty(axesHandle)
+ axesHandle = axes(...
+ figure('Color',matRad_cfg.gui.backgroundColor),...
+ 'Color',matRad_cfg.gui.elementColor,...
+ 'XColor',matRad_cfg.gui.textColor,...
+ 'YColor',matRad_cfg.gui.textColor,...
+ 'GridColor',matRad_cfg.gui.textColor,...
+ 'MinorGridColor',matRad_cfg.gui.backgroundColor);
+end
+
+%Hold for plotting multiple DVHs
+hold(axesHandle,'on');
+
+hFig = get(axesHandle,'Parent');
+h = findobj(hFig,'type','legend');
+
+if ~isempty(h)
+ if ~plotLegend
+ h.AutoUpdate = false;
+ else
+ h.AutoUpdate = true;
+ end
+end
+
+%reduce cst
+visibleIx = cellfun(@(c) c.Visible == 1,cst(:,5));
+cstNames = cst(visibleIx,2);
+cstInfo = cst(visibleIx,5);
+%cstObjectives = cst(visibleIx,6);
+dvh = dvh(visibleIx);
+
+numOfVois = numel(cstNames);
+
+%try to get colors from cst
+try
+ colorMx = cellfun(@(c) c.visibleColor,cstInfo,'UniformOutput',false);
+ colorMx = cell2mat(colorMx);
+catch
+ colorMx = colorcube;
+ colorMx = colorMx(1:floor(64/numOfVois):64,:);
+end
+
+maxDVHvol = 0;
+maxDVHdose = 0;
+
+%% print the dvhs
+for i = 1:numOfVois
+ % cut off at the first zero value where there is no more signal
+ % behind
+ ix = max([1 find(dvh(i).volumePoints>0,1,'last')]);
+ currDvh = [dvh(i).doseGrid(1:ix);dvh(i).volumePoints(1:ix)];
+
+ if plotLegend
+ plot(axesHandle,currDvh(1,:),currDvh(2,:),'LineWidth',lineWidth,'Color',colorMx(i,:),'DisplayName',cstNames{i},unmatchedPlotArguments{:});
+ else
+ plot(axesHandle,currDvh(1,:),currDvh(2,:),'LineWidth',lineWidth,'Color',colorMx(i,:),unmatchedPlotArguments{:});
+ end
+
+ maxDVHvol = max(maxDVHvol,max(currDvh(2,:)));
+ maxDVHdose = max(maxDVHdose,max(currDvh(1,:)));
+end
+
+
+%% Legend and limits
+fontSizeValue = matRad_cfg.gui.fontSize;
+if plotLegend
+ myLegend = legend(axesHandle,'location','NorthEast','FontSize',matRad_cfg.gui.fontSize,'TextColor',matRad_cfg.gui.textColor,'Interpreter','none');
+ legend(axesHandle,'boxoff');
+ legend(axesHandle,'show');
+end
+
+ylim(axesHandle,[0 1.1*maxDVHvol]);
+xlim(axesHandle,[0 1.2*maxDVHdose]);
+
+grid(axesHandle,'on'),grid(axesHandle,'minor')
+box(axesHandle,'on'); %box(gca,'on');
+set(axesHandle,'LineWidth',1.5,'FontSize',fontSizeValue); %set(gca,'LineWidth',1.5,'FontSize',fontSizeValue);
+ylabel(axesHandle,'Volume [%]','FontSize',fontSizeValue)
+
+if ~isempty(pln)
+
+ if strcmp(pln.bioParam.model,'none')
+ xlabel('Dose [Gy]','FontSize',fontSizeValue);
+ else
+ xlabel(axesHandle,'RBE x Dose [Gy(RBE)]','FontSize',fontSizeValue);
+ end
+else
+ xlabel('Dose [Gy]','FontSize',fontSizeValue);
+end
+hold(axesHandle,'off');
diff --git a/matRad/planAnalysis/matRad_showQualityIndicators.m b/matRad/planAnalysis/matRad_showQualityIndicators.m
new file mode 100644
index 000000000..6ccdf296f
--- /dev/null
+++ b/matRad/planAnalysis/matRad_showQualityIndicators.m
@@ -0,0 +1,119 @@
+function matRad_showQualityIndicators(figHandle,qi)
+% matRad display of quality indicators as table
+%
+% call
+% matRad_showQualityIndicators(qi)
+%
+% input
+% figHandle: handle to figure to display the Quality Indicators in
+% qi: result struct from matRad_calcQualityIndicators
+%
+% output
+% graphical display of quality indicators in table form
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+[env, vStr] = matRad_getEnvironment();
+
+% Create the column and row names in cell arrays
+rnames = {qi.name};
+qi = rmfield(qi,'name');
+cnames = fieldnames(qi);
+for i = 1:numel(cnames)
+ ix = find(cnames{i}(4:end) == '_');
+ if ~isempty(ix)
+ cnames{i}(ix+3) = '.';
+ end
+end
+
+%To avoid parse error in octave, replace empty qi values with '-'
+qi = (squeeze(struct2cell(qi)))';
+qiEmpty = cellfun(@isempty,qi);
+qi(qiEmpty) = {'-'};
+
+%Layout depending on axes type
+
+hType = get(figHandle,'Type');
+
+if ~strcmp(hType,'figure')
+ pos = get(figHandle,'position');
+ if strcmp(hType,'axes')
+ axis(figHandle,'off');
+ end
+ hF = ancestor(figHandle,'figure');
+else
+ pos = [0 0 1 1];
+ hF = figHandle;
+end
+
+%since uitable is only available in newer octave versions, we try and catch
+try
+ colorMatrix = repmat(matRad_cfg.gui.elementColor,numel(rnames),1);
+ ix2 = 2:2:numel(rnames);
+ if ~isempty(ix2)
+ shadeColor = rgb2hsv(matRad_cfg.gui.elementColor);
+ if shadeColor(3) < 0.5
+ shadeColor(3) = shadeColor(3)*1.5+0.1;
+ else
+ shadeColor(3) = shadeColor(3)*0.5-0.1;
+ end
+
+ colorMatrix(ix2,:) = repmat(hsv2rgb(shadeColor),numel(ix2),1);
+ end
+
+
+
+ % Create the uitable
+ table = uitable(hF,'Data',qi,...
+ 'ColumnName',cnames,...
+ 'RowName',rnames,'ColumnWidth',{70},...
+ 'units','normalized',...
+ 'position',pos, ...
+ 'ForegroundColor',matRad_cfg.gui.textColor,...
+ 'BackgroundColor',colorMatrix,...
+ 'RowStriping','on');
+
+ %Try to adapt the position of the table
+ try
+ ext = get(table,'Extent');
+
+ pixPosTableBefore = getpixelposition(table);
+
+ relScrollSize = 16./pixPosTableBefore([3 4]);
+
+ posOld = pos;
+
+ if ext(3) < pos(3)
+ pos(3) = ext(3) + relScrollSize(1);
+ pos(1) = posOld(3) - pos(3);
+ end
+
+ if ext(4) < pos(4)
+ pos(4) = ext(4) + relScrollSize(2);
+ pos(2) = posOld(4) - pos(4);
+ end
+
+ set(table,'Position',pos);
+
+
+
+ catch
+ end
+catch ME
+ matRad_cfg.dispWarning('The uitable function is not implemented in %s v%s.',env,vStr);
+end
diff --git a/matRad/planAnalysis/samplingAnalysis/README.txt b/matRad/planAnalysis/samplingAnalysis/README.txt
new file mode 100644
index 000000000..84b4066bd
--- /dev/null
+++ b/matRad/planAnalysis/samplingAnalysis/README.txt
@@ -0,0 +1,9 @@
+In order to generate an automated uncertainty sampling report, do the following steps:
+
+1. create empty folder to save your study
+2. copy tools/samplingAnalysis/setupStudy_template.m (rename)
+3. copy the already imported patient as a *.mat file into the directory
+4. use appropriate parameters for uncertainty sampling
+5. start matlab, add matRad root folder to the search path and run setupStudy.m
+
+after succesful calculation, a pdf report will be generated and saved (studyPath/report/main.pdf)
diff --git a/matRad/planAnalysis/samplingAnalysis/main_template.tex b/matRad/planAnalysis/samplingAnalysis/main_template.tex
new file mode 100644
index 000000000..e81984c58
--- /dev/null
+++ b/matRad/planAnalysis/samplingAnalysis/main_template.tex
@@ -0,0 +1,282 @@
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad uncertainty analysis report latex template
+%
+% call
+% e.g. 'xelatex main.tex' in console
+% output
+% pdf report
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\RequirePackage{luatex85,shellesc}
+\documentclass[a4paper]{scrartcl}
+\usepackage[english]{babel}
+\usepackage[utf8]{inputenc}
+\usepackage{amsmath}
+\usepackage{graphicx}
+\usepackage[autoplay,loop]{animate}
+\usepackage[verbose]{placeins}
+\usepackage{fullpage}
+\usepackage{ifthen}
+\usepackage{caption}
+\usepackage{subcaption}
+\usepackage[colorinlistoftodos]{todonotes}
+\addtokomafont{disposition}{\rmfamily}
+
+% pgfplots for matlab2tikz
+\usepackage{pgfplots}
+\pgfplotsset{compat=newest}
+%% the following commands are needed for some matlab2tikz features
+\usetikzlibrary{plotmarks}
+\usetikzlibrary{arrows.meta}
+\usepgfplotslibrary{patchplots}
+\usepackage{grffile}
+\usepackage{amsmath}
+\usepackage{booktabs}
+\usepackage{color}
+\usepackage{fancyhdr}
+\usepackage{footnote}
+\usepackage[hidelinks]{hyperref} % clickable table of content
+\makesavenoteenv{figure}
+
+\usetikzlibrary{external}
+\tikzexternalize[prefix=data/figures/]
+
+\newlength\figW
+\setlength{\figW}{0.9\textwidth}
+\newlength\figH
+\setlength{\figH}{0.56\textwidth} % 0.9 / golden ratio
+
+\usepackage[babel,english=british]{csquotes}
+
+
+\begin{document}
+\input{data/parameters.tex}
+\input{data/patientInformation.tex}
+
+\title{Uncertainty Sampling Report}
+\subtitle{\patientLastName{}, \patientFirstName{}, \patientID\\ \textcolor{red}{\warnMessage}}
+\author{\operator}
+% \date{\today}
+\maketitle
+\IfFileExists{data/frames/anim_1.png}{
+ \begin{figure}[!b]
+ \centering
+ \animategraphics[controls,width = \textwidth]{\framerate}{data/frames/anim_}{\firstframe}{\lastframe}
+ \end{figure}
+ }{No animation available.}
+
+\newpage
+\tableofcontents
+
+\newpage
+\section{Patient Information}
+\label{sec:patientInformation}
+
+\begin{table}[h]
+\centering
+%\caption{My caption}
+\label{table:patientInformation}
+\begin{tabular}{rl}
+\toprule
+Patient's name & \patientLastName{}, \patientFirstName \\
+Patient ID & \patientID \\
+Patient's sex & \patientSex \\
+\bottomrule
+\end{tabular}
+\end{table}
+
+\section{Plan Overview}
+\label{sex:planOverview}
+
+\subsection{Setup}
+
+\begin{table}[h]
+ \centering
+ %\caption{My caption}
+ \label{table:planInformation}
+ \begin{tabular}{rl}
+ \toprule
+ Gantry angles & \planGantryAngles \\
+ Couch angles & \planCouchAngles \\
+ Radiation modalitiy & \planRadiationModality \\
+ \bottomrule
+ \end{tabular}
+\end{table}
+
+\subsection{Parameter for Uncertainty Analysis}
+\input{data/uncertaintyParameters.tex}
+The following parameters were used as scenarios, set by the user \operator.
+\begin{table}[!h]
+ \centering
+ %\caption{My caption}
+ \label{table:userParameters}
+ \begin{tabular}{rl}
+ \toprule
+ Number of shifts & \numOfShiftScen \\
+ Shift size & \shiftSize \\
+ Shift combination & \shiftCombType \\
+ Number of range shifts & \numOfRangeShiftScen \\
+ Max abs. range Shift & \maxAbsRangeShift \\
+ Max rel. range Shift & \maxRelRangeShift \\
+ Range shift combination & \rangeCombType \\
+ Total scen combination & \scenCombType \\
+ Computation time & \computationTime \\
+ \bottomrule
+ \end{tabular}
+\end{table}
+The following uncertainties were incorporated in this analysis.
+\begin{table}[!h]
+ \centering
+ %\caption{My caption}
+ \label{table:typeUncertainty}
+ \begin{tabular}{rl}
+ \toprule
+ Ct uncertainties & \ctScen \\
+ Shift uncertainties & \shiftScen \\
+ Range uncertainties & \rangeScen \\
+ \bottomrule
+ \end{tabular}
+\end{table}
+Assuming the uncertainties above to be distributed by a normally around 0 with standard deviaton
+\begin{table}[!h]
+ \centering
+ %\caption{My caption}
+ \label{table:uncertaintySD}
+ \begin{tabular}{rl}
+ \toprule
+ $\sigma_{shift} $ & \shiftSD \ mm\\
+ $\sigma_{relRange} $ & \rangeRelSD \ \% \\
+ $\sigma_{absRange} $ & \rangeAbsSD \ mm \\
+ \bottomrule
+ \end{tabular}
+\end{table}
+
+\subsection{Explenatory Information}
+In section \ref{sec:usa} common quality indictors are used to examine their behaviour under the influence of uncertainties.
+The quality indicator can also be found in the table (top row, column-wise).
+Metrics (i.e. percentiles and confidence values) are applied on an individual quality indicators of the scenarios and can be found in the first column (row-wise).
+
+\subsubsection{Statistics}
+All types of uncertainties are assumed to be uncorrelated, i.e. range uncertainties are not correlated to shift uncertainties.
+Shift uncertainties are assumed to be ineherently uncorrelated as well, i.e. no correlation between shifts in x-, y- or z direction.
+Therefore the probability assigned to a scenario $i$ is the product of the probabilities of the individual components $j$, i.e.
+\begin{equation}
+ p_i = \prod\limits_j p_j.
+\end{equation}
+
+\paragraph{Mean} $\mu$ on scenarios $X_i$ is weighted by the probabilities $p_i$:
+\begin{equation}
+ \mu = \frac{\sum\limits_{i} p_i X_i}{\sum\limits_{i} p_i}
+\end{equation}
+
+\paragraph{Variance} is analogously defined as:
+\begin{equation}
+ \sigma^2 = \frac{\sum\limits_{i} p_{i}\left(x_{i}-\mu\right)^{2}}{\sum\limits _{i}p_{i}}
+\end{equation}
+
+\subsubsection{Parameters}
+The complete explaination of parameters can be found next to the usage in every \textit{setupStudy.m}.
+\paragraph{shiftGenIsotropy} is set by default to \textit{+-}, which means that shifts go from negative to positive direction.
+One can also choose to only use positive or negative shifts.
+\paragraph{Combination Type} is a paremeter which is used for shift scenarios, range scenarios and the total combination of both (i.e. shiftCombType, rangeCombType, scenCombType).
+It can be set to \textit{individual}, where one type is computed after the other, \textit{combined} combines both types directly and \textit{permuted} is only available for shifts, where every possible permutation of the given x-, y- and z-shifts is being sampled.
+
+
+
+
+
+
+\FloatBarrier
+\newpage
+
+\section{Nominal Plan}
+\subsection{Slices at isoCentre}
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane3_nominal.tex}
+ \caption{CT and nominal dose at isocentre. Axial view.}
+\end{figure}
+
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane3_stdW.tex}
+ \caption{CT and standard deviation of dose at isocentre. Axial view.}
+\end{figure}
+
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane3_gamma.tex}
+ \caption{CT and $\gamma$ index of nominal and mean dose (\gammaDoseAgreement \%, \gammaDoseAgreement mm, global). Axial view.}
+\end{figure}
+
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane1_nominal.tex}
+ \caption{CT and nominal dose at isocentre. Coronal view.}
+\end{figure}
+
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane1_stdW.tex}
+ \caption{CT and standard deviation of dose at isocentre. Coronal view.}
+\end{figure}
+
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane1_gamma.tex}
+ \caption{CT and $\gamma$ index of nominal and mean dose (\gammaDoseAgreement \%, \gammaDoseAgreement mm, global). Coronal view.}
+\end{figure}
+
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane2_nominal.tex}
+ \caption{CT and nominal dose at isocentre. Sagittal view.}
+\end{figure}
+
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane2_stdW.tex}
+ \caption{CT and standard deviation of dose at isocentre. Sagittal view.}
+\end{figure}
+
+\begin{figure}[!b]
+ \centering
+ \input{data/figures/isoSlicePlane2_gamma.tex}
+ \caption{CT and $\gamma$ index of nominal and mean dose (\gammaDoseAgreement \%, \gammaDoseAgreement mm, global). Sagittal view.}
+\end{figure}
+
+
+\FloatBarrier
+
+\newpage
+\subsection{DVH and Quality Indicator}
+\begin{center}
+\input{data/figures/nominalDVH.tex}
+\end{center}
+\input{data/nominalQI.tex}
+
+\FloatBarrier
+\newpage
+\section{Uncertainty Scenario Analysis}
+\label{sec:usa}
+
+\input{data/structureWrapper.tex}
+
+
+
+
+\end{document}
diff --git a/matRad/planAnalysis/samplingAnalysis/matRad_calcStudy.m b/matRad/planAnalysis/samplingAnalysis/matRad_calcStudy.m
new file mode 100644
index 000000000..ae1614708
--- /dev/null
+++ b/matRad/planAnalysis/samplingAnalysis/matRad_calcStudy.m
@@ -0,0 +1,136 @@
+function matRad_calcStudy(multScen,varargin)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad uncertainty study wrapper
+%
+% call
+% matRad_calcStudy(structSel,multScen,matPatientPath,param)
+%
+% input
+% structSel: structures which should be examined (can be empty,
+% to examine all structures) cube
+% multScen: parameterset of uncertainty analysis
+% matPatientPath: (optional) absolut path to patient mat file. If
+% empty mat file in current folder will be used
+% param: structure defining additional parameter
+% outputPath
+% output
+% (binary) all results are saved; a pdf report will be generated
+% and saved
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+p = inputParser;
+p.addRequired('multScen',@(x) isa(x,'matRad_multScen'));
+p.addParameter('SelectStructures',cell(0),@iscellstr);
+p.addParameter('OutputPath',matRad_cfg.userfolder{1},@isfolder);
+p.addParameter('PatientMatFile','',@isfile);
+p.addParameter('ListOfQI',{'mean', 'std', 'max', 'min', 'D_2', 'D_5', 'D_50', 'D_95', 'D_98'},@iscellstr);
+p.addParameter('OperatorName','matRad User',@(x) isstring(x) || ischar(x));
+
+%
+
+p.parse(multScen,varargin{:});
+multScen = p.Results.multScen;
+outputPath = p.Results.OutputPath;
+structSel = p.Results.SelectStructures;
+matPatientPath = p.Results.PatientMatFile;
+listOfQI = p.Results.ListOfQI;
+operator = p.Results.OperatorName;
+
+
+% require minimum number of scenarios to ensure proper statistics
+if multScen.numOfRangeShiftScen + sum(multScen.numOfShiftScen) < 20
+ matRad_cfg.dispWarning('Detected a low number of scenarios. Proceeding is not recommended.');
+ sufficientStatistics = false;
+ pause(1);
+else
+ sufficientStatistics = true;
+end
+
+%% load DICOM imported patient or run from workspace
+if exist('matPatientPath', 'var') && ~isempty(matPatientPath) && exist('matPatientPath','file') == 2
+ load(matPatientPath);
+else
+ try
+ ct = evalin('base','ct');
+ cst = evalin('base','cst');
+ stf = evalin('base','stf');
+ pln = evalin('base','pln');
+ resultGUI = evalin('base','resultGUI');
+ catch
+ matRad_cfg.dispError('Workspace for sampling is incomplete.');
+ end
+end
+
+% check if nominal workspace is complete
+if ~(exist('ct','var') && exist('cst','var') && exist('stf','var') && exist('pln','var') && exist('resultGUI','var'))
+ matRad_cfg.dispError('Workspace for sampling is incomplete.');
+end
+
+% calculate RBExDose
+if ~isfield(pln, 'bioParam')
+ if strcmp(pln.radiationMode, 'protons')
+ pln.bioOptimization = 'RBExD';
+ pln.model = 'constRBE';
+ elseif strcmp(pln.radiationMode, 'carbon')
+ pln.bioOptimization = 'RBExD';
+ pln.model = 'LEM';
+ end
+ pln.bioParam = matRad_bioModel(pln.radiationMode, pln.bioOptimization, pln.model);
+end
+
+
+pln.robOpt = false;
+pln.sampling = true;
+
+%% perform calculation and save
+tic
+[caSampRes, mSampDose, pln, resultGUInomScen] = matRad_sampling(ct,stf,cst,pln,resultGUI.w,structSel,multScen);
+computationTime = toc;
+
+filename = 'resultSampling';
+save(filename, '-v7.3');
+
+%% perform analysis
+% start here loading resultSampling.mat if something went wrong during analysis or report generation
+[structureStat, doseStat, meta] = matRad_samplingAnalysis(ct,cst,pln,caSampRes,mSampDose,resultGUInomScen);
+
+%% generate report
+reportPath = 'report';
+
+mkdir([outputPath filesep reportPath]);
+
+copyfile(fullfile(matRad_cfg.matRadSrcRoot,'tools','samplingAnalysis','main_template.tex'),fullfile(outputPath,reportPath,'main.tex'));
+
+% generate actual latex report
+success = matRad_latexReport([outputPath filesep reportPath],ct, cst, pln, resultGUInomScen, structureStat, doseStat, mSampDose, listOfQI,...
+ 'ComputationTime',computationTime,...
+ 'SufficientStatistics',sufficientStatistics,...
+ 'OperatorName',operator);
+
+
+if success
+ open(fullfile([outputPath filesep reportPath],'main.pdf'));
+
+else
+ matRad_cfg.dispError('Report PDF can not be opened...');
+end
+
+
+
+
+
diff --git a/matRad/planAnalysis/samplingAnalysis/matRad_createAnimationForLatexReport.m b/matRad/planAnalysis/samplingAnalysis/matRad_createAnimationForLatexReport.m
new file mode 100644
index 000000000..050b2632f
--- /dev/null
+++ b/matRad/planAnalysis/samplingAnalysis/matRad_createAnimationForLatexReport.m
@@ -0,0 +1,116 @@
+function matRad_createAnimationForLatexReport(confidenceValue, ct, cst, slice, meanCube, mRealizations, scenProb, subIx, outpath, legendColorbar,varargin)
+% matRad function to create figures for a GIF animation
+%
+% call
+% matRad_createAnimationForLatexReport(confidenceValue, ct, cst, slice, ...
+% meanCube, mRealizations, scenProb, subIx, outpath, legendColorbar)
+%
+% input
+% confidenceValue confidence used for visualization
+% ct matRad ct struct
+% cst matRad cst struct
+% slice slice of the ct used for visualization
+% meanCube cube holding the mean dose
+% mRealzations samples
+% scenProb linear vector of all probabilities for the individual
+% scenarios
+% subIx voxel indices that are considered during analysis
+% outpath output path for files
+% legendColorbar colorbar used for the legend
+%
+% Additional Name Value Pairs:
+% PrescribedDose prescription (per fraction)
+% FramesPerSecond frames per second for the animation (default 24)
+% Period total period [s] for the animation (default 5)
+% FilePrefix default 'anim'
+%
+% output
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+p = inputParser;
+
+p.addParameter('PrescribedDose',[],@(x) isscalar(x) && isnumeric(x));
+p.addParameter('FramesPerSecond',24,@(x) isscalar(x) && isnumeric(x));
+p.addParameter('Period',5,@(x) isscalar(x) && isnumeric(x));
+p.addParameter('FilePrefix','anim',@ischar);
+
+p.parse(varargin{:});
+
+fps = p.Results.FramesPerSecond;
+period = p.Results.Period;
+dPres = p.Results.PrescribedDose;
+
+ctDim = size(meanCube);
+doseSlice = meanCube(:,:,slice);
+doseSliceIx = find(doseSlice) + (slice-1)*prod(ctDim(1:2));
+
+% check whether expDoseSliceIx is subset of subIx
+assert(all(ismember(doseSliceIx, subIx)))
+assert(issorted(doseSliceIx) && issorted(subIx))
+
+if isempty(dPres)
+ dPres = 0.95 * max(doseSlice(:));
+end
+
+[~,idxIntoSubIx] = intersect(subIx, doseSliceIx);
+mRealizationsSub = mRealizations(idxIntoSubIx,:);
+
+selectIx = doseSliceIx;
+
+scenProbs = unique(scenProb(:));
+
+if length(scenProbs) == 1 %All scenarios equally probable
+ wCovMat = cov(mRealizationsSub');
+ wCovMat = scenProbs.^2 * wCovMat; %TODO: validate
+else %weighted covariance
+ [nVoxels,nSamples] = size(mRealizationsSub);
+ wCovMat = mRealizationsSub' - repmat(scenProb' * mRealizationsSub',nSamples,1);
+ wCovMat = wCovMat' * (wCovMat .* repmat(scenProb,1,nVoxels));
+ wCovMat = 0.5 * (wCovMat + wCovMat');
+end
+
+mu = meanCube(selectIx);
+
+%Compute starting vector from confidence value
+xr = sqrt(gammaincinv(confidenceValue,numel(mu)/2)*2);
+
+
+fname = 'anim';
+nFrames = period*fps;
+samples = matRad_getGaussianOrbitSamples(mu,wCovMat,nFrames,xr);
+samplesMax = max(samples(:));
+hfAnim = figure('units','normalized','outerposition',[0 0 0.5 0.5]);
+%outfile = [fname '.gif'];
+alpha = 0.5;
+close
+figure
+set(gcf,'color','w');
+for f=1:nFrames
+ sampleCube = zeros(size(meanCube));
+ sampleCube(selectIx) = samples(:,f);
+ matRad_plotSliceWrapper(gca,ct,cst,1,sampleCube,3,slice,0,alpha,colorcube,jet,[0.01*dPres dPres*1.3],[0.1 0.25 0.6 0.9 0.95 1 1.05 1.25]'*dPres,[],legendColorbar,false);%,figXzoom,[figYzoom]);
+ F(f) = getframe(gcf);
+ im = frame2im(F(f));
+ [imind,cm] = rgb2ind(im,256);
+
+ outfile = fullfile(outpath,[fname '_' num2str(f) '.png']);
+ imwrite(imind,cm,outfile,'png');
+ delete(gca);
+end
+close
+end
diff --git a/matRad/planAnalysis/samplingAnalysis/matRad_getGaussianOrbitSamples.m b/matRad/planAnalysis/samplingAnalysis/matRad_getGaussianOrbitSamples.m
new file mode 100644
index 000000000..f4284b523
--- /dev/null
+++ b/matRad/planAnalysis/samplingAnalysis/matRad_getGaussianOrbitSamples.m
@@ -0,0 +1,166 @@
+function samples = matRad_getGaussianOrbitSamples(mu,SIGMA,nFrames,varargin)
+% matRad orbit sampling
+%
+% call
+% samples = matRad_getGaussianOrbitSamples(mu,SIGMA,nFrames)
+% samples = matRad_getGaussianOrbitSamples(mu,SIGMA,nFrames,xr)
+% samples = matRad_getGaussianOrbitSamples(___,Name,Value)
+%
+% input
+% mu mean vector
+% SIGMA covariance matrix
+% nFrames number of sample frames
+% xr (optional) if scalar, a radius for the sample. if vector, a starting coordiante
+%
+% Optional Name-Value-Pairs:
+% Method: can be either 'chol' (default, fast) or 'eig'
+% (more stable but slow)
+% MaximumTriesChol: number of maxim tries to make matrix PSD for
+% cholesky decomposition. Default is 10
+%
+%
+% output
+%
+% References
+% [1] http://mlss.tuebingen.mpg.de/2013/Hennig_2013_Animating_Samples_from_Gaussian_Distributions.pdf
+% [2] http://www.sciencedirect.com/science/article/pii/0024379588902236
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+
+p = inputParser;
+
+p.addRequired('mu',@(x) isvector(x) && isnumeric(x));
+p.addRequired('SIGMA',@(x) size(x,1) == size(x,2) && isnumeric(x));
+p.addRequired('nFrames',@(x) isscalar(x) && isnumeric(x));
+p.addOptional('xr',[],@(x) isnumeric(x) && isvector(x));
+p.addParameter('Method','chol',@(x) validatestring(x,{'chol'},{'eig'}));
+p.addParameter('MaximumTriesChol',10,@(x) isscalar(x) && isnumeric(x));
+
+p.parse(mu,SIGMA,nFrames,varargin{:});
+
+method = p.Results.Method;
+maxTries = p.Results.MaximumTriesChol;
+xr = p.Results.xr;
+
+%Get samples for standard Gaussian
+if isempty(xr)
+ samples = GPanimation(numel(mu),nFrames);
+else
+ samples = GPanimation(numel(mu),nFrames,xr);
+end
+
+%Transform Samples with mean vector and covariance matrix
+if strcmp(method,'chol')
+ matRad_cfg.dispInfo('Creating smooth samples via cholesky decomposition...\n');
+ [SIGMAcol,p] = chol(SIGMA);
+
+ %If the cholesky decomposition doesn't work because the matrix is
+ %not positive semi-definite, we try to fix it by finding the
+ %nearest positive semidefinite matrix [2]
+ nTries = 0;
+
+ if p~=0
+ %Equations from [2]
+ [~,S,V] = svd((SIGMA + SIGMA')/2);
+ SIGMA = (SIGMA+V*S*V')/2;
+ SIGMA = (SIGMA + SIGMA')/2;
+
+ matRad_cfg.dispInfo('Fixing Covariance matrix to be SPD: ');
+
+ %To account for numerical instabilities
+ while p~=0 && nTries <= maxTries
+ nTries = nTries + 1;
+ matRad_cfg.dispInfo('.');
+ [SIGMAcol,p] = chol(SIGMA);
+ if p~=0
+ minEigenVal = min(eig(SIGMA));
+ SIGMA = SIGMA + (-minEigenVal*nTries^2 + diag(ones(size(SIGMA,1),1)*eps(minEigenVal)));
+ end
+ end
+ matRad_cfg.dispInfo('\n');
+ end
+
+ if p~=0
+ matRad_cfg.dispWarning('Covariance matrix could not be fixed to be PSD in %d tries, falling back to eigenvalue method!',maxTries);
+ method = 'eig';
+ else
+ samples = arrayfun(@(f) mu + SIGMAcol' * samples(:,f),1:nFrames,'UniformOutput',false);
+ samples = cell2mat(samples);
+ end
+end
+
+if strcmp(method,'eig')
+ matRad_cfg.dispInfo('Creating smooth samples via eigen decomposition...\n');
+ try %Try evaluation on GPU
+ SIGMAgpu = gpuArray(SIGMA);
+ [V,D] = eig(SIGMAgpu);
+ Q = gather(real(sqrt(complex(D)))*V);
+ catch
+ [V,D] = eig(SIGMA);
+ Q = real(sqrt(complex(D)))*V;
+ end
+ samples = arrayfun(@(f) mu + Q' * samples(:,f),1:nFrames,'UniformOutput',false);
+
+ samples = cell2mat(samples);
+ samples = real(samples);
+end
+
+end
+
+
+function X = GPanimation(d,n,xr)
+% returns a matrix X of size [d,n], representing a grand circle on the
+% unit d-sphere in n steps, starting at a random location. Given a kernel
+% matrix K, this can be turned into a tour through the sample space, simply by
+% calling chol(K)' * X; Philipp Hennig, September 2013
+
+if nargin == 3
+ if numel(xr) == d % check if we have starting coordinates
+ x = xr;
+ r = sqrt(sum(x.^2));
+ else % use radius
+ r = xr;
+ x = randn(d,1);
+ end
+else % starting sample
+ x = randn(d,1);
+ r = sqrt(sum(x.^2));
+end
+
+
+x = x ./ sqrt(sum(x.^2)); % project onto unit sphere
+
+t = randn(d,1); % sample tangent direction
+t = t - (t'*x) * x; % orthogonalise by Gram-Schmidt.
+t = t ./ sqrt(sum(t.^2)); % standardise
+s = linspace(0,2*pi,n+1); s = s(1:end-1); % space to span
+t = bsxfun(@times,s,t); % span linspace in direction of t
+X = r.* exp_map(x,t); % project onto sphere, re-scale
+end
+
+function M = exp_map(mu, E)
+% Computes exponential map on a sphere
+D = size(E,1);
+theta = sqrt(sum((E.^2)));
+M = mu * cos(theta) + E .* repmat(sin(theta)./theta, D, 1);
+if (any (abs (theta) <= 1e-7))
+ for a = find (abs (theta) <= 1e-7)
+ M (:, a) = mu;
+ end % for
+end % if
+% M (:,abs (theta) <= 1e-7) = mu;
+end % function
diff --git a/matRad/planAnalysis/samplingAnalysis/matRad_latexReport.m b/matRad/planAnalysis/samplingAnalysis/matRad_latexReport.m
new file mode 100644
index 000000000..ca8dbea8a
--- /dev/null
+++ b/matRad/planAnalysis/samplingAnalysis/matRad_latexReport.m
@@ -0,0 +1,499 @@
+function success = matRad_latexReport(outputPath, ct, cst, pln, nominalScenario, structureStat, doseStat, sampDose, listOfQI, varargin)
+% matRad uncertainty analysis report generaator function
+%
+% call
+% latexReport(ct, cst, pln, nominalScenario, structureStat)
+%
+% input
+% outputPath: where to generate the report
+% ct: ct cube
+% cst: matRad cst struct
+% pln: matRad plan meta information struct
+% nominalScenario: struct containing dose, qi and dvh of the nominal scenario
+% structureStat: structures which were examined (can be empty,
+% when all structures were examined)
+% doseStat: structure containing dose statistics as returned
+% from matRad_samplingAnalysis
+% sampDose: matrix containing all sampled Doses as returned
+% from matRad_samplingAnalysis
+% listOfQI: cellstring containing list of quality indicators to
+% be reported
+%
+% Optional Name-Value Pairs:
+% ComputationTime: state computation time to state in the report
+% OperatorName: Name of the Operator generating the report
+% SufficientStatistics: true/false (to warn for bad statistics)
+% PrescribedDose: Set a prescription value. If not set, will use
+% the largest value found in target objectives
+%
+
+% output
+% (binary) a pdf report will be generated and saved
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+p = inputParser;
+p.addParameter('ComputationTime',[],@(x) isscalar(x) && isnumeric(x));
+p.addParameter('OperatorName','matRad User',@(x) isstring(x) || ischar(x));
+p.addParameter('SufficientStatistics',true,@(x) isscalar(x));
+p.addParameter('PrescribedDose',[],@(x) isscalar(x) && isnumeric(x));
+
+p.parse(varargin{:});
+
+computationTime = p.Results.ComputationTime;
+operator = p.Results.OperatorName;
+sufficientStatistics = p.Results.SufficientStatistics;
+dPres = p.Results.PrescribedDose;
+
+dataPath = [outputPath filesep 'data'];
+mkdir(dataPath);
+mkdir(fullfile(dataPath,'frames'));
+mkdir(fullfile(dataPath,'figures'));
+
+
+%% correct cst for unwanted characters and disable commonly not wanted structures
+
+doseCube = nominalScenario.(pln.bioParam.quantityVis);
+
+fillPrescription = isempty(dPres);
+
+if fillPrescription
+ dPres = 0;
+end
+
+notVisibleStructs = {'Beekleys', 'Beekley', 'CT-Referenzpunkt'};
+for i = 1:size(cst,1)
+ % use only alphanumerical characters
+ cst{i,2} = regexprep(cst{i,2},'[^a-zA-Z0-9]','-');
+ if isempty(cst{i,4}{1}) || (sum(strcmp(cst{i,2}, notVisibleStructs)) >= 1)
+ cst{i,5}.Visible = false;
+ end
+
+ if strcmp(cst{i,3},'TARGET')
+ dPres = max([dPres mean(doseCube(cst{i,4}{1}))]);
+ end
+end
+
+if fillPrescription
+ dPres = round(dPres,1);
+ matRad_cfg.dispInfo('Estimated prescribed dose to be %f\n',dPres);
+end
+
+%% check whether all qi are available
+if ~all(ismember(listOfQI, fieldnames(nominalScenario.qi)))
+ error('Not all QI are available. Possible TYPO in at least one of them.');
+end
+[~, ~, ixOfQIinNominal] = intersect(listOfQI, fieldnames(nominalScenario.qi), 'stable');
+
+
+%% insert standard patient information
+% issue warning for insufficient number of scenarios
+if ~sufficientStatistics
+ warnMessage = 'Insufficient statistics. Handle with care.';
+else
+ warnMessage = '';
+end
+
+if isfield(ct, 'dicomInfo')
+ if isfield(ct.dicomInfo, 'PatientName')
+ patientInformation.firstName = parseFromDicom(ct.dicomInfo.PatientName, 'GivenName');
+ patientInformation.lastName = parseFromDicom(ct.dicomInfo.PatientName, 'FamilyName');
+ end
+ %%% in future release, patient sex and patient id might be moved in dicomInfo
+ patientInformation.sex = parseFromDicom(ct.dicomInfo, 'PatientSex');
+ patientInformation.patientID = parseFromDicom(ct.dicomInfo, 'PatientID');
+else
+ patientInformation.firstName = 'N.A.';
+ patientInformation.lastName = 'N.A.';
+ patientInformation.sex = 'N.A.';
+ patientInformation.patientID = 'N.A.';
+end
+
+%%% when id and sex moved to dicomInfo this get's obsolete
+try
+ patientInformation.sex = ct.dicomMeta.PatientSex;
+catch
+end
+try
+ patientInformation.patientID = ct.dicomMeta.PatientID;
+catch
+end
+
+% import plan information
+planInformation.gantryAngles = num2str(pln.propStf.gantryAngles);
+planInformation.couchAngles = num2str(pln.propStf.couchAngles);
+planInformation.modality = pln.radiationMode;
+
+line = cell(0);
+line = [line; '\newcommand{\warnMessage}{',warnMessage,'}'];
+line = [line; '\newcommand{\patientFirstName}{',patientInformation.firstName,'}'];
+line = [line; '\newcommand{\patientLastName}{',patientInformation.lastName,'}'];
+line = [line; '\newcommand{\patientSex}{',patientInformation.sex,'}'];
+line = [line; '\newcommand{\patientID}{',patientInformation.patientID,'}'];
+line = [line; '\newcommand{\operator}{',operator,'}'];
+
+line = [line; '\newcommand{\reportGenerationDate}{\today}'];
+line = [line; '\newcommand{\computationTime}{', num2str(round(computationTime / 3600,2)), ' h}'];
+
+line = [line; '\newcommand{\planGantryAngles}{',planInformation.gantryAngles,'}'];
+line = [line; '\newcommand{\planCouchAngles}{',planInformation.couchAngles,'}'];
+line = [line; '\newcommand{\planRadiationModality}{',planInformation.modality,'}'];
+
+fid = fopen(fullfile(dataPath,'patientInformation.tex'),'w');
+for i = 1:numel(line)
+ text = regexprep(line{i},{'\','_'},{'\\\','-'});
+ fprintf(fid,text);
+ fprintf(fid,'\n');
+end
+fclose(fid);
+
+%% analysis parameters
+line = cell(0);
+
+% raw input
+multScen = pln.multScen;
+% shift parameters
+line = [line; '\newcommand{\numOfShiftScen}{', num2str(multScen.numOfShiftScen), '}'];
+line = [line; '\newcommand{\shiftSize}{', num2str(max(multScen.isoShift)), '}'];
+line = [line; '\newcommand{\shiftGenType}{', num2str(multScen.shiftGenType), '}'];
+line = [line; '\newcommand{\shiftCombType}{', num2str(multScen.shiftCombType), '}'];
+
+% range parameters
+line = [line; '\newcommand{\numOfRangeShiftScen}{', num2str(multScen.numOfRangeShiftScen), '}'];
+line = [line; '\newcommand{\maxAbsRangeShift}{', num2str(max(multScen.absRangeShift)), '}'];
+line = [line; '\newcommand{\maxRelRangeShift}{', num2str(max(multScen.relRangeShift)), '}'];
+line = [line; '\newcommand{\rangeCombType}{', num2str(multScen.rangeCombType), '}'];
+line = [line; '\newcommand{\rangeGenType}{', num2str(multScen.rangeGenType), '}'];
+line = [line; '\newcommand{\scenCombType}{', num2str(multScen.scenCombType), '}'];
+
+% gamma analysis parameters
+line = [line; '\newcommand{\gammaDoseAgreement}{', num2str(doseStat.gammaAnalysis.doseAgreement), '}'];
+line = [line; '\newcommand{\gammaDistAgreement}{', num2str(doseStat.gammaAnalysis.distAgreement), '}'];
+
+if pln.multScen.numOfCtScen <= 1
+ line = [line; '\newcommand{\ctScen}{false}'];
+else
+ line = [line; '\newcommand{\ctScen}{true}'];
+end
+
+if pln.multScen.numOfRangeShiftScen <= 1
+ line = [line; '\newcommand{\rangeScen}{false}'];
+else
+ line = [line; '\newcommand{\rangeScen}{true}'];
+end
+
+if pln.multScen.numOfShiftScen <= 1
+ line = [line; '\newcommand{\shiftScen}{false}'];
+else
+ line = [line; '\newcommand{\shiftScen}{true}'];
+end
+
+line = [line; '\newcommand{\shiftSD}{', num2str(pln.multScen.shiftSD), '}'];
+line = [line; '\newcommand{\rangeAbsSD}{', num2str(pln.multScen.rangeAbsSD), '}'];
+line = [line; '\newcommand{\rangeRelSD}{', num2str(pln.multScen.rangeRelSD), '}'];
+
+fid = fopen(fullfile(dataPath,'uncertaintyParameters.tex'),'w');
+for i = 1:numel(line)
+ text = regexprep(line{i},'\','\\\');
+ fprintf(fid,text);
+ fprintf(fid,'\n');
+end
+fclose(fid);
+
+
+%% plot isocentre slices (nominal, mean, std)
+
+
+doseWindow = [0 1.1 * max(doseCube(:))];
+stdWindow = [0 1.1 * max(doseStat.stdCubeW(:))];
+gammaWindow = [0 1.1 * max(doseStat.gammaAnalysis.gammaCube(:))];
+
+for plane=1:3
+ switch plane
+ case 1
+ slice = round(pln.propStf.isoCenter(1,2) / ct.resolution.x,0);
+ case 2
+ slice = round(pln.propStf.isoCenter(1,1) / ct.resolution.y,0);
+ case 3
+ slice = round(pln.propStf.isoCenter(1,3) / ct.resolution.z,0);
+ end
+ colors = colorcube(size(cst,1));
+ for cubesToPlot = 1:3
+ figure; ax = gca;
+
+ doseCube = nominalScenario.(pln.bioParam.quantityVis);
+
+ if cubesToPlot == 1
+
+ if isfield(nominalScenario,'RBExD')
+ colorMapLabel = 'RBExDose [Gy(RBE)]';
+ else
+ colorMapLabel = 'physical Dose [Gy]';
+ end
+ fileSuffix = 'nominal';
+ matRad_plotSliceWrapper(ax,ct,cst,1,doseCube,plane,slice,[],[],colors,[],[],[],[],colorMapLabel);
+
+ elseif cubesToPlot == 2
+
+ doseCube = doseStat.gammaAnalysis.gammaCube;
+ colorMapLabel = 'gamma index';
+ fileSuffix = 'gamma';
+ gammaColormap = matRad_getColormap('gammaIndex');
+ matRad_plotSliceWrapper(ax,ct,cst,1,doseCube,plane,slice,[],[],colors,gammaColormap,[0 2],[],[],colorMapLabel);
+
+ elseif cubesToPlot == 3
+
+ if isfield(nominalScenario,'RBExD')
+ colorMapLabel = 'Standard deviation [Gy(RBE)]';
+ else
+ colorMapLabel = 'Standard deviation [Gy]';
+ end
+ doseCube = doseStat.stdCubeW;
+ fileSuffix = 'stdW';
+ matRad_plotSliceWrapper(ax,ct,cst,1,doseCube,plane,slice,[],[],colors,[],[],[],[],colorMapLabel);
+
+ end
+ drawnow();
+ cleanfigure();
+ matlab2tikz(fullfile(dataPath,'figures',['isoSlicePlane', num2str(plane), '_', fileSuffix, '.tex']), 'relativeDataPath', ['data' filesep 'figures'], 'showInfo', false, 'width', '\figW');
+ close
+ end
+end
+
+%% gaussian orbit sample animation
+if exist('matRad_getGaussianOrbitSamples','file') == 2
+ confidenceValue = 0.5;
+
+ slice = round(pln.propStf.isoCenter(1,plane) / ct.resolution.z,0);
+ framePath = fullfile(dataPath, 'frames');
+ if isfield(nominalScenario,'RBExD')
+ legendColorbar = 'RBExDose [Gy(RBE)]';
+ else
+ legendColorbar = 'physical Dose [Gy]';
+ end
+ % any(w == 0) is not allowed, due to numerical reasons use insignificant w, for weights which are numerically zero
+ w = pln.multScen.scenProb;
+ w(w == 0) = eps(class(w));
+ matRad_createAnimationForLatexReport(confidenceValue, ct, cst, slice, doseStat.meanCubeW, sampDose, w, pln.subIx, framePath, legendColorbar,'PrescribedDose',dPres);
+
+ line = cell(0);
+ line = [line; '\newcommand{\framerate}{24}'];
+ line = [line; '\newcommand{\firstframe}{1}'];
+ line = [line; '\newcommand{\lastframe}{120}'];
+
+ fid = fopen(fullfile(dataPath,'parameters.tex'),'w');
+ for i = 1:numel(line)
+ text = regexprep(line{i},{'\','_'},{'\\\','-'});
+ fprintf(fid,text);
+ fprintf(fid,'\n');
+ end
+ fclose(fid);
+end
+
+%% plot nominal dvh and write qi table
+% plot dvh
+colors = jet(size(cst,1));
+hold off;
+for i = 1:size(cst,1)
+ if cst{i,5}.Visible == true
+ [y, argmin] = cutAtArgmin(nominalScenario.dvh(i).volumePoints);
+ x = nominalScenario.dvh(i).doseGrid(1:argmin);
+ h(1) = plot(x,y,'LineWidth',2, 'Color', colors(i,:), 'DisplayName', cst{i,2});
+ ylim([0 100]);
+ if strncmp(pln.bioParam.quantityVis,'RBExD',5)
+ xlabel('Dose RBE x [Gy]');
+ else
+ xlabel('Dose [Gy]');
+ end
+ ylabel('Volume [%]');
+ lh = legend('show','Location','northeastoutside');
+ hold on;
+ end
+end
+drawnow;
+matlab2tikz(fullfile(dataPath,'figures','nominalDVH.tex'),'showInfo', false, 'width', '0.7\textwidth');
+hold off
+close
+
+% write qi table
+% get list of all fields
+fieldsInQi = fieldnames(nominalScenario.qi);
+for i= 1:numel(fieldsInQi)
+ nanRow.(fieldsInQi{i}) = NaN;
+end
+
+nomQi = nominalScenario.qi(1);
+structName = {};
+c = 1;
+for i = 1:size(cst,1)
+ if cst{i,5}.Visible == true && ~isempty(cst{i,4}{1})
+ nomQi(c) = nominalScenario.qi(i);
+ fullQi(i) = nominalScenario.qi(i);
+ structName = [structName, cst{i,2}];
+ c = c + 1;
+ else
+ fullQi(i) = nanRow;
+ end
+end
+
+
+
+nomQiTable = struct2table(nomQi);
+fullNomQiTable = struct2table(fullQi);
+fullNomQiTable = fullNomQiTable(:,ixOfQIinNominal);
+nomQiTable.Properties.RowNames = structName;
+nomQiTable.Properties.VariableNames = regexprep(nomQiTable.Properties.VariableNames, '_', '');
+input.data = nomQiTable(:,ixOfQIinNominal);
+input.dataFormat = {'%.2f'};
+input.booktabs = 1;
+input.tableBorders = 0;
+input.tableEnvironment = 0;
+
+latex = latexTable(input);
+
+% save LaTex code as file
+filename{i}.QI = regexprep([cst{i,2},'_QI.tex'], '\s+', '');
+fid=fopen(fullfile(dataPath,'nominalQI.tex'),'w');
+[nrows, ~] = size(latex);
+for row = 1:nrows
+ fprintf(fid,'%s\n',latex{row,:});
+end
+fclose(fid);
+clear latex
+clear input
+
+%% add DVH and QI
+clear h
+clear filename
+% relative file path (relative to main.tex)
+relativePath = fullfile('data','structures');
+
+if strcmp(pln.bioParam.quantityVis, 'RBExD')
+ labelDoseDVH = 'Dose RBE x [Gy]';
+else
+ labelDoseDVH = 'Dose [Gy]';
+end
+for i = 1:size(cst,1)
+ if cst{i,5}.Visible == true
+ [~, ~, ixOfQIinStruct] = intersect(listOfQI, fieldnames(structureStat(i).qiStat), 'stable');
+
+ matRad_plotDVHBand(nominalScenario.dvh(i), structureStat(i), labelDoseDVH);
+ cleanfigure();
+ filename{i}.DVH = regexprep([cst{i,2},'_DVH.tex'], '\s+', '');
+ matlab2tikz(fullfile(dataPath,'structures',filename{i}.DVH),'showInfo', false, 'width', '\figW', 'height', '\figH', 'extraAxisOptions', 'reverse legend');
+ close
+
+ % QI
+ fprintf([num2str(i),'\n']);
+ qiTable = fullNomQiTable(i,:);
+ qiTable = [qiTable; structureStat(i).qiStat(:,ixOfQIinStruct)];
+ qiTable.Properties.RowNames{1} = 'nominal';
+ qiTable.Properties.VariableNames = regexprep(qiTable.Properties.VariableNames, '_', '');
+ input.data = qiTable;
+ input.dataFormat = {'%.2f'};
+ input.tableBorders = 0;
+ input.booktabs = 1;
+ input.tableEnvironment = 0;
+ input.tableRowLabel = ['scen-', cst{i,2}];
+
+ latex = latexTable(input);
+
+ % save LaTex code as file
+ filename{i}.QI = regexprep([cst{i,2},'_QI.tex'], '\s+', '');
+ fid=fopen(fullfile(dataPath,'structures',filename{i}.QI),'w');
+ [nrows,~] = size(latex);
+ for row = 1:nrows
+ fprintf(fid,'%s\n',latex{row,:});
+ end
+ fclose(fid);
+ end
+end
+
+% write them to structureWrapper
+counter = 0;
+line = cell(0);
+for i = 1:size(cst,1)
+ if cst{i,5}.Visible == true
+ counter = counter + 1;
+ if counter ~= 1
+ line = [line; '\newpage'];
+ end
+ line = [line; ['\subsection{', cst{i,2}, '}']];
+ line = [line; '\begin{center}'];
+ line = [line; ['\input{', regexprep(fullfile(relativePath,filename{i}.DVH),'\','/'), '}']];
+ line = [line; '\end{center}'];
+ line = [line; ['\input{', regexprep(fullfile(relativePath,filename{i}.QI),'\','/'), '}']];
+ end
+end
+
+fid = fopen(fullfile(dataPath,'structureWrapper.tex'),'w');
+for i = 1:numel(line)
+ text = regexprep(line{i},'\','\\\');
+ fprintf(fid,text);
+ fprintf(fid,'\n');
+end
+fclose(fid);
+
+
+%% clean up
+close all
+
+
+if ispc
+ executeLatex = 'lualatex --shell-escape --interaction=nonstopmode main.tex';
+elseif isunix
+ executeLatex = '/Library/TeX/texbin/lualatex --shell-escape --interaction=nonstopmode main.tex';
+end
+
+currPath = pwd;
+cd(outputPath);
+
+response = system(executeLatex);
+if response == 127 % means not found
+ matRad_cfg.dispWarning('Could not find tex distribution. Please compile manually.');
+ success = false;
+else
+ system(executeLatex); %Execute seccond time
+
+ if exist('main.pdf','file')
+ matRad_cfg.dispInfo('PDF generated.');
+ success = true;
+ else
+ success = false;
+ end
+end
+
+cd(currPath);
+
+end
+
+
+function [y, argmin] = cutAtArgmin(x)
+ [~,argmin] = min(x);
+ y = x(1:argmin);
+end
+
+function text = parseFromDicom(dicomStruct, field, default)
+ if ~exist('default', 'var') || isempty(default)
+ default = 'N.A.';
+ end
+ if isfield(dicomStruct, field)
+ text = dicomStruct.(field);
+ else
+ text = default;
+ end
+end
diff --git a/matRad/planAnalysis/samplingAnalysis/matRad_samplingAnalysis.m b/matRad/planAnalysis/samplingAnalysis/matRad_samplingAnalysis.m
new file mode 100644
index 000000000..a87e2ee54
--- /dev/null
+++ b/matRad/planAnalysis/samplingAnalysis/matRad_samplingAnalysis.m
@@ -0,0 +1,253 @@
+function [cstStat, doseStat, meta] = matRad_samplingAnalysis(ct,cst,pln,caSampRes,mSampDose,resultGUInomScen,varargin)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad uncertainty sampling analysis function
+%
+% call
+% [structureStat, doseStat] = samplingAnalysis(ct,cst,subIx,mSampDose,w)
+%
+% input
+% ct: ct cube
+% cst: matRad cst struct
+% pln: matRad's pln struct
+% caSampRes: cell array of sampling results depicting plan
+% parameter
+% mSampDose: matrix holding the sampled doses, each row
+% corresponds to one dose sample
+% resultGUInomScen: resultGUI struct of the nominal plan
+% varargin: optional Name/Value pairs for additional custom
+% settings
+% - 'GammaCriterion': 1x2 vector [% mm]
+% - 'Percentiles': vector with desired percentiles
+% between (0,1)
+%
+%
+% output
+% cstStat structure-wise statistics (mean, max, percentiles, ...)
+% doseStat dose-wise statistics (mean, max, percentiles, ...)
+% meta contains additional information about sampling analysis
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% check integrity of statistics
+matRad_cfg = MatRad_Config.instance();
+
+p = inputParser;
+p.addParameter('gammaCriterion',[2 2],@(g) numel(g) == 2 && isnumeric(g) && all(g > 0));
+p.addParameter('percentiles',[0.01 0.05 0.125 0.25 0.5 0.75 0.875 0.95 0.99],@(p) (isscalar(p) || isvector(p)) && isnumeric(p) && all(p > 0 & p < 1));
+
+parse(p,varargin{:});
+
+meta = p.Results;
+
+meta.sufficientStatistics = matRad_checkSampIntegrity(pln.multScen);
+
+ix = pln.subIx;
+vProb = pln.multScen.scenProb;
+
+%% generate structurewise statistics
+cstStat = struct();
+% reassing dvh to stats structure
+for i = 1:size(resultGUInomScen.cst,1)
+ cstStat(i).name = caSampRes(1).qi(i).name;
+ for l = 1:numel(caSampRes)
+ % check if structures still match
+ if any(~strcmp(cstStat(i).name, {caSampRes(l).dvh(i).name, caSampRes(l).qi(i).name}))
+ matRad_cfg.dispError('matRad: Error, wrong structure.');
+ end
+ cstStat(i).dvh(l).doseGrid = caSampRes(l).dvh(i).doseGrid;
+ cstStat(i).dvh(l).volumePoints = caSampRes(l).dvh(i).volumePoints;
+ cstStat(i).qi(l) = caSampRes(l).qi(i);
+ cstStat(i).w(l) = vProb(l)';
+ end
+end
+%% calculate mean and std cube
+% compute doseMatrix with columns correspond to scenarios
+
+
+[env,envver] = matRad_getEnvironment();
+
+doseStat.meanCube = zeros(ct.cubeDim);
+doseStat.stdCube = zeros(ct.cubeDim);
+
+doseStat.meanCubeW = zeros(ct.cubeDim);
+doseStat.stdCubeW = zeros(ct.cubeDim);
+
+doseStat.meanCube(ix) = mean(mSampDose,2);
+doseStat.stdCube(ix) = std(mSampDose,1,2);
+doseStat.meanCubeW(ix) = (sum(mSampDose * diag(vProb),2));
+
+%Weighting of std is not similar in Octave & Matlab
+switch env
+ case 'MATLAB'
+
+ doseStat.meanCubeW(ix) = (sum(mSampDose * diag(vProb),2));
+ doseStat.stdCubeW(ix) = std(mSampDose,vProb,2);
+ case 'OCTAVE'
+ try
+ pkg load nan;
+ nanLoaded = true;
+ catch
+ nanLoaded = false;
+ matRad_cfg.dispWarning('Weighted std not possible due to missing ''nan'' package!');
+ end
+
+ if nanLoaded
+ doseStat.stdCubeW(ix) = std(mSampDose,[],2,vProb);
+ else
+ doseStat.stdCubeW(ix) = doseStat.stdCube(ix);
+ end
+end
+
+
+% gamma cube
+doseCube = resultGUInomScen.(pln.bioParam.quantityVis);
+if strncmp(pln.bioParam.quantityVis,'RBExD', 5)
+ doseStat.gammaAnalysis.cube1Name = 'resultGUInomScen.RBExD';
+else
+ doseStat.gammaAnalysis.cube1Name = 'resultGUInomScen.physicalDose';
+end
+
+doseStat.gammaAnalysis.cube1 = doseCube;
+doseStat.gammaAnalysis.cube2 = doseStat.meanCubeW;
+doseStat.gammaAnalysis.cube2Name = 'doseStat.meanCubeW';
+
+matRad_cfg.dispInfo(['matRad: Performing gamma index analysis with parameters', num2str(meta.gammaCriterion), '[%% mm] \n']);
+doseStat.gammaAnalysis.doseAgreement = meta.gammaCriterion(1);
+doseStat.gammaAnalysis.distAgreement = meta.gammaCriterion(2);
+
+doseStat.gammaAnalysis.gammaCube = matRad_gammaIndex(doseCube,doseStat.meanCubeW,[ct.resolution.x ct.resolution.y ct.resolution.z],meta.gammaCriterion);
+
+%% percentiles
+percentiles = meta.percentiles;
+percentileNames = cell(numel(percentiles),1);
+% create fieldnames
+for i = 1:numel(percentiles)
+ percentileNames{i} = ['P',num2str(percentiles(i)*100)];
+end
+% create table rownames
+metric = vertcat({'mean';'min';'max';'std'},percentileNames{:});
+
+% create statstics where structure based results (QI and DVH) are available
+for i = 1:size(cst,1)
+ cstStat(i).percentiles = percentiles;
+ cstStat(i).metric = metric;
+ cstStat(i).dvhStat = calcDVHStat(cstStat(i).dvh,cstStat(i).percentiles,cstStat(i).w);
+ cstStat(i).qiStat = calcQiStat(cstStat(i).qi,cstStat(i).percentiles,cstStat(i).w);
+end
+
+
+% dvh statistics
+ function [dvhStat, doseGrid] = calcDVHStat(dvh,percentiles,w)
+ doseGrid = dvh(1).doseGrid;
+ dvhMat = NaN * ones(numel(dvh),numel(dvh(1).volumePoints));
+ for j = 1:numel(dvh)
+ dvhMat(j,:) = dvh(j).volumePoints;
+ end
+ % for statistical reasons, treat NaN as 0
+ dvhMat(isnan(dvhMat)) = 0;
+
+ dvhStat.mean.doseGrid = doseGrid;
+ dvhStat.mean.volumePoints = wMean(dvhMat,w);
+ % [~,argmin] = min(dvhStat.mean.volumePoints);
+ % dvhStat.mean(2,argmin + 1:end) = NaN;
+
+ dvhStat.min.doseGrid = doseGrid;
+ dvhStat.min.volumePoints = min(dvhMat);
+ % [~,argmin] = min(dvhStat.min.volumePoints);
+ % dvhStat.min(2,argmin + 1:end) = NaN;
+
+ dvhStat.max.doseGrid = doseGrid;
+ dvhStat.max.volumePoints = max(dvhMat);
+ % [~,argmin] = min(dvhStat.max.volumePoints);
+ % dvhStat.max(2,argmin + 1:end) = NaN;
+
+ dvhStat.std.doseGrid = doseGrid;
+ dvhStat.std.volumePoints = std(dvhMat,w);
+
+ dvhStat.percDVH = NaN * ones(numel(percentiles),numel(doseGrid));
+
+ for j = 1:size(dvhMat,2)
+ wQ = matRad_weightedQuantile(dvhMat(:,j), percentiles, w', false);
+ dvhStat.percDVH(:,j) = wQ;
+ end
+
+ end % eof calcDVHStat
+
+ % qi statistics
+ function qiStat = calcQiStat(qi,percentiles,w)
+ fields = fieldnames(qi);
+ % remove name field
+ if sum(strcmp('VOIname', fields)) >= 1
+ qi = rmfield(qi, 'VOIname');
+ end
+ fields = fieldnames(qi);
+ qiStruct = qi;
+
+ % create helper matlab structure which will be converted to table
+ qiStatH = struct();
+ for j = 1:numel(fields)
+ if numel([qiStruct(:).(fields{j})]) == numel(w)
+ qiStatH(1).(fields{j}) = wMean([qiStruct(:).(fields{j})],w);
+ qiStatH(2).(fields{j}) = min([qiStruct(:).(fields{j})]);
+ qiStatH(3).(fields{j}) = max([qiStruct(:).(fields{j})]);
+ qiStatH(4).(fields{j}) = std([qiStruct(:).(fields{j})],w);
+ wQ = matRad_weightedQuantile([qiStruct(:).(fields{j})], percentiles, w', false);
+ for k = 1:numel(wQ)
+ sIx = k + 4;
+ qiStatH(sIx).(fields{j}) = wQ(k);
+ end
+ else
+ for k = 1:(4 + numel(percentiles))
+ qiStatH(k).(fields{j}) = [];
+ end
+ end
+ end
+ env = matRad_getEnvironment();
+ if strcmp(env,'MATLAB')
+ qiStat = struct2table(qiStatH);
+ qiStat.Properties.RowNames = metric;
+ else
+ qiStat = qiStatH;
+ end
+ end % eof calcQiStat
+
+ function S = wMean(X,w)
+ if exist('w','var') || ~isempty(w)
+ if isvector(X) && isvector(w)
+ S = reshape(w,1,[]) * reshape(X,[],1) / sum(w);
+ else
+ % row-wise
+ S = reshape(w,1,[]) * X ./ sum(w);
+ end
+
+ else
+ S = mean(X);
+ end
+ end
+
+ % check integrity of scenario analysis (i.e. check number of scenarios)
+ function statCheck = matRad_checkSampIntegrity(multScen)
+
+ if multScen.totNumScen > 20
+ totalNum = true;
+ else
+ totalNum = false;
+ end
+
+ statCheck = totalNum; % * .... *
+
+ end
+
+end
diff --git a/matRad/planAnalysis/samplingAnalysis/matRad_setupStudyTemplate.m b/matRad/planAnalysis/samplingAnalysis/matRad_setupStudyTemplate.m
new file mode 100644
index 000000000..807c72afe
--- /dev/null
+++ b/matRad/planAnalysis/samplingAnalysis/matRad_setupStudyTemplate.m
@@ -0,0 +1,62 @@
+% configuration script for uncertainty sampling
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% examine only specific structures? improves calculation time
+examineStructures = {}; % e.g. examinedStructures = {'CTV', 'OAR'};
+
+multScen = matRad_multScen([],'impScen');
+% a) define shift scenarios
+multScen.numOfShiftScen = [0 0 0]; % number of shifts in x y and z direction
+multScen.shiftSize = [4.5 4.5 4.5]; % maximum shift [mm] % (e.g. prostate cases 5mm otherwise 3mm)
+multScen.shiftGenType = 'equidistant'; % equidistant: equidistant shifts, sampled: sample shifts from normal distribution
+multScen.shiftCombType = 'individual'; % individual: no combination of shift scenarios; number of shift scenarios is sum(multScen.numOfShiftScen)
+ % permuted: create every possible shift combination; number of shift scenarios is 8,27,64 ...
+ % combined: number of shifts in each directions must be the same. only use when shifts are sampled
+
+
+% b) define range error scenarios
+multScen.numOfRangeShiftScen = 30; % number of absolute and/or relative range scnearios.
+ % if absolute and relative range scenarios are defined then multScen.rangeCombType defines the resulting number of range scenarios
+multScen.maxAbsRangeShift = 2; % maximum absolute over and undershoot in mm
+multScen.maxRelRangeShift = 4.5; % maximum relative over and undershoot in %
+multScen.rangeCombType = 'combined'; % individual: no combination of absolute and relative range scenarios; combined: combine absolute and relative range scenarios
+multScen.rangeGenType = 'equidistant'; % equidistant: equidistant range shifts, sampled: sample range shifts from normal distribution
+multScen.scenCombType = 'individual'; % individual: no combination of range and setup scenarios,
+ % combined: combine range and setup scenarios if their scenario number is consistent
+ % permuted: create every possible combination of range and setup scenarios
+multScen.includeNomScen = false;
+
+
+% define standard deviation of normal distribution - important for probabilistic treatment planning
+multScen.rangeRelSD = 0; % given in [%]
+multScen.rangeAbsSD = 1.8; % given in [mm]
+multScen.shiftSD = [1.8 1.8 1.8]; % given in [mm]
+
+multScen = multScen.matRad_createValidInstance();
+
+%% path for output pdf and mat
+param.outputPath = pwd;
+
+%% report parameters
+param.operator = 'matrad';
+
+% default set of percentiles for scenario analysis; uncomment to override
+% param.percentiles = [0.01 0.05 0.125 0.25 0.5 0.75 0.875 0.95 0.99];
+% default criteria for gamma analysis between nominal and mean dose
+% param.criteria = [3 3]; %%% [X % Y mm]
+
+%% start calculation
+matRad_calcStudy(examineStructures, multScen, [], param);
+
+% exit;
diff --git a/plotting/colormaps/colormapTemplate.txt b/matRad/plotting/colormaps/colormapTemplate.txt
similarity index 92%
rename from plotting/colormaps/colormapTemplate.txt
rename to matRad/plotting/colormaps/colormapTemplate.txt
index 782bb0dd2..0071f6ec4 100644
--- a/plotting/colormaps/colormapTemplate.txt
+++ b/matRad/plotting/colormaps/colormapTemplate.txt
@@ -12,7 +12,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/plotting/colormaps/diffMap.m b/matRad/plotting/colormaps/diffMap.m
similarity index 97%
rename from plotting/colormaps/diffMap.m
rename to matRad/plotting/colormaps/diffMap.m
index 5e15f14d1..4e51f549b 100644
--- a/plotting/colormaps/diffMap.m
+++ b/matRad/plotting/colormaps/diffMap.m
@@ -7,7 +7,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/plotting/colormaps/gammaIndex.m b/matRad/plotting/colormaps/gammaIndex.m
similarity index 98%
rename from plotting/colormaps/gammaIndex.m
rename to matRad/plotting/colormaps/gammaIndex.m
index 683586925..7f3bc36a9 100644
--- a/plotting/colormaps/gammaIndex.m
+++ b/matRad/plotting/colormaps/gammaIndex.m
@@ -10,7 +10,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/plotting/matRad_computeAllVoiSurfaces.m b/matRad/plotting/matRad_computeAllVoiSurfaces.m
new file mode 100644
index 000000000..b41a1541f
--- /dev/null
+++ b/matRad/plotting/matRad_computeAllVoiSurfaces.m
@@ -0,0 +1,98 @@
+function cst = matRad_computeAllVoiSurfaces(ct,cst)
+% matRad function that computes all VOI surfaces
+%
+% call
+% cst = matRad_computeAllVoiSurfaces(ct,cst)
+%
+% input
+% ct matRad ct struct
+% cst matRad cst struct
+%
+% output
+% cst the new cst with the column containing the precomputed surface
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispInfo('Computing 3D Surfaces...\n');
+
+% initialize waitbar
+% TODO: This should be managed from the user interface instead
+if ~matRad_cfg.disableGUI
+ figureWait = waitbar(0,'Computing 3D Surfaces...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor);
+ matRad_applyThemeToWaitbar(figureWait);
+ % prevent closure of waitbar and show busy state
+ set(figureWait,'pointer','watch');
+end
+
+xCoord = ct.resolution.x * double(1:ct.cubeDim(2));
+yCoord = ct.resolution.y * double(1:ct.cubeDim(1));
+zCoord = ct.resolution.z * double(1:ct.cubeDim(3));
+
+[xMesh,yMesh,zMesh] = meshgrid(xCoord,yCoord,zCoord);
+
+numVois = size(cst,1);
+
+
+
+for s = 1:numVois
+ mask = zeros(ct.cubeDim); % create zero cube with same dimeonsions like dose cube
+ mask(cst{s,4}{1}) = 1;
+
+ %Smooth the VOI
+ v = smooth3(mask,'gaussian',[5 5 5],2);
+
+ maskThreshold = 0.5;
+
+ %Small sanity check in case we smoothed to much on a small VOI
+ if all(v(:) < maskThreshold)
+ v = mask;
+ end
+
+ isoSurface = isosurface(xMesh,yMesh,zMesh,v,maskThreshold);
+
+
+ %reduce the complexity
+ isoSurface = reducepatch(isoSurface,0.05);
+
+ %compute isonormals
+ isoNormals = isonormals(xMesh,yMesh,zMesh,v,isoSurface.vertices);
+
+ cst{s,8}{1} = isoSurface;
+ cst{s,8}{2} = isoNormals;
+
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(s,numVois);
+ end
+
+ if ~matRad_cfg.disableGUI && any(ishandle(figureWait))
+ waitbar(s/numVois,figureWait); % Update the waitbar
+ end
+end
+
+try
+ if ~matRad_cfg.disableGUI
+ delete(figureWait);
+ pause(0.1);
+ end
+catch
+ %Nothing to do
+end
+
+end
+
diff --git a/plotting/matRad_computeIsoDoseContours.m b/matRad/plotting/matRad_computeIsoDoseContours.m
similarity index 77%
rename from plotting/matRad_computeIsoDoseContours.m
rename to matRad/plotting/matRad_computeIsoDoseContours.m
index a63e07471..b37a152ad 100644
--- a/plotting/matRad_computeIsoDoseContours.m
+++ b/matRad/plotting/matRad_computeIsoDoseContours.m
@@ -21,7 +21,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -31,21 +31,22 @@
dim = size(doseCube);
isoDoseContours = cell(max(dim(:)),3);
+isoLevels = double(isoLevels);
minLevel = min(isoLevels(:));
for slice = 1:dim(1)
if any(any(doseCube(slice,:,:) >= minLevel))
- isoDoseContours{slice,1} = contourc(squeeze(doseCube(slice,:,:)),isoLevels);
+ isoDoseContours{slice,1} = contourc(double(squeeze(doseCube(slice,:,:))),isoLevels);
end
end
for slice = 1:dim(2)
if any(any(doseCube(:,slice,:) >= minLevel))
- isoDoseContours{slice,2} = contourc(squeeze(doseCube(:,slice,:)),isoLevels);
+ isoDoseContours{slice,2} = contourc(double(squeeze(doseCube(:,slice,:))),isoLevels);
end
end
for slice = 1:dim(3)
if any(any(doseCube(:,:,slice) >= minLevel))
- isoDoseContours{slice,3} = contourc(squeeze(doseCube(:,:,slice)),isoLevels);
+ isoDoseContours{slice,3} = contourc(double(squeeze(doseCube(:,:,slice))),isoLevels);
end
end
diff --git a/matRad/plotting/matRad_computeVoiContours.m b/matRad/plotting/matRad_computeVoiContours.m
new file mode 100644
index 000000000..d4679187e
--- /dev/null
+++ b/matRad/plotting/matRad_computeVoiContours.m
@@ -0,0 +1,58 @@
+function cst = matRad_computeVoiContours(ct,cst)
+% matRad function that computes all VOI contours
+%
+% call
+% cst = matRad_computeVoiContours(ct,cst)
+%
+% input
+% ct matRad ct struct
+% cst matRad cst struct
+%
+% output
+% cst the new cst with the column containing the precomputed contours
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+matRad_cfg.dispInfo('Precomputing Contours for Display...\n');
+
+mask = zeros(ct.cubeDim); % create zero cube with same dimeonsions like dose cube
+for ctScen = 1:ct.numOfCtScen
+ for s = 1:size(cst,1)
+ cst{s,7}{1,ctScen} = cell(max(ct.cubeDim(:)),3);
+ mask(:) = 0;
+ mask(cst{s,4}{ctScen}) = 1;
+ for slice = 1:ct.cubeDim(1)
+ if any(any(mask(slice,:,:) > 0))
+ cst{s,7}{1,ctScen}{slice,1} = contourc(squeeze(mask(slice,:,:)),.5*[1 1]);
+ end
+ end
+ for slice = 1:ct.cubeDim(2)
+ if any(any(mask(:,slice,:) > 0))
+ cst{s,7}{1,ctScen}{slice,2} = contourc(squeeze(mask(:,slice,:)),.5*[1 1]);
+ end
+ end
+ for slice = 1:ct.cubeDim(3)
+ if any(any(mask(:,:,slice) > 0))
+ cst{s,7}{1,ctScen}{slice,3} = contourc(squeeze(mask(:,:,slice)),.5*[1 1]);
+ end
+ end
+ if matRad_cfg.logLevel > 2
+ matRad_progress(s + (ctScen-1)*s,size(cst,1)*ct.numOfCtScen);
+ end
+ end
+end
\ No newline at end of file
diff --git a/plotting/matRad_computeVoiContoursWrapper.m b/matRad/plotting/matRad_computeVoiContoursWrapper.m
similarity index 81%
rename from plotting/matRad_computeVoiContoursWrapper.m
rename to matRad/plotting/matRad_computeVoiContoursWrapper.m
index 72f57a25e..cc690e25a 100644
--- a/plotting/matRad_computeVoiContoursWrapper.m
+++ b/matRad/plotting/matRad_computeVoiContoursWrapper.m
@@ -20,7 +20,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -31,7 +31,8 @@
cst = matRad_computeVoiContours(ct,cst);
else
for i = 1:size(cst,1)
- if isempty(cst{i,7})
+ %Check integrity and if mismatch found, recompute contours
+ if isempty(cst{i,7}) || ~iscell(cst{i,7}) || length(cst{i,7}) ~= ct.numOfCtScen
cst = matRad_computeVoiContours(ct,cst);
break
end
diff --git a/plotting/matRad_getColormap.m b/matRad/plotting/matRad_getColormap.m
similarity index 96%
rename from plotting/matRad_getColormap.m
rename to matRad/plotting/matRad_getColormap.m
index 5a9e03a14..e587f41fa 100644
--- a/plotting/matRad_getColormap.m
+++ b/matRad/plotting/matRad_getColormap.m
@@ -23,7 +23,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/plotting/matRad_plotAxisLabels.m b/matRad/plotting/matRad_plotAxisLabels.m
similarity index 56%
rename from plotting/matRad_plotAxisLabels.m
rename to matRad/plotting/matRad_plotAxisLabels.m
index 2207ca0ec..d01eec69d 100644
--- a/plotting/matRad_plotAxisLabels.m
+++ b/matRad/plotting/matRad_plotAxisLabels.m
@@ -26,64 +26,76 @@ function matRad_plotAxisLabels(axesHandle,ct,plane,slice,defaultFontSize,tickdi
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+
if ~exist('defaultFontSize','var') || isempty(defaultFontSize)
- defaultFontSize = 12;
+ defaultFontSize = matRad_cfg.gui.fontSize;
end
+ct = matRad_getWorldAxes(ct);
+
+
if ~exist('tickdist','var') || isempty(tickdist)
- tickdist = 50;
+ tickdist = abs(ct.x(end)-ct.x(1))/10;
end
+
%% Set axis labels and plot iso center
if plane == 3% Axial plane
if ~isempty(ct.resolution.x) && ~isempty(ct.resolution.y)
- set(axesHandle,'XTick',0:tickdist/ct.resolution.x:1000);
- set(axesHandle,'YTick',0:tickdist/ct.resolution.y:1000);
- set(axesHandle,'XTickLabel',0:tickdist:1000*ct.resolution.x);
- set(axesHandle,'YTickLabel',0:tickdist:1000*ct.resolution.y);
+ set(axesHandle,'XTick',linspace(0,ct.x(end)-ct.x(1),10)./ct.resolution.x);
+ set(axesHandle,'YTick',linspace(0,ct.y(end)-ct.y(1),10)./ct.resolution.y);
+ set(axesHandle,'XTickLabel',round(linspace(ct.x(1),ct.x(end),10)));
+ set(axesHandle,'YTickLabel',round(linspace(ct.y(1),ct.y(end),10)));
xlabel(axesHandle,'x [mm]','FontSize',defaultFontSize)
ylabel(axesHandle,'y [mm]','FontSize',defaultFontSize)
- title(axesHandle,['axial plane z = ' num2str(ct.resolution.z*slice) ' [mm]'],'FontSize',defaultFontSize)
+ vcoord = matRad_cubeIndex2worldCoords([1,1,slice],ct);
+ title(axesHandle,['axial plane z = ' num2str(vcoord(3)) ' [mm]'],'FontSize',defaultFontSize,'Color',matRad_cfg.gui.highlightColor);
else
xlabel(axesHandle,'x [voxels]','FontSize',defaultFontSize)
ylabel(axesHandle,'y [voxels]','FontSize',defaultFontSize)
- title(axesHandle,'axial plane','FontSize',defaultFontSize)
+ title(axesHandle,'axial plane','FontSize',defaultFontSize,'Color',matRad_cfg.gui.highlightColor)
end
elseif plane == 2 % Sagittal plane
if ~isempty(ct.resolution.y) && ~isempty(ct.resolution.z)
- set(axesHandle,'XTick',0:tickdist/ct.resolution.z:1000)
- set(axesHandle,'YTick',0:tickdist/ct.resolution.y:1000)
- set(axesHandle,'XTickLabel',0:tickdist:1000*ct.resolution.z)
- set(axesHandle,'YTickLabel',0:tickdist:1000*ct.resolution.y)
+ set(axesHandle,'XTick',linspace(0,ct.z(end)-ct.z(1),10)./ct.resolution.z);
+ set(axesHandle,'YTick',linspace(0,ct.y(end)-ct.y(1),10)./ct.resolution.y);
+ set(axesHandle,'XTickLabel',round(linspace(ct.z(1),ct.z(end),10)));
+ set(axesHandle,'YTickLabel',round(linspace(ct.y(1),ct.y(end),10)));
xlabel(axesHandle,'z [mm]','FontSize',defaultFontSize);
ylabel(axesHandle,'y [mm]','FontSize',defaultFontSize);
- title(axesHandle,['sagittal plane x = ' num2str(ct.resolution.x*slice) ' [mm]'],'FontSize',defaultFontSize)
+ vcoord = matRad_cubeIndex2worldCoords([slice,1,1],ct);
+ title(axesHandle,['sagittal plane x = ' num2str(vcoord(1)) ' [mm]'],'FontSize',defaultFontSize,'Color',matRad_cfg.gui.highlightColor);
else
xlabel(axesHandle,'z [voxels]','FontSize',defaultFontSize)
ylabel(axesHandle,'y [voxels]','FontSize',defaultFontSize)
- title(axesHandle,'sagittal plane','FontSize',defaultFontSize);
+ title(axesHandle,'sagittal plane','FontSize',defaultFontSize,'Color',matRad_cfg.gui.highlightColor);
end
elseif plane == 1 % Coronal plane
if ~isempty(ct.resolution.x) && ~isempty(ct.resolution.z)
- set(axesHandle,'XTick',0:tickdist/ct.resolution.z:1000)
- set(axesHandle,'YTick',0:tickdist/ct.resolution.x:1000)
- set(axesHandle,'XTickLabel',0:tickdist:1000*ct.resolution.z)
- set(axesHandle,'YTickLabel',0:tickdist:1000*ct.resolution.x)
+ set(axesHandle,'XTick',linspace(0,ct.z(end)-ct.z(1),10)./ct.resolution.z);
+ set(axesHandle,'YTick',linspace(0,ct.x(end)-ct.x(1),10)./ct.resolution.x);
+ set(axesHandle,'XTickLabel',round(linspace(ct.z(1),ct.z(end),10)));
+ set(axesHandle,'YTickLabel',round(linspace(ct.x(1),ct.x(end),10)));
xlabel(axesHandle,'z [mm]','FontSize',defaultFontSize)
ylabel(axesHandle,'x [mm]','FontSize',defaultFontSize)
- title(axesHandle,['coronal plane y = ' num2str(ct.resolution.y*slice) ' [mm]'],'FontSize',defaultFontSize)
+ vcoord = matRad_cubeIndex2worldCoords([1,slice,1],ct);
+ title(axesHandle,['coronal plane y = ' num2str(vcoord(2)) ' [mm]'],'FontSize',defaultFontSize,'Color',matRad_cfg.gui.highlightColor)
else
xlabel(axesHandle,'z [voxels]','FontSize',defaultFontSize)
ylabel(axesHandle,'x [voxels]','FontSize',defaultFontSize)
- title(axesHandle,'coronal plane','FontSize',defaultFontSize)
+ title(axesHandle,'coronal plane','FontSize',defaultFontSize,'Color',matRad_cfg.gui.highlightColor)
end
end
+%Apply coloring
+set(axesHandle,'XColor',matRad_cfg.gui.textColor,'YColor',matRad_cfg.gui.textColor);
+
end
diff --git a/plotting/matRad_plotColorbar.m b/matRad/plotting/matRad_plotColorbar.m
similarity index 75%
rename from plotting/matRad_plotColorbar.m
rename to matRad/plotting/matRad_plotColorbar.m
index b277f80a5..a912870c1 100644
--- a/plotting/matRad_plotColorbar.m
+++ b/matRad/plotting/matRad_plotColorbar.m
@@ -25,27 +25,29 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-v=version;
-
colormap(axesHandle,cMap);
-caxis(window);
-
-if str2double(v(1:3))>=8.5
- cBarHandle = colorbar(axesHandle,varargin{:});
-else
- cBarHandle = colorbar('peer',axesHandle,varargin{:});
-end
-
-
-
+caxis(axesHandle,window);
+%sanity check to avoid a crash
+% if window(1) < window(2)
+%
+% else
+%
+% matRad_cfg = MatRad_Config.instance();
+% matRad_cfg.dispWarning('Unsuitable display window [%d,%d] for color axis!',window(1),window(2));
+% end
+matRad_cfg = MatRad_Config.instance();
+if matRad_cfg.isMatlab && str2double(matRad_cfg.envVersion)<8.5
+ cBarHandle = colorbar('peer',axesHandle,varargin{:});
+else
+ cBarHandle = colorbar(axesHandle,varargin{:});
end
diff --git a/plotting/matRad_plotCtSlice.m b/matRad/plotting/matRad_plotCtSlice.m
similarity index 95%
rename from plotting/matRad_plotCtSlice.m
rename to matRad/plotting/matRad_plotCtSlice.m
index 20cdfba66..708a5431e 100644
--- a/plotting/matRad_plotCtSlice.m
+++ b/matRad/plotting/matRad_plotCtSlice.m
@@ -35,7 +35,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -53,6 +53,10 @@
window = [min(ctCube{cubeIdx}(:)) max(ctCube{cubeIdx}(:))];
end
+if window(2) - window(1) <= 0
+ window = [-1000 2000];
+end
+
cMapScale = size(cMap,1) - 1;
%Prepare the slice and convert it to uint8
diff --git a/plotting/matRad_plotCtSlice3D.m b/matRad/plotting/matRad_plotCtSlice3D.m
similarity index 98%
rename from plotting/matRad_plotCtSlice3D.m
rename to matRad/plotting/matRad_plotCtSlice3D.m
index 43d6fd702..59124a60e 100644
--- a/plotting/matRad_plotCtSlice3D.m
+++ b/matRad/plotting/matRad_plotCtSlice3D.m
@@ -32,7 +32,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/plotting/matRad_plotDVHBand.m b/matRad/plotting/matRad_plotDVHBand.m
new file mode 100644
index 000000000..fdb4f35d9
--- /dev/null
+++ b/matRad/plotting/matRad_plotDVHBand.m
@@ -0,0 +1,87 @@
+function matRad_plotDVHBand(nominalDVH, structureStat, doseLabel)
+% matRad_plotDVHBand to plot dose volume bands
+%
+% call
+% matRad_plotDVHBand(nominalDVH, structureStat, doseLabel)
+%
+% input
+% nominalDVH: x axis values
+% structureStat: lower bound (start of shadowing)
+% doseLabel: upper bound (end of shadowing)
+%
+% output
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if ~exist('doseLabel', 'var') || isempty(doseLabel)
+ doseLabel = 'a.u.';
+end
+
+figure;
+numOfConf = floor(size(structureStat.dvhStat.percDVH,1) / 2);
+% DVH
+doseGrid = structureStat.dvhStat.mean.doseGrid;
+
+% plot nominal plan
+[y, argmin] = cutAtArgmin(nominalDVH.volumePoints);
+x = nominalDVH.doseGrid(1:argmin);
+h(1) = plot(x,y,'LineWidth',2, 'Color', 'k', 'DisplayName', 'nominal');
+hold on;
+% plot mean
+[y, argmin] = cutAtArgmin(structureStat.dvhStat.mean.volumePoints);
+x = structureStat.dvhStat.mean.doseGrid(1:argmin);
+h(2) = plot(x,y,'--','LineWidth',2, 'Color', 'k', 'DisplayName', '\mu');
+% plot dvh confidence bands
+% colors
+colors = jet(numOfConf);
+alphaTrans = 1;
+
+hIx = numel(h);
+for j = 1:numOfConf
+ hIx = hIx + 1;
+ lIx = j;
+ hIx = size(structureStat.dvhStat.percDVH,1) - (j-1);
+ lowerLimit = structureStat.dvhStat.percDVH(lIx,:);
+ upperLimit = structureStat.dvhStat.percDVH(hIx,:);
+ confIn = structureStat.percentiles(hIx) - structureStat.percentiles(lIx);
+ confName = ['C', num2str(round(confIn * 100,0))];
+ h(hIx) = matRad_shadowPlot(doseGrid, lowerLimit, upperLimit, colors(j,:), confName, alphaTrans);
+end
+
+ylim([0 100]);
+xlabel(doseLabel);
+
+ylabel('Volume [%]');
+lh = legend('show','Location','northeastoutside');
+uistack(h(2), 'top')
+uistack(h(1), 'top')
+labels = get(legend(), 'String');
+neworder = numel(labels):-1:1;
+plots = flipud(get(gca, 'children'));
+
+% Now re-create the legend
+legend(plots(neworder), labels(neworder))
+
+drawnow;
+hold off;
+
+
+function [y, argmin] = cutAtArgmin(x)
+ [~,argmin] = min(x);
+ y = x(1:argmin);
+end
+
+end
+
diff --git a/plotting/matRad_plotDoseSlice.m b/matRad/plotting/matRad_plotDoseSlice.m
similarity index 95%
rename from plotting/matRad_plotDoseSlice.m
rename to matRad/plotting/matRad_plotDoseSlice.m
index 7a51db16a..1c49dd991 100644
--- a/plotting/matRad_plotDoseSlice.m
+++ b/matRad/plotting/matRad_plotDoseSlice.m
@@ -45,7 +45,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -65,6 +65,12 @@
window = [min(doseCube(:)) max(doseCube(:))];
end
+% window sanity check
+if window(2) - window(1) <= 0
+ window = [0 2];
+end
+
+%store maximum dose for thresholding
maxDose = max(doseCube(:));
cMapScale = size(cMap,1) - 1;
diff --git a/plotting/matRad_plotDoseSlice3D.m b/matRad/plotting/matRad_plotDoseSlice3D.m
similarity index 97%
rename from plotting/matRad_plotDoseSlice3D.m
rename to matRad/plotting/matRad_plotDoseSlice3D.m
index 6d368d7ac..83ad41c42 100644
--- a/plotting/matRad_plotDoseSlice3D.m
+++ b/matRad/plotting/matRad_plotDoseSlice3D.m
@@ -44,7 +44,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -76,7 +76,7 @@
[xMesh,zMesh] = meshgrid(coords{2},coords{3});
yMesh = slice*ct.resolution.x*ones(size(xMesh));
%dose_slice = uint8(cMapScale*(squeeze(doseCube(slice,:,:)) - window(1))/(window(2)-window(1)));
- doseSlice = permute(squeeze(doseCube(slice,:,:)),[2 1]);
+ dose_slice = permute(squeeze(doseCube(slice,:,:)),[2 1]);
elseif plane == 2 % sagittal plane
[yMesh,zMesh] = meshgrid(coords{1},coords{3});
xMesh = slice*ct.resolution.y*ones(size(yMesh));
diff --git a/plotting/matRad_plotIsoCenterMarker.m b/matRad/plotting/matRad_plotIsoCenterMarker.m
similarity index 95%
rename from plotting/matRad_plotIsoCenterMarker.m
rename to matRad/plotting/matRad_plotIsoCenterMarker.m
index d63c9eb7e..e857151d8 100644
--- a/plotting/matRad_plotIsoCenterMarker.m
+++ b/matRad/plotting/matRad_plotIsoCenterMarker.m
@@ -29,7 +29,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -40,7 +40,8 @@
for i = 1:size(uniqueIsoCenters,1)
- vIsoCenter = round(uniqueIsoCenters(i,:)./[ct.resolution.x ct.resolution.y ct.resolution.z]);
+ vIsoCenter = matRad_world2cubeIndex(pln.propStf.isoCenter(i,:), ct);
+ vIsoCenter = vIsoCenter([2 1 3]);
if plane == 3% Axial plane
vIsoCenterPlot = [vIsoCenter(1) vIsoCenter(2)];
if vIsoCenter(3) == slice
@@ -50,7 +51,7 @@
end
elseif plane == 2
vIsoCenterPlot = [vIsoCenter(3) vIsoCenter(2)];
- if vIsoCenter(2) == slice
+ if vIsoCenter(1) == slice
isoCenterDirection = 0;
else
isoCenterDirection = sign(double(vIsoCenter(1) > slice) - 0.5);
@@ -58,7 +59,7 @@
elseif plane == 1
vIsoCenterPlot = [vIsoCenter(3) vIsoCenter(1)];
- if vIsoCenter(1) == slice
+ if vIsoCenter(2) == slice
isoCenterDirection = 0;
else
isoCenterDirection = sign(double(vIsoCenter(2) > slice) - 0.5);
diff --git a/plotting/matRad_plotIsoDose3D.m b/matRad/plotting/matRad_plotIsoDose3D.m
similarity index 96%
rename from plotting/matRad_plotIsoDose3D.m
rename to matRad/plotting/matRad_plotIsoDose3D.m
index cfc8a333a..c36d65fe9 100644
--- a/plotting/matRad_plotIsoDose3D.m
+++ b/matRad/plotting/matRad_plotIsoDose3D.m
@@ -25,7 +25,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/plotting/matRad_plotIsoDoseLines.m b/matRad/plotting/matRad_plotIsoDoseLines.m
similarity index 85%
rename from plotting/matRad_plotIsoDoseLines.m
rename to matRad/plotting/matRad_plotIsoDoseLines.m
index 43e819235..195ffb8ae 100644
--- a/plotting/matRad_plotIsoDoseLines.m
+++ b/matRad/plotting/matRad_plotIsoDoseLines.m
@@ -4,10 +4,8 @@
% itself
%
% call
-% isoLineHandles = matRad_plotIsoDoseLines(axesHandle,doseCube,isoContours,isoLevels,plotLabels,plane,slice,cMap)
-% isoLineHandles = matRad_plotIsoDoseLines(axesHandle,doseCube,isoContours,isoLevels,plotLabels,plane,slice,window)
-% isoLineHandles = matRad_plotIsoDoseLines(axesHandle,doseCube,isoContours,isoLevels,plotLabels,plane,slice,cMap,window)
-% isoLineHandles = matRad_plotIsoDoseLines(axesHandle,doseCube,isoContours,isoLevels,plotLabels,plane,slice,cMap,window, ...)
+% isoLineHandles =
+% matRad_plotIsoDoseLines(axesHandle,doseCube,isoContours,isoLevels,plotLabels,plane,slice,...)
%
% input
% axesHandle handle to axes the slice should be displayed in
@@ -38,7 +36,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -86,7 +84,7 @@
colors = squeeze(ind2rgb(isoColorLevel,cMap));
axes(axesHandle);
-hold on;
+hold(axesHandle,'on');
%Check if there is a contour in the plane
if any(isoContours{slice,plane}(:))
@@ -100,7 +98,7 @@
else
color = unique(colors,'rows');
end
- isoLineHandles(end+1) = line(isoContours{slice,plane}(1,lower+1:lower+steps),...
+ isoLineHandles{end+1} = line(isoContours{slice,plane}(1,lower+1:lower+steps),...
isoContours{slice,plane}(2,lower+1:lower+steps),...
'Color',color,'Parent',axesHandle,varargin{:});
if plotLabels
@@ -113,6 +111,6 @@
end
end
-hold off;
+hold(axesHandle,'off');
end
diff --git a/plotting/matRad_plotIsoDoseLines3D.m b/matRad/plotting/matRad_plotIsoDoseLines3D.m
similarity index 98%
rename from plotting/matRad_plotIsoDoseLines3D.m
rename to matRad/plotting/matRad_plotIsoDoseLines3D.m
index d88f773ff..5eb67edc0 100644
--- a/plotting/matRad_plotIsoDoseLines3D.m
+++ b/matRad/plotting/matRad_plotIsoDoseLines3D.m
@@ -39,7 +39,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -94,7 +94,7 @@
isoLineHandles = contourslice(axesHandle,xMesh,yMesh,zMesh,doseCube,slices{[1 2 3]},isoLevels);
else
axes(axesHandle);
- hold on;
+ hold(axesHandle,'on');
for s = 1:numel(sliceIndices)
currSlice = sliceIndices(s);
@@ -157,7 +157,7 @@
end
end
- hold off;
+ hold(axesHandle,'off');
end
end
diff --git a/plotting/matRad_plotPlan3D.m b/matRad/plotting/matRad_plotPlan3D.m
similarity index 99%
rename from plotting/matRad_plotPlan3D.m
rename to matRad/plotting/matRad_plotPlan3D.m
index 1932e5723..ce5be996f 100644
--- a/plotting/matRad_plotPlan3D.m
+++ b/matRad/plotting/matRad_plotPlan3D.m
@@ -27,7 +27,7 @@ function matRad_plotPlan3D(axesHandle,pln,stf)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/plotting/matRad_plotProjectedGantryAngles.m b/matRad/plotting/matRad_plotProjectedGantryAngles.m
similarity index 60%
rename from plotting/matRad_plotProjectedGantryAngles.m
rename to matRad/plotting/matRad_plotProjectedGantryAngles.m
index a82e377bf..a72adb033 100644
--- a/plotting/matRad_plotProjectedGantryAngles.m
+++ b/matRad/plotting/matRad_plotProjectedGantryAngles.m
@@ -23,7 +23,7 @@ function matRad_plotProjectedGantryAngles(axesHandle,pln,ct,plane)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -35,33 +35,33 @@ function matRad_plotProjectedGantryAngles(axesHandle,pln,ct,plane)
meanIsoCenter = mean(pln.propStf.isoCenter,1);
- xOffset = meanIsoCenter(1)/ct.resolution.x;
- yOffset = meanIsoCenter(2)/ct.resolution.y;
+ cubeIso = matRad_world2cubeIndex(meanIsoCenter,ct);
% find radius of inner circle from isocenter
- r = 0.8*min([abs([1 ct.cubeDim(1)]-xOffset) abs([1 ct.cubeDim(2)]-yOffset)]);
+ r = 0.8*min([abs([1 ct.cubeDim(1)]-cubeIso(1)) abs([1 ct.cubeDim(2)]-cubeIso(2))]);
% coordinates of circle
- x = r*cosd(0:360)+xOffset;
- y = r*sind(0:360)+yOffset;
+ x = r*cosd(0:360)+cubeIso(2);
+ y = r*sind(0:360)+cubeIso(1);
gantryAngleVisColor = 'w';
+ hold(axesHandle,'on');
plot(axesHandle,x,y,'LineWidth',1,'Color',gantryAngleVisColor)
% add text
- txt = '180�';
- text(axesHandle,1.1*r*sind(0)+xOffset,1.1*r*cosd(0)+yOffset,txt,'Color',gantryAngleVisColor)
- txt = '90�';
- text(axesHandle,1.1*r*sind(90)+xOffset,1.1*r*cosd(90)+yOffset,txt,'Color',gantryAngleVisColor)
- txt = '0�';
- text(axesHandle,1.1*r*sind(180)+xOffset,1.1*r*cosd(180)+yOffset,txt,'Color',gantryAngleVisColor)
- txt = '270�';
- text(axesHandle,1.22*r*sind(270)+xOffset,1.22*r*cosd(270)+yOffset,txt,'Color',gantryAngleVisColor)
+ txt = '180°';
+ text(axesHandle,1.1*r*sind(0)+cubeIso(2),1.1*r*cosd(0)+cubeIso(1),txt,'Color',gantryAngleVisColor)
+ txt = '90°';
+ text(axesHandle,1.1*r*sind(90)+cubeIso(2),1.1*r*cosd(90)+cubeIso(1),txt,'Color',gantryAngleVisColor)
+ txt = '0°';
+ text(axesHandle,1.1*r*sind(180)+cubeIso(2),1.1*r*cosd(180)+cubeIso(1),txt,'Color',gantryAngleVisColor)
+ txt = '270°';
+ text(axesHandle,1.22*r*sind(270)+cubeIso(2),1.22*r*cosd(270)+cubeIso(1),txt,'Color',gantryAngleVisColor)
% plot gantry angles
for i = 1:numel(pln.propStf.gantryAngles)
- plot(axesHandle,[0 r*sind(180-pln.propStf.gantryAngles(i))]+xOffset,[0 r*cosd(180-pln.propStf.gantryAngles(i))]+yOffset,'LineWidth',1,'Color',gantryAngleVisColor)
+ plot(axesHandle,[0 r*sind(180-pln.propStf.gantryAngles(i))]+cubeIso(2),[0 r*cosd(180-pln.propStf.gantryAngles(i))]+cubeIso(1),'LineWidth',1,'Color',gantryAngleVisColor)
end
end
\ No newline at end of file
diff --git a/plotting/matRad_plotVoiContourSlice.m b/matRad/plotting/matRad_plotVoiContourSlice.m
similarity index 83%
rename from plotting/matRad_plotVoiContourSlice.m
rename to matRad/plotting/matRad_plotVoiContourSlice.m
index 02062341e..7f483c6e6 100644
--- a/plotting/matRad_plotVoiContourSlice.m
+++ b/matRad/plotting/matRad_plotVoiContourSlice.m
@@ -1,4 +1,4 @@
-function [voiContourHandles] = matRad_plotVoiContourSlice(axesHandle,cst,ct,ctIndex,selection,plane,slice,cMap,varargin)
+function [voiContourHandles, visibleOnSlice] = matRad_plotVoiContourSlice(axesHandle,cst,ct,ctIndex,selection,plane,slice,cMap,varargin)
% matRad function that plots the contours of the segmentations given in cst
%
% call
@@ -34,7 +34,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -61,7 +61,7 @@
if isempty(selection) || numel(selection) ~= size(cst,1)
selection = true(size(cst,1),1);
end
-
+visibleOnSlice = false(size(cst,1),1);
voiContourHandles = cell(0);
switch env
case 'MATLAB'
@@ -77,7 +77,14 @@
%Check for precalculated contours
C =[];
if size(cst,2) >= 7 && ~isempty(cst{s,7})
- C = cst{s,7}{slice,plane};
+ %Downwards compatibility
+ if (numel(cst{s,7}) == 1 && ctIndex > numel(cst{s,7})) || ~iscell(cst{s,7}{ctIndex})
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning("Recognizing old cst (<= matRad 2.10.1)! Format of storing contours has changed, contours on multiple ct scenarios might be wrong!");
+ C = cst{s,7}{slice,plane};
+ else
+ C = cst{s,7}{ctIndex}{slice,plane};
+ end
else
%If we do not have precomputed contours available, then compute them
mask = zeros(ct.cubeDim);
@@ -104,7 +111,7 @@
if any(C(:))
lower = 1; % lower marks the beginning of a section
while lower-1 ~= size(C,2)
- hold on
+ hold(axesHandle,'on') %hold on
steps = C(2,lower); % number of elements of current line section
tmpLineHandle(end+1) = line(C(1,lower+1:lower+steps),...
C(2,lower+1:lower+steps),...
@@ -113,6 +120,7 @@
lower = lower+steps+1;
end
voiContourHandles{end+1} = tmpLineHandle;
+ visibleOnSlice(s) = true;
else
% create empty line object
voiContourHandles{end+1} = {};
diff --git a/plotting/matRad_plotVois3D.m b/matRad/plotting/matRad_plotVois3D.m
similarity index 88%
rename from plotting/matRad_plotVois3D.m
rename to matRad/plotting/matRad_plotVois3D.m
index 56eaa5ada..1bf2204c8 100644
--- a/plotting/matRad_plotVois3D.m
+++ b/matRad/plotting/matRad_plotVois3D.m
@@ -28,7 +28,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -41,23 +41,26 @@
%Use stored colors or colormap?
if nargin < 5 || isempty(cMap)
+ for i = 1:size(cst,1)
+ if isfield(cst{i,5},'visibleColor')
+ voiColors(i,:) = cst{i,5}.visibleColor;
+ else
+ voiColors(i,:) = [0 0 0];
+ end
+ end
+else
cMapScale = size(cMap,1)-1;
%determine colors
voiColors = cMap(round(linspace(1,cMapScale,size(cst,1))),:);
-else
- for i = 1:size(cst,1)
- voiColors(i,:) = cst{i,5}.visibleColor;
- end
end
+%Check if there's a subselection of VOIs
if nargin < 4 || isempty(selection) || numel(selection) ~= size(cst,1)
selection = logical(ones(size(cst,1),1));
end
-cMapScale = size(cMap,1)-1;
-
axes(axesHandle);
-wasHold = ishold();
+wasHold = ishold(axesHandle);
hold(axesHandle,'on');
diff --git a/matRad/plotting/matRad_shadowPlot.m b/matRad/plotting/matRad_shadowPlot.m
new file mode 100644
index 000000000..fed2e8e7d
--- /dev/null
+++ b/matRad/plotting/matRad_shadowPlot.m
@@ -0,0 +1,62 @@
+function s = matRad_shadowPlot(x, yLow, yUp, color, legendName, alphaTrans)
+% shadowPlot to plot confidence bands mainly
+%
+% call
+% shadowPlot(x, yLow, yUp, color, legendName, alphaTrans)
+%
+% input
+% x: x axis values
+% yLow: lower bound (start of shadowing)
+% yUp: upper bound (end of shadowing)
+% color: color as [R G B]
+% legendName: legendname to be shown in the plot
+% alphaTrans: transparency
+%
+% output
+% axesHandle
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% set alpha value to a default if not parsed
+if ~exist('alphaTrans', 'var') || isempty(alphaTrans)
+ alphaTrans = .3;
+end
+
+% x, yLow, yUp has to be col vector
+if size(x, 1) == 1
+ x = x';
+end
+if size(yLow, 1) == 1
+ yLow = yLow';
+end
+if size(yUp, 1) == 1
+ yUp = yUp';
+end
+
+% check for nan
+if any(isnan(yLow)) || any(isnan(yUp))
+ s = gca;
+ return
+end
+
+% no shadow under the lower limit line
+sThick.low = 0 * yLow;
+sThick.up = yUp - yLow;
+
+s = fill([x;flipud(x)],[yLow - sThick.low;flipud(yLow + sThick.up)], color,'linestyle','none', 'DisplayName', legendName);
+alpha(s, alphaTrans)
+hold on;
+
+end % eof
diff --git a/matRad_rayTracing.m b/matRad/rayTracing/matRad_rayTracing.m
similarity index 89%
rename from matRad_rayTracing.m
rename to matRad/rayTracing/matRad_rayTracing.m
index ee2f1e697..87cd3df98 100644
--- a/matRad_rayTracing.m
+++ b/matRad/rayTracing/matRad_rayTracing.m
@@ -1,9 +1,9 @@
-function radDepthV = matRad_rayTracing(stf,ct,V,rot_coordsV,lateralCutoff)
-% matRad visualization of two-dimensional dose distributions on ct
-% including segmentation
+function [radDepthV, radDepthCube] = matRad_rayTracing(stf,ct,V,rot_coordsV,lateralCutoff)
+% matRad visualization of two-dimensional dose distributions on ct including
+% segmentation
%
% call
-% radDepthV = matRad_rayTracing(stf,ct,V,rot_coordsV,lateralCutoff)
+% [radDepthV, radDepthCube] = matRad_rayTracing(stf,ct,V,rot_coordsV,lateralCutoff)
%
% input
% stf: matRad steering information struct of one beam
@@ -14,7 +14,8 @@
%
% output
-% radDepthV: radiological depth inside the patient
+% radDepthV: radiological depth inside the patient
+% radDepthCube: radiological depth in whole ct
%
% References
% [1] http://www.sciencedirect.com/science/article/pii/S1120179711001359
@@ -25,7 +26,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -86,8 +87,10 @@
% perform ray tracing over all rays
for i = 1:size(rayMx_world,1)
+ cubeIsoCenter = matRad_world2cubeCoords(stf.isoCenter,ct);
+
% run siddon ray tracing algorithm
- [~,l,rho,~,ixHitVoxel] = matRad_siddonRayTracer(stf.isoCenter, ...
+ [~,l,rho,~,ixHitVoxel] = matRad_siddonRayTracer(cubeIsoCenter, ...
ct.resolution, ...
stf.sourcePoint, ...
rayMx_world(i,:), ...
@@ -130,3 +133,4 @@
radDepthV{i} = radDepthCube{i}(V);
end
+
diff --git a/matRad_siddonRayTracer.m b/matRad/rayTracing/matRad_siddonRayTracer.m
similarity index 87%
rename from matRad_siddonRayTracer.m
rename to matRad/rayTracing/matRad_siddonRayTracer.m
index 8fde44baf..c1cc9313e 100644
--- a/matRad_siddonRayTracer.m
+++ b/matRad/rayTracing/matRad_siddonRayTracer.m
@@ -1,10 +1,11 @@
-function [alphas,l,rho,d12,ix] = matRad_siddonRayTracer(isocenter, ...
+function [alphas,l,rho,d12,ix] = matRad_siddonRayTracer(isocenterCube, ...
resolution, ...
sourcePoint, ...
targetPoint, ...
cubes)
% siddon ray tracing through 3D cube to calculate the radiological depth
-% according to Siddon 1985 Medical Physics
+% according to Siddon 1985 Medical Physics. The raytracer expects the
+% isocenter in cube coordinates!
%
% call
% [alphas,l,rho,d12,vis] = matRad_siddonRayTracer(isocenter, ...
@@ -14,7 +15,7 @@
% cubes)
%
% input
-% isocenter: isocenter within cube [voxels]
+% isocenterCube: isocenter in cube coordinates [mm]
% resolution: resolution of the cubes [mm/voxel]
% sourcePoint: source point of ray tracing
% targetPoint: target point of ray tracing
@@ -39,7 +40,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -50,8 +51,8 @@
% works with negatives values. This put (resolution.x,resolution.y,resolution.z)
% in the center of first voxel
-sourcePoint = sourcePoint + isocenter;
-targetPoint = targetPoint + isocenter;
+sourcePoint = sourcePoint + isocenterCube;
+targetPoint = targetPoint + isocenterCube;
% Save the numbers of planes.
[yNumPlanes, xNumPlanes, zNumPlanes] = size(cubes{1});
@@ -75,9 +76,36 @@
yPlane_end = (yNumPlanes - .5)*resolution.y;
zPlane_end = (zNumPlanes - .5)*resolution.z;
+% Calc insersection point with CT cube planes
+tvalues = ([xPlane_1,yPlane_1,zPlane_1] - sourcePoint)./ (targetPoint - sourcePoint);
+tvalues = [tvalues,([xPlane_end,yPlane_end,zPlane_end] - sourcePoint)./ (targetPoint - sourcePoint)];
+
+%check if intersection point within CT
+doesHit = false;
+for t = tvalues
+ p = sourcePoint + t*(targetPoint - sourcePoint);
+ lowerPlanes = [xPlane_1,yPlane_1,zPlane_1] - sqrt(eps);
+ upperPlanes = [xPlane_end,yPlane_end,zPlane_end] + sqrt(eps);
+ if all(p > lowerPlanes & p < upperPlanes)
+ doesHit = true;
+ continue;
+ end
+end
+
+%If it does not hit, write empty values
+if ~doesHit
+ alphas = [];
+ l = [];
+ for i = 1:numel(cubes)
+ rho{i} = [];
+ end
+ ix = [];
+ return;
+end
+
% eq 4
% Calculate parametrics values of \alpha_{min} and \alpha_{max} for every
-% axis, intersecting the ray with the sides of the CT.
+% axis, intersecting the ray with the sides of the CT.
if targetPoint(1) ~= sourcePoint(1)
aX_1 = (xPlane_1 - sourcePoint(1)) / (targetPoint(1) - sourcePoint(1));
aX_end = (xPlane_end - sourcePoint(1)) / (targetPoint(1) - sourcePoint(1));
@@ -210,7 +238,7 @@
k(k>zNumPlanes-1) = zNumPlanes-1;
% Convert to linear indices
-ix = j + (i-1)*size(cubes{1},1) + (k-1)*size(cubes{1},1)*size(cubes{1},2);
+ix = j + (i-1)*size(cubes{1},1) + (k-1)*size(cubes{1},1)*size(cubes{1},2);
% obtains the values from cubes
rho = cell(numel(cubes),1);
diff --git a/matRad/scenarios/matRad_GriddedScenariosAbstract.m b/matRad/scenarios/matRad_GriddedScenariosAbstract.m
new file mode 100644
index 000000000..fd88abf51
--- /dev/null
+++ b/matRad/scenarios/matRad_GriddedScenariosAbstract.m
@@ -0,0 +1,282 @@
+classdef (Abstract) matRad_GriddedScenariosAbstract < matRad_ScenarioModel
+ %UNTITLED Summary of this class goes here
+ % Detailed explanation goes here
+
+ properties (AbortSet = true)
+ %includeNominalScenario = true;
+ combinations = 'none'; %Can be 'none', 'shift', 'all' to ontrol creation of worst case combinations
+ combineRange = true; %Wether to treat absolute & relative range as one shift or as separate scenarios
+ end
+
+ %Each subclass needs to define how many gridpoints it uses and if this
+ %can be set or not
+ properties (Abstract)
+ numOfSetupGridPoints;
+ numOfRangeGridPoints;
+ end
+
+ properties (Constant)
+ validCombinationTypes = {'all','none','shift'};
+ end
+
+ methods
+ function this = matRad_GriddedScenariosAbstract(ct)
+
+ if nargin == 0
+ superclassArgs = {};
+ else
+ superclassArgs = {ct};
+ end
+
+ this@matRad_ScenarioModel(superclassArgs{:});
+
+ %TODO: We could do this automatically in the superclass
+ %Octave 5 has a bug there and throws an error
+ %this.updateScenarios();
+ end
+
+ function set.combineRange(this,combineRange_)
+ valid = isscalar(combineRange_) && (isnumeric(combineRange_) || islogical(combineRange_));
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for combineRange! Needs to be a boolean / logical value!');
+ end
+ this.combineRange = combineRange_;
+ this.updateScenarios();
+ end
+
+ %% set methods
+ %{
+ function set.includeNominalScenario(this,includeNomScen)
+ valid = isscalar(includeNomScen) && (isnumeric(includeNomScen) || islogical(includeNomScen));
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for includeNominalScenario! Needs to be a boolean / logical value!');
+ end
+ this.includeNominalScenario = includeNomScen;
+ this.updateScenarios();
+ end
+ %}
+
+ function set.combinations(this,combinations_)
+ valid = any(strcmp(combinations_,this.validCombinationTypes));
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for combinations! Needs to be one of the strings %s!',strjoin(this.validCombinationTypes,' / '));
+ end
+ this.combinations = combinations_;
+ this.updateScenarios();
+ end
+
+ function scenarios = updateScenarios(this)
+ matRad_cfg = MatRad_Config.instance();
+
+ %
+ this.numOfCtScen = size(this.ctScenProb,1);
+
+ %Get the maximum, i.e., worst case shifts
+ wcSetupShifts = this.wcSigma * this.shiftSD;
+
+ %% Create gridded setup shifts
+ %Create grid vectors for setup shifts
+ setupShiftGrid = zeros(this.numOfSetupGridPoints,numel(wcSetupShifts));
+ %{
+ if mod(this.numOfSetupGridPoints,2) == 0 && this.includeNominalScenario
+ matRad_cfg.dispWarning('Obtaining Setup Shifts: Including the nominal scenario with even number of grid points creates asymmetrical shifts!');
+ end
+ %}
+
+ for i = 1:numel(wcSetupShifts)
+ setupShiftGrid(:,i) = linspace(-wcSetupShifts(i),wcSetupShifts(i),this.numOfSetupGridPoints);
+ %{
+ if this.includeNominalScenario
+
+ [~,ix] = min(abs(setupShiftGrid(:,i)));
+ setupShiftGrid(ix,i) = 0;
+ end
+ %}
+ end
+
+ %Now create vector of all shifts for different combinatorial
+ %settings
+ switch this.combinations
+ case 'none'
+ %Create independent shifts
+ griddedSetupShifts = [];
+ for i=1:size(setupShiftGrid,2)
+ tmpGrid = zeros(size(setupShiftGrid,1),3);
+ tmpGrid(:,i) = setupShiftGrid(:,i);
+ griddedSetupShifts = [griddedSetupShifts; tmpGrid];
+ end
+ case {'shift','all'}
+ [X,Y,Z] = meshgrid(setupShiftGrid(:,1),setupShiftGrid(:,2),setupShiftGrid(:,3));
+ griddedSetupShifts = [X(:), Y(:), Z(:)];
+ otherwise
+ matRad_cfg.dispError('Invalid value for combinations! This sanity check should never be reached!');
+ end
+
+ griddedSetupShifts = matRad_ImportanceScenarios.uniqueStableRowsCompat(griddedSetupShifts);
+ shiftNomScenIx = find(all(griddedSetupShifts == zeros(1,3),2));
+
+ if ~isempty(shiftNomScenIx) %|| this.includeNominalScenario
+ if ~isempty(shiftNomScenIx)
+ griddedSetupShifts(shiftNomScenIx,:) = [];
+ end
+ griddedSetupShifts = [0 0 0; griddedSetupShifts];
+ end
+
+ this.totNumShiftScen = size(griddedSetupShifts,1);
+
+ %% Create gridded range shifts
+ %Obtain worst case range shifts
+ wcRangeShifts = this.wcSigma * [this.rangeAbsSD this.rangeRelSD./100];
+
+ rangeShiftGrid = zeros(this.numOfRangeGridPoints,numel(wcRangeShifts));
+ %{
+ if mod(this.numOfRangeGridPoints,2) == 0 && this.includeNominalScenario
+ matRad_cfg.dispWarning('Obtaining Range Shifts: Including the nominal scenario with even number of grid points creates asymmetrical shifts!');
+ end
+ %}
+
+ for i = 1:numel(wcRangeShifts)
+ rangeShiftGrid(:,i) = linspace(-wcRangeShifts(i),wcRangeShifts(i),this.numOfRangeGridPoints);
+
+ %{
+ if this.includeNominalScenario
+ [~,ix] = min(abs(rangeShiftGrid(:,i)));
+ rangeShiftGrid(ix,i) = 0;
+ end
+ %}
+ end
+
+ if this.combineRange
+ griddedRangeShifts = rangeShiftGrid;
+ else
+ [rngAbs,rngRel] = meshgrid(rangeShiftGrid(:,1),rangeShiftGrid(:,2));
+ griddedRangeShifts = [rngAbs(:),rngRel(:)];
+ end
+
+ %Remove duplicate scenarios and update number of shifts
+ griddedRangeShifts = this.uniqueStableRowsCompat(griddedRangeShifts);
+
+ rangeNomScenIx = find(all(griddedRangeShifts == zeros(1,2),2));
+
+ if ~isempty(rangeNomScenIx) %|| this.includeNominalScenario
+ if ~isempty(rangeNomScenIx)
+ griddedRangeShifts(rangeNomScenIx,:) = [];
+ end
+ griddedRangeShifts = [0 0; griddedRangeShifts];
+ end
+
+ this.totNumRangeScen = size(griddedRangeShifts,1);
+
+ %Aggregate scenarios
+ switch this.combinations
+ case {'none','shift'}
+ scenarios = zeros(this.totNumShiftScen + this.totNumRangeScen,5);
+ scenarios(1:this.totNumShiftScen,1:3) = griddedSetupShifts;
+ scenarios(this.totNumShiftScen+1:this.totNumShiftScen+this.totNumRangeScen,4:5) = griddedRangeShifts;
+
+ %create the linear mask of scenarios
+ linearMaskTmp = ones(size(scenarios,1),3);
+ linearMaskTmp(1:this.totNumShiftScen,2) = (1:this.totNumShiftScen)';
+ linearMaskTmp(this.totNumShiftScen+1:this.totNumShiftScen+this.totNumRangeScen,3) = (1:this.totNumRangeScen)';
+
+ [scenarios,ia] = matRad_ImportanceScenarios.uniqueStableRowsCompat(scenarios);
+ linearMaskTmp = linearMaskTmp(ia,:);
+
+ case 'all'
+ %Prepare scenario matrix by replicating shifts
+ %with the number of range scenarios
+ scenarios = repmat(griddedSetupShifts,this.totNumRangeScen,1);
+ scenarios = [scenarios zeros(size(scenarios,1),2)];
+
+ %create the linear mask of scenarios
+ linearMaskTmp = ones(size(scenarios,1),3);
+ for r = 1:this.totNumRangeScen
+ offset = (r-1)*this.totNumShiftScen;
+ ixR = (offset + 1):(offset + this.totNumShiftScen);
+ scenarios(ixR,4:5) = repmat(griddedRangeShifts(r,:),this.totNumShiftScen,1);
+
+ %Set linear mask
+ linearMaskTmp(ixR,2) = (1:this.totNumShiftScen)';
+ linearMaskTmp(ixR,3) = r;
+ end
+
+
+ %create the linear mask of scenarios
+ [scenarios,ia] = matRad_ImportanceScenarios.uniqueStableRowsCompat(scenarios);
+ linearMaskTmp = linearMaskTmp(ia,:);
+ otherwise
+ matRad_cfg.dispError('Invalid value for combinations! This sanity check should never be reached!');
+ end
+
+ %if ~this.includeNominalScenario
+ % nomScen = all(scenarios == zeros(1,5),2);
+ % scenarios(nomScen,:) = [];
+ % linearMaskTmp(nomScen,:) = [];
+ %end
+
+ %Handle 4D phases
+ phases = repmat(this.ctScenProb(:,1)',size(scenarios,1),1);
+ phases = phases(:);
+ scenarios = horzcat(phases, repmat(scenarios,[this.numOfCtScen 1]));
+ linearMaskTmp = repmat(linearMaskTmp,this.numOfCtScen,1);
+ linearMaskTmp(:,1) = phases;
+ this.ctScenIx = phases;
+
+ %Finalize meta information
+ this.totNumScen = size(scenarios,1);
+
+ this.relRangeShift = scenarios(:,6);
+ this.absRangeShift = scenarios(:,5);
+ this.isoShift = scenarios(:,2:4);
+
+ this.maxAbsRangeShift = max(this.absRangeShift);
+ this.maxRelRangeShift = max(this.relRangeShift);
+
+ this.scenMask = false(this.numOfAvailableCtScen,this.totNumShiftScen,this.totNumRangeScen);
+
+ this.scenForProb = scenarios;
+ this.linearMask = linearMaskTmp;
+
+ maskIx = sub2ind(size(this.scenMask),linearMaskTmp(:,1),linearMaskTmp(:,2),linearMaskTmp(:,3));
+ this.scenMask(maskIx) = true;
+
+ %Get Scenario probability
+ %First, we use the Gaussian Uncertainty model for range and
+ %setup
+ Sigma = diag([this.shiftSD,this.rangeAbsSD,this.rangeRelSD./100].^2);
+ d = size(Sigma,1);
+ [cs,p] = chol(Sigma);
+
+ tmpScenProb = (2*pi)^(-d/2) * exp(-0.5*sum((scenarios(:,2:end)/cs).^2, 2)) / prod(diag(cs));
+
+ %Now we combine with the 4D ct phase probabilities (multiply)
+ tmpPhaseProb = arrayfun(@(phase) this.ctScenProb(find(this.ctScenProb(:,1) == phase),2),phases);
+
+ %Finalize probabilities
+ this.scenProb = tmpPhaseProb .* tmpScenProb;
+ this.scenWeight = this.scenProb./sum(this.scenProb);
+
+ %TODO: Discard scenarios with probability 0?
+ end
+ end
+
+ methods (Static)
+ function [uniqueStableRows,ia] = uniqueStableRowsCompat(values)
+ %This is a compatability wrapper to call unique without sorting
+
+ matRad_cfg = MatRad_Config.instance();
+
+ if matRad_cfg.isOctave
+ %https://stackoverflow.com/questions/37828894/
+ [~,ia,~] = unique(values,'rows','first');
+ ia = sort(ia);
+ uniqueStableRows = values(ia,:);
+ else
+ [uniqueStableRows,ia] = unique(values,'rows','stable');
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/matRad/scenarios/matRad_ImportanceScenarios.m b/matRad/scenarios/matRad_ImportanceScenarios.m
new file mode 100644
index 000000000..cee133084
--- /dev/null
+++ b/matRad/scenarios/matRad_ImportanceScenarios.m
@@ -0,0 +1,77 @@
+classdef matRad_ImportanceScenarios < matRad_GriddedScenariosAbstract
+% matRad_ImportanceScenarios
+% Implements gridded importance scenarios, i.e., weighted according to a
+% probability distribution. It is not advised to create "combined" grids
+% with a large number of grid-points as the curse of dimensionality will
+% quickly break memory requirements when putting this in to dose influence
+% matrix computation.
+%
+%
+% constructor
+% matRad_ImportanceScenarios()
+% matRad_ImportanceScenarios(ct)
+%
+% input
+% ct: ct cube
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (AbortSet = true)
+ numOfSetupGridPoints = 9;
+ numOfRangeGridPoints = 9;
+ end
+
+ properties (SetAccess=protected)
+ shortName = 'impScen';
+ name = 'Gridded Scenarios with Importance Weights';
+
+ end
+
+ methods
+ function this = matRad_ImportanceScenarios(ct)
+ if nargin == 0
+ superclassArgs = {};
+ else
+ superclassArgs = {ct};
+ end
+
+ this@matRad_GriddedScenariosAbstract(superclassArgs{:});
+
+ %TODO: We could do this automatically in the superclass
+ %Octave 5 has a bug there and throws an error
+ this.updateScenarios();
+ end
+
+ function set.numOfSetupGridPoints(this,numGridPoints)
+ valid = isscalar(numGridPoints) && numGridPoints > 0;
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid number of setup grid points, needs to be a positive scalar!');
+ end
+ this.numOfSetupGridPoints = inumGridPoints;
+ this.updateScenarios();
+ end
+
+ function set.numOfRangeGridPoints(this,numGridPoints)
+ valid = isscalar(numGridPoints) && numGridPoints > 0;
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid number of range grid points, needs to be a positive scalar!');
+ end
+ this.numOfRAngeGridPoints = inumGridPoints;
+ this.updateScenarios();
+ end
+ end
+end
+
diff --git a/matRad/scenarios/matRad_NominalScenario.m b/matRad/scenarios/matRad_NominalScenario.m
new file mode 100644
index 000000000..23912443f
--- /dev/null
+++ b/matRad/scenarios/matRad_NominalScenario.m
@@ -0,0 +1,103 @@
+classdef matRad_NominalScenario < matRad_ScenarioModel
+% matRad_RandomScenarios
+% Implements a single nominal planning scenario
+%
+% constructor
+% matRad_NominalScenario()
+% matRad_NominalScenario(ct)
+%
+% input
+% ct: ct cube
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (SetAccess = protected)
+ shortName = 'nomScen';
+ name = 'Nominal Scenario';
+ end
+
+ methods
+ function this = matRad_NominalScenario(ct)
+ if nargin == 0
+ superclassArgs = {};
+ else
+ superclassArgs = {ct};
+ end
+ this@matRad_ScenarioModel(superclassArgs{:});
+
+ %TODO: We could do this automatically in the superclass
+ %Octave 5 has a bug there and throws an error
+ this.updateScenarios();
+ end
+
+ function scenarios = updateScenarios(this)
+ this.numOfCtScen = size(this.ctScenProb,1);
+
+ %Scenario weight
+ this.scenWeight = ones(this.numOfCtScen,1)./this.numOfCtScen;
+ this.scenWeight = this.ctScenProb(:,2);
+ this.ctScenIx = this.ctScenProb(:,1);
+
+ %set variables
+ this.totNumShiftScen = 1;
+ this.totNumRangeScen = 1;
+ this.totNumScen = this.numOfCtScen;
+
+ %Individual shifts
+ this.relRangeShift = zeros(this.numOfCtScen,1);
+ this.absRangeShift = zeros(this.numOfCtScen,1);
+ this.isoShift = zeros(this.numOfCtScen,3);
+
+ %Probability matrices
+ this.scenForProb = [this.ctScenProb(:,1) zeros(this.numOfCtScen,5)]; %Realization matrix
+ this.scenProb = this.ctScenProb(:,2); %Probabilities for each scenario
+
+ this.maxAbsRangeShift = max(this.absRangeShift);
+ this.maxRelRangeShift = max(this.absRangeShift);
+
+ %Mask for scenario selection
+ this.scenMask = false(this.numOfAvailableCtScen,this.totNumShiftScen,this.totNumRangeScen);
+ this.scenMask(this.ctScenIx,:,:) = true;
+
+ %generic code
+ [x{1}, x{2}, x{3}] = ind2sub(size(this.scenMask),find(this.scenMask));
+ this.linearMask = cell2mat(x);
+ totNumScen = sum(this.scenMask(:));
+
+ %Get Scenario probability
+ Sigma = diag([this.shiftSD,this.rangeAbsSD,this.rangeRelSD./100].^2);
+ d = size(Sigma,1);
+ [cs,p] = chol(Sigma);
+ tmpScenProb = (2*pi)^(-d/2) * exp(-0.5*sum((this.scenForProb(:,2:end)/cs).^2, 2)) / prod(diag(cs));
+
+ %Multiply with 4D phase probability
+ this.scenProb = this.ctScenProb(:,2) .* tmpScenProb;
+
+ %Get relative (normalized) weight of the scenario
+ this.scenWeight = this.scenProb./sum(this.scenProb);
+
+ %Return variable
+ scenarios = this.scenForProb;
+
+
+ if totNumScen ~= this.totNumScen
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('Check Implementation of Total Scenario computation - given %d but found %d!',this.totNumScen,totNumScen);
+ this.totNumScen = totNumScen;
+ end
+ end
+
+ end
+end
+
diff --git a/matRad/scenarios/matRad_RandomScenarios.m b/matRad/scenarios/matRad_RandomScenarios.m
new file mode 100644
index 000000000..91a309d8e
--- /dev/null
+++ b/matRad/scenarios/matRad_RandomScenarios.m
@@ -0,0 +1,187 @@
+classdef matRad_RandomScenarios < matRad_ScenarioModel
+% matRad_RandomScenarios
+% Implements randomly sampled scenarios
+%
+% constructor
+% matRad_RandomScenarios()
+% matRad_RandomScenarios(ct)
+%
+% input
+% ct: ct cube
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ includeNominalScenario = false; %Forces inclusion of the nominal scenario
+ nSamples = 10; %Standard number of random samples
+ end
+
+ %Deprecated Properties that were used
+ properties (Dependent)
+ numOfShiftScen;
+ numOfRangeShiftScen;
+ end
+
+ properties (SetAccess=protected)
+ name = 'Randomly sampled Scenarios';
+ shortName = 'rndScen';
+ end
+
+ methods
+ function this = matRad_RandomScenarios(ct)
+ if nargin == 0
+ superclassArgs = {};
+ else
+ superclassArgs = {ct};
+ end
+
+ this@matRad_ScenarioModel(superclassArgs{:});
+
+ %TODO: We could do this automatically in the superclass
+ %Octave 5 has a bug there and throws an error
+ this.updateScenarios();
+ end
+
+ %% Setters & Update
+ function set.nSamples(this,nSamples)
+ valid = isnumeric(nSamples) && isscalar(nSamples) && mod(nSamples,1) == 0 && nSamples > 0;
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for nSamples! Needs to be a positive integer!');
+ end
+ this.nSamples = nSamples;
+ this.updateScenarios();
+ end
+
+ function set.numOfShiftScen(this,numOfShiftScen)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('The property numOfShiftScen of the scenario class will soon be deprecated! Use nSamples instead');
+
+ %That's for downwards compatibility
+ if ~isscalar(numOfShiftScen)
+ numOfShiftScen = unique(numOfShiftScen);
+ end
+
+ this.nSamples = numOfShiftScen;
+ end
+
+ function value = get.numOfShiftScen(this)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('The property numOfShiftScen of the scenario class will soon be deprecated! Use nSamples instead');
+ value = this.nSamples;
+ end
+
+ function set.numOfRangeShiftScen(this,numOfRangeShiftScen)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('The property numOfRangeShiftScen of the scenario class will soon be deprecated! Use nSamples instead');
+ this.nSamples = numOfRangeShiftScen;
+ end
+
+ function value = get.numOfRangeShiftScen(this)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('The property numOfRangeShiftScen of the scenario class will soon be deprecated! Use nSamples instead');
+ value = this.nSamples;
+ end
+
+ function scenarios = updateScenarios(this)
+ matRad_cfg = MatRad_Config.instance();
+
+ this.numOfCtScen = size(this.ctScenProb,1);
+
+ %Multivariate Normal Sampling
+ Sigma = diag([this.shiftSD,this.rangeAbsSD,this.rangeRelSD./100].^2);
+ d = size(Sigma,1);
+ [cs,p] = chol(Sigma);
+
+ %The lower part is there but commented for completeness, we do
+ %not need it since we know we that sigma is PSD
+ %if p ~= 0 %for the positive semi-definite case, we don't check for negative definite
+ % % [V,L] = eig(Sigma);
+ % cs = sqrt(L) * V';
+ %end
+ %transform normal samples, mean is always zero
+ scenarios = randn(this.nSamples,d) * cs;
+
+ if this.includeNominalScenario
+ %We include the nominal scenario by just replacing the
+ %first one to keep the number of scenarios the same
+ scenarios(1,:) = 0;
+ end
+
+ %Handle 4D phases
+ phases = repmat(this.ctScenProb(:,1)',size(scenarios,1),1);
+ phases = phases(:);
+ scenarios = horzcat(phases, repmat(scenarios,[this.numOfCtScen 1]));
+ this.ctScenIx = phases;
+
+ %Scenario Probability from pdf
+ this.scenForProb = scenarios;
+
+ %Create phase and setup/range probabilities
+ tmpScenProb = (2*pi)^(-d/2) * exp(-0.5*sum((scenarios(:,2:end)/cs).^2, 2)) / prod(diag(cs));
+
+ %Now we combine with the 4D ct phase probabilities (multiply)
+ tmpPhaseProb = arrayfun(@(phase) this.ctScenProb(find(this.ctScenProb(:,1) == phase),2),phases);
+
+ %Finalize Probability
+ this.scenProb = tmpPhaseProb .* tmpScenProb;
+
+ %Scenario weight
+ this.scenWeight = [];
+ for sCt = 1:this.numOfCtScen
+ %equal weights within a phase since they have been randomly sampled
+ %(not entirely true if the Nominal scenario was forced)
+ this.scenWeight = [this.scenWeight; this.ctScenProb(sCt,2) * ones(this.nSamples,1)./this.nSamples];
+ end
+
+ %set variables
+ this.totNumShiftScen = this.nSamples;
+ this.totNumRangeScen = this.nSamples;
+ this.totNumScen = this.nSamples * this.numOfCtScen; %check because of CT scenarios
+ %this.totNumCtScen =
+ %this.numOfShiftScen = [nSamples,nSamples,nSamples];
+ %this.numOfRangeShiftScen = nSamples;
+
+ %Individual shifts
+ this.relRangeShift = scenarios(:,6);
+ this.absRangeShift = scenarios(:,5);
+ this.isoShift = scenarios(:,2:4);
+
+ this.maxAbsRangeShift = max(this.absRangeShift);
+ this.maxRelRangeShift = max(this.relRangeShift);
+
+ %Mask for scenario selection
+ this.scenMask = false(this.numOfAvailableCtScen,this.totNumShiftScen,this.totNumRangeScen);
+
+ for sCt = 1:this.numOfCtScen
+ scenIx = this.ctScenProb(sCt,1);
+ this.scenMask(scenIx,:,:) = diag(true(this.nSamples,1));
+ end
+
+
+ tmpScenMask = permute(this.scenMask,[3 2 1]);
+ [x{3}, x{2}, x{1}] = ind2sub(size(tmpScenMask),find(tmpScenMask));
+ this.linearMask = cell2mat(x);
+ totNumScen = sum(this.scenMask(:));
+
+ if totNumScen ~= this.totNumScen
+ matRad_cfg.dispWarning('Check Implementation of Total Scenario computation - given %d but found %d!',this.totNumScen,totNumScen);
+ this.totNumScen = totNumScen;
+ end
+ end
+ end
+
+
+end
+
diff --git a/matRad/scenarios/matRad_ScenarioModel.m b/matRad/scenarios/matRad_ScenarioModel.m
new file mode 100644
index 000000000..c1ff46d61
--- /dev/null
+++ b/matRad/scenarios/matRad_ScenarioModel.m
@@ -0,0 +1,318 @@
+classdef (Abstract) matRad_ScenarioModel < handle
+% matRad_ScenarioModel
+% This is an abstract interface class to define Scenario Models for use in
+% robust treatment planning and uncertainty analysis.
+% Subclasses should at least implement the update() function to generate
+% their own scenarios.
+%
+% constructor (Abstract)
+% matRad_ScenarioModel()
+% matRad_ScenarioModel(ct)
+%
+% input
+% ct: ct cube
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (AbortSet = true) %We use AbortSet = true here to avoid updates when
+ %Uncertainty model
+ rangeRelSD = 3.5; % given in %
+ rangeAbsSD = 1; % given in [mm]
+ shiftSD = [2.25 2.25 2.25]; % given in [mm]
+ wcSigma = 1; % Multiplier to compute the worst case / maximum shifts
+
+ ctScenProb = [1 1]; % Ct Scenarios to be included in the model. Left column: Scenario Index. Right column: Scenario Probability
+ end
+
+ properties (Abstract,SetAccess = protected)
+ name
+ shortName
+ end
+
+ properties (Dependent)
+ wcFactor;
+ end
+
+ properties (SetAccess = protected)
+ numOfCtScen; % total number of CT scenarios used
+ numOfAvailableCtScen; % total number of CT scenarios existing in ct structure
+ ctScenIx; % map of all ct scenario indices per scenario
+
+
+ % these parameters will be filled according to the choosen scenario type
+ isoShift;
+ relRangeShift;
+ absRangeShift;
+
+ maxAbsRangeShift;
+ maxRelRangeShift;
+
+ totNumShiftScen; % total number of shift scenarios in x,y and z direction
+ totNumRangeScen; % total number of range and absolute range scenarios
+ totNumScen; % total number of samples
+
+ scenForProb; % matrix for probability calculation - each row denotes one scenario, whereas columns denotes the realization value
+ scenProb; % probability of each scenario stored in a vector (according to uncertainty model)
+ scenWeight; % weight of scenario relative to the underlying uncertainty model (depends on how scenarios are chosen / sampled)
+ scenMask;
+ linearMask;
+ end
+
+ methods
+ function this = matRad_ScenarioModel(ct)
+ if nargin == 0 || isempty(ct)
+ this.numOfCtScen = 1;
+ this.numOfAvailableCtScen = 1;
+ else
+ this.numOfCtScen = ct.numOfCtScen;
+ this.numOfAvailableCtScen = ct.numOfCtScen;
+ end
+
+ this.ctScenProb = [(1:this.numOfCtScen)', ones(this.numOfCtScen,1)./this.numOfCtScen]; %Equal probability to be in each phase of the 4D ct
+
+ %TODO: We could do this here automatically in the constructor, but
+ %Octave 5 has a bug here and throws an error
+ %this.updateScenarios();
+ end
+
+ function listAllScenarios(this)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('Listing all scenarios...\n');
+ matRad_cfg.dispInfo('\t#\tctScen\txShift\tyShift\tzShift\tabsRng\trelRng\tprob.\n');
+ for s = 1:size(this.scenForProb,1)
+ str = [num2str(this.scenForProb(s,1),'\t%d'),sprintf('\t\t'), num2str(this.scenForProb(s,2:end),'\t%.3f')];
+ matRad_cfg.dispInfo('\t%d\t%s\t%.3f\n',s,str,this.scenProb(s));
+ end
+ end
+
+ %% SETTERS & UPDATE
+ function set.rangeRelSD(this,rangeRelSD)
+ valid = isnumeric(rangeRelSD) && isscalar(rangeRelSD) && rangeRelSD >= 0;
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for rangeRelSD! Needs to be a real positive scalar!');
+ end
+ this.rangeRelSD = rangeRelSD;
+ this.updateScenarios();
+ end
+
+ function set.rangeAbsSD(this,rangeAbsSD)
+ valid = isnumeric(rangeAbsSD) && isscalar(rangeAbsSD) && rangeAbsSD >= 0;
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for rangeAbsSD! Needs to be a real positive scalar!');
+ end
+ this.rangeAbsSD = rangeAbsSD;
+ this.updateScenarios();
+ end
+
+ function set.shiftSD(this,shiftSD)
+ valid = isnumeric(shiftSD) && isrow(shiftSD) && numel(shiftSD) == 3 && all(shiftSD > 0);
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for shiftSD! Needs to be 3-element numeric row vector!');
+ end
+ this.shiftSD = shiftSD;
+ this.updateScenarios();
+ end
+
+ function set.wcSigma(this,wcSigma)
+ valid = isnumeric(wcSigma) && isscalar(wcSigma) && wcSigma >= 0;
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for wcSigma! Needs to be a real positive scalar!');
+ end
+ this.wcSigma = wcSigma;
+ this.updateScenarios();
+ end
+
+ function set.ctScenProb(this,ctScenProb)
+ valid = isnumeric(ctScenProb) && ismatrix(ctScenProb) && size(ctScenProb,2) == 2 && all(round(ctScenProb(:,1)) == ctScenProb(:,1)) && all(ctScenProb(:) >= 0);
+ if ~valid
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid value for used ctScenProb! Needs to be a valid 2-column matrix with left column representing the scenario index and right column representing the appropriate probabilities [0,1]!');
+ end
+ this.ctScenProb = ctScenProb;
+ this.updateScenarios();
+ end
+
+
+ function scenarios = updateScenarios(this)
+ %This function will always update the scenarios given the
+ %current property settings
+
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('This abstract function needs to be implemented!');
+ end
+
+ function newInstance = extractSingleScenario(this,scenNum)
+ newInstance = matRad_NominalScenario();
+
+ ctScenNum = this.linearMask(scenNum,1);
+
+ %First set properties that force an update
+ newInstance.numOfCtScen = 1;
+ newInstance.ctScenProb = this.ctScenProb(ctScenNum,:);
+
+ %Now overwrite existing variables for correct probabilties and
+ %error realizations
+ newInstance.scenForProb = this.scenForProb(scenNum,:);
+ newInstance.relRangeShift = this.scenForProb(scenNum,6);
+ newInstance.absRangeShift = this.scenForProb(scenNum,5);
+ newInstance.isoShift = this.scenForProb(scenNum,2:4);
+ newInstance.scenProb = this.scenProb(scenNum);
+ newInstance.scenWeight = this.scenWeight(scenNum);
+ newInstance.maxAbsRangeShift = max(abs(this.absRangeShift(scenNum)));
+ newInstance.maxRelRangeShift = max(abs(this.relRangeShift(scenNum)));
+ newInstance.scenMask = false(this.numOfAvailableCtScen,1,1);
+ newInstance.linearMask = [newInstance.ctScenIx 1 1];
+
+ newInstance.scenMask(newInstance.linearMask(:,1),newInstance.linearMask(:,2),newInstance.linearMask(:,3)) = true;
+ %newInstance.updateScenarios();
+ end
+
+ function scenIx = sub2scenIx(this,ctScen,shiftScen,rangeShiftScen)
+ %Returns linear index in the scenario cell array from scenario
+ %subscript indices
+ if ~isvector(this.scenMask)
+ scenIx = sub2ind(size(this.scenMask),this.ctScenIx(ctScen),shiftScen,rangeShiftScen);
+ else
+ scenIx = this.ctScenIx(ctScen);
+ end
+ end
+
+ function scenNum = scenNum(this,fullScenIx)
+ %gets number of scneario from full scenario index in scenMask
+ scenNum = find(find(this.scenMask) == fullScenIx);
+ end
+
+ %% Deprecated functions / properties
+ function newInstance = extractSingleNomScen(this,~,scenIdx)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('The function extractSingleNomScen of the scenario class will soon be deprecated! Use extractSingleScenario instead!');
+ newInstance = this.extractSingleScenario(scenIdx);
+ end
+
+ function t = TYPE(this)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('The property TYPE of the scenario class will soon be deprecated!');
+ t = this.shortName;
+ end
+
+ function value = get.wcFactor(this)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('The property wcFactor of the scenario class will soon be deprecated!');
+ value = this.wcSigma;
+ end
+
+ function set.wcFactor(this,value)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispDeprecationWarning('The property wcFactor of the scenario class will soon be deprecated!');
+ this.wcSigma = value;
+ end
+
+ end
+
+ methods (Static)
+
+ function classList = getAvailableModels()
+ matRad_cfg = MatRad_Config.instance();
+
+ %Use the root folder and the scenarios folder only
+ folders = {fileparts(mfilename('fullpath'))};
+ folders = [folders matRad_cfg.userfolders];
+ metaScenarioModels = matRad_findSubclasses(meta.class.fromName(mfilename('class')),'folders',folders,'includeSubfolders',true);
+ classList = matRad_identifyClassesByConstantProperties(metaScenarioModels,'shortName','defaults',{'nomScen'});
+
+ if isempty(classList)
+ matRad_cfg.dispError('No models found in paths %s',strjoin(folders,'\n'));
+ end
+ end
+
+ function model = create(modelMetadata,ct)
+ if isa(modelMetadata,'matRad_ScenarioModel')
+ model = modelMetadata;
+ return;
+ end
+
+ matRad_cfg = MatRad_Config.instance();
+
+ if ischar(modelMetadata) || isstring(modelMetadata)
+ modelMetadata = struct('model',modelMetadata);
+ end
+
+ modelClassList = matRad_ScenarioModel.getAvailableModels();
+ modelNames = {modelClassList.shortName};
+
+ if ~isfield(modelMetadata,'model') || ~any(strcmp(modelNames,modelMetadata.model))
+ matRad_cfg.dispWarning('Scenario Model not found, creating nominal scenario instead!');
+ modelMetadata.model = 'nomScen';
+ end
+
+ usedModel = find(strcmp(modelNames,modelMetadata.model));
+
+ if ~isscalar(usedModel)
+ usedModel = usedModel(1);
+ end
+
+ modelClassInfo = modelClassList(usedModel);
+
+ if nargin < 2
+ model = modelClassInfo.handle();
+ else
+ model = modelClassInfo.handle(ct);
+ end
+
+ modelMetadata = rmfield(modelMetadata,'model');
+
+ %Now overwrite properties
+ fields = fieldnames(modelMetadata);
+
+ % iterate over all fieldnames and try to set the
+ % corresponding properties inside the engine
+ if matRad_cfg.isOctave
+ c2sWarningState = warning('off','Octave:classdef-to-struct');
+ end
+
+ for i = 1:length(fields)
+ try
+ field = fields{i};
+ if matRad_ispropCompat(model,field)
+ model.(field) = matRad_recursiveFieldAssignment(model.(field),modelMetadata.(field),true);
+ else
+ matRad_cfg.dispWarning('Not able to assign property ''%s'' from multScen struct to Scenario Model!',field);
+ end
+ catch ME
+ % catch exceptions when the model has no properties,
+ % which are defined in the struct.
+ % When defining an engine with custom setter and getter
+ % methods, custom exceptions can be caught here. Be
+ % careful with Octave exceptions!
+ matRad_cfg = MatRad_Config.instance();
+ switch ME.identifier
+ case 'MATLAB:noPublicFieldForClass'
+ matRad_cfg.dispWarning('Not able to assign property from multScen struct to scenario model: %s',ME.message);
+ otherwise
+ matRad_cfg.dispWarning('Problem while setting up scenario Model from struct:%s %s',field,ME.message);
+ end
+ end
+ end
+
+ if matRad_cfg.isOctave
+ warning(c2sWarningState.state,'Octave:classdef-to-struct');
+ end
+ end
+ end
+end
+
diff --git a/matRad/scenarios/matRad_WorstCaseScenarios.m b/matRad/scenarios/matRad_WorstCaseScenarios.m
new file mode 100644
index 000000000..76f5730d0
--- /dev/null
+++ b/matRad/scenarios/matRad_WorstCaseScenarios.m
@@ -0,0 +1,60 @@
+classdef matRad_WorstCaseScenarios < matRad_GriddedScenariosAbstract
+% matRad_WorstCaseScenarios
+% Implements single worst-case shifts per dimension.%
+%
+% constructor
+% matRad_WorstCaseScenarios()
+% matRad_WorstCaseScenarios(ct)
+%
+% input
+% ct: ct cube
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ properties (SetAccess=protected)
+ shortName = 'wcScen';
+ name = 'Worst Case Scenarios';
+ end
+
+ properties (Hidden)
+ numOfSetupGridPoints = 3;
+ numOfRangeGridPoints = 3;
+ end
+
+ methods
+ function this = matRad_WorstCaseScenarios(ct)
+ if nargin == 0
+ superclassArgs = {};
+ else
+ superclassArgs = {ct};
+ end
+
+ this@matRad_GriddedScenariosAbstract(superclassArgs{:});
+
+ %TODO: We could do this automatically in the superclass
+ %Octave 5 has a bug there and throws an error
+ this.updateScenarios();
+ end
+
+ function scenarios = updateScenarios(this)
+ %Use the static gridded shift function from
+ %ImportanceScenarios. We set inclusion of nominal scenarios to
+ %false and handle it automatically via the grid point number
+ scenarios = this.updateScenarios@matRad_GriddedScenariosAbstract();
+ end
+ end
+
+
+end
diff --git a/matRad/scenarios/matRad_calcScenProb.m b/matRad/scenarios/matRad_calcScenProb.m
new file mode 100644
index 000000000..1c266c064
--- /dev/null
+++ b/matRad/scenarios/matRad_calcScenProb.m
@@ -0,0 +1,70 @@
+function scenProb = matRad_calcScenProb(mu,sigma,samplePos,calcType,probDist)
+% matRad_calcScenProb provides different ways of calculating the probability
+% of occurance of individual scenarios
+%
+% call
+% scenProb = matRad_calcScenProb(mu,sigma,samplePos,calcType,probDist)
+%
+% input
+% mu: mean of the distrubtion
+% sigma: standard deviation of the distribution
+% calcType: can be set to
+% (i) probBins to calculate the accumulated occurance probability in a certain bin width
+% (ii) pointwise to calculate the pointwise occurance probability
+% probDist: identifier for the underlying probability distribution
+% (i) normDist
+%
+% output
+% scenProb: occurance probability of the specified scenario
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+if isequal(probDist,'normDist')
+
+ scenProb = 1;
+
+ if isequal(calcType,'probBins')
+
+ for i = 1:length(mu)
+ samplePosSorted = sort(unique(samplePos(:,i)));
+ if numel(samplePosSorted) == 1 || sigma(i) == 0
+ continue;
+ end
+ binWidth = (samplePosSorted(2) - samplePosSorted(1));
+ lowerBinLevel = samplePos(:,i) - 0.5*binWidth;
+ upperBinLevel = samplePos(:,i) + 0.5*binWidth;
+
+ scenProb = scenProb.*0.5.*(erf((upperBinLevel-mu(i))/(sqrt(2)*sigma(i)))-erf((lowerBinLevel-mu(i))/(sqrt(2)*sigma(i))));
+ end
+
+ elseif isequal(calcType,'pointwise')
+ for i = 1:length(mu)
+ scenProb = scenProb .* (1/sqrt(2*pi*sigma(i)^2)*exp(-((samplePos(:,i)-mu(i)).^2./(2*sigma(i)^2))));
+ end
+ end
+
+ % normalize probabilities since we use only a subset of
+ % the 3D grid
+ scenProb = scenProb./sum(scenProb);
+
+elseif isequal(probDist,'equalProb')
+
+ numScen = size(samplePos,1);
+ scenProb = repmat(1/numScen,1,numScen);
+
+else
+ matRad_dispToConsole('Until now, only normally distributed scenarios implemented','error')
+end
diff --git a/matRad/scenarios/matRad_multScen.m b/matRad/scenarios/matRad_multScen.m
new file mode 100644
index 000000000..156d8ff23
--- /dev/null
+++ b/matRad/scenarios/matRad_multScen.m
@@ -0,0 +1,47 @@
+function multScen = matRad_multScen(ct,scenarioModel)
+% matRad_multScen
+% This function replaces the deprecated matRad_multScen class. It creates
+% the respective matRad_ScenarioModel instance for the specific scenario
+% model chosen with standard parameters.
+%
+% call
+% pln.multScen = matRad_multScen(ct,scenarioModel);
+%
+% e.g. pln.multScen = matRad_multScen(ct,'nomScen');
+%
+% input
+% ct: ct cube
+% scenarioModel: string to denote a scenario creation method
+% 'nomScen' create only the nominal scenario
+% 'wcScen' create worst case scenarios
+% 'impScen' create important/grid scenarios
+% 'rndScen' create random scenarios
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%matRad_cfg = MatRad_Config.instance();
+%matRad_cfg.dispWarning('The matRad_multScen function will be deprecated soon!\nCheck out the new Scenario Models in the scenarios folder.');
+
+multScen = matRad_ScenarioModel.create(scenarioModel,ct);
+
+end
+
+
+
+
+
+
+
+
+
diff --git a/matRad/sequencing/matRad_aperture2collimation.m b/matRad/sequencing/matRad_aperture2collimation.m
new file mode 100644
index 000000000..934daf8e4
--- /dev/null
+++ b/matRad/sequencing/matRad_aperture2collimation.m
@@ -0,0 +1,131 @@
+function [pln,stf] = matRad_aperture2collimation(pln,stf,sequencing,apertureInfo)
+% matRad function to convert sequencing information / aperture information
+% into collimation information in pln and stf for field-based dose
+% calculation
+%
+% call
+% [pln,stf] = matRad_aperture2collimation(pln,stf,sequencing,apertureInfo)
+%
+% input
+% pln: pln file used to generate the sequenced plan
+% stf: stf file used to generate the sequenced plan
+% sequencing: sequencing information (from resultGUI)
+% apertureInfo: apertureInfo (from resultGUI)
+%
+% output
+% pln: matRad pln struct with collimation information
+% stf: matRad stf struct with shapes instead of beamlets
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+% Author: wahln
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%Debug visualization
+visBool = false;
+
+bixelWidth = apertureInfo.bixelWidth;
+leafWidth = bixelWidth;
+convResolution = 0.5; %[mm]
+
+%The collimator limits are infered here from the apertureInfo. This could
+%be handled differently by explicitly storing collimator info in the base
+%data?
+symmetricMLClimits = vertcat(apertureInfo.beam.MLCWindow);
+symmetricMLClimits = max(abs(symmetricMLClimits));
+fieldWidth = 2*max(symmetricMLClimits);
+
+%modify basic pln variables
+pln.propStf.bixelWidth = 'field';
+pln.propStf.collimation.convResolution = 0.5; %[mm]
+pln.propStf.collimation.fieldWidth = fieldWidth;
+pln.propStf.collimation.leafWidth = leafWidth;
+
+%
+%[bixelFieldX,bixelFieldY] = ndgrid(-fieldWidth/2:bixelWidth:fieldWidth/2,-fieldWidth/2:leafWidth:fieldWidth/2);
+[convFieldX,convFieldY] = meshgrid(-fieldWidth/2:convResolution:fieldWidth/2);
+
+%TODO: Not used in calcPhotonDose but imported from DICOM
+%pln.propStf.collimation.Devices ...
+%pln.propStf.collimation.numOfFields
+%pln.propStf.collimation.beamMeterset
+
+for iBeam = 1:numel(stf)
+ stfTmp = stf(iBeam);
+ beamSequencing = sequencing.beam(iBeam);
+ beamAperture = apertureInfo.beam(iBeam);
+
+ stfTmp.bixelWidth = 'field';
+
+ nShapes = beamSequencing.numOfShapes;
+
+ stfTmp.numOfRays = 1;%
+ stfTmp.numOfBixelsPerRay = nShapes;
+ stfTmp.totalNumOfBixels = nShapes;
+
+ ray = struct();
+ ray.rayPos_bev = [0 0 0];
+ ray.targetPoint_bev = [0 stfTmp.SAD 0];
+ ray.weight = 1;
+ ray.energy = stfTmp.ray(1).energy;
+ ray.beamletCornersAtIso = stfTmp.ray(1).beamletCornersAtIso;
+ ray.rayCorners_SCD = stfTmp.ray(1).rayCorners_SCD;
+
+ %ray.shape = beamSequencing.sum;
+ shapeTotalF = zeros(size(convFieldX));
+
+ ray.shapes = struct();
+ for iShape = 1:nShapes
+ currShape = beamAperture.shape(iShape);
+ activeLeafPairPosY = beamAperture.leafPairPos;
+ F = zeros(size(convFieldX));
+ if visBool
+ hF = figure; imagesc(F); title(sprintf('Beam %d, Shape %d',iBeam,iShape)); hold on;
+ end
+ for iLeafPair = 1:numel(activeLeafPairPosY)
+ posY = activeLeafPairPosY(iLeafPair);
+ ixY = convFieldY >= posY-leafWidth/2 & convFieldY < posY + leafWidth/2;
+ ixX = convFieldX >= currShape.leftLeafPos(iLeafPair) & convFieldX < currShape.rightLeafPos(iLeafPair);
+ ix = ixX & ixY;
+ F(ix) = 1;
+ if visBool
+ figure(hF); imagesc(F); drawnow; pause(0.1);
+ end
+ end
+
+ if visBool
+ pause(1); close(hF);
+ end
+
+ F = F*currShape.weight;
+ shapeTotalF = shapeTotalF + F;
+
+ ray.shapes(iShape).convFluence = F;
+ ray.shapes(iShape).shapeMap = currShape.shapeMap;
+ ray.shapes(iShape).weight = currShape.weight;
+ ray.shapes(iShape).leftLeafPos = currShape.leftLeafPos;
+ ray.shapes(iShape).rightLeafPos = currShape.rightLeafPos;
+ ray.shapes(iShape).leafPairCenterPos = activeLeafPairPosY;
+ end
+
+ ray.shape = shapeTotalF;
+ ray.weight = ones(1,nShapes);
+ ray.collimation = pln.propStf.collimation;
+ stfTmp.ray = ray;
+
+ stf(iBeam) = stfTmp;
+end
+
+
diff --git a/matRad_engelLeafSequencing.m b/matRad/sequencing/matRad_engelLeafSequencing.m
similarity index 99%
rename from matRad_engelLeafSequencing.m
rename to matRad/sequencing/matRad_engelLeafSequencing.m
index 965aeea48..a1d944374 100644
--- a/matRad_engelLeafSequencing.m
+++ b/matRad/sequencing/matRad_engelLeafSequencing.m
@@ -27,7 +27,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad_sequencing2ApertureInfo.m b/matRad/sequencing/matRad_sequencing2ApertureInfo.m
similarity index 98%
rename from matRad_sequencing2ApertureInfo.m
rename to matRad/sequencing/matRad_sequencing2ApertureInfo.m
index 0ae56b763..867cabd43 100644
--- a/matRad_sequencing2ApertureInfo.m
+++ b/matRad/sequencing/matRad_sequencing2ApertureInfo.m
@@ -21,7 +21,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad_siochiLeafSequencing.m b/matRad/sequencing/matRad_siochiLeafSequencing.m
similarity index 97%
rename from matRad_siochiLeafSequencing.m
rename to matRad/sequencing/matRad_siochiLeafSequencing.m
index a2940b964..4240a6c9a 100644
--- a/matRad_siochiLeafSequencing.m
+++ b/matRad/sequencing/matRad_siochiLeafSequencing.m
@@ -34,12 +34,13 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
% if visBool not set toogle off visualization
if nargin < 5
visBool = 0;
@@ -58,12 +59,18 @@
offset = 0;
+if ~isfield(resultGUI,'wUnsequenced')
+ wUnsequenced = resultGUI.w;
+else
+ wUnsequenced = resultGUI.wUnsequenced;
+end
+
for i = 1:numOfBeams
numOfRaysPerBeam = stf(i).numOfRays;
% get relevant weights for current beam
- wOfCurrBeams = resultGUI.wUnsequenced(1+offset:numOfRaysPerBeam+offset);%REVIEW OFFSET
+ wOfCurrBeams = wUnsequenced(1+offset:numOfRaysPerBeam+offset);%REVIEW OFFSET
X = ones(numOfRaysPerBeam,1)*NaN;
Z = ones(numOfRaysPerBeam,1)*NaN;
diff --git a/matRad_xiaLeafSequencing.m b/matRad/sequencing/matRad_xiaLeafSequencing.m
similarity index 99%
rename from matRad_xiaLeafSequencing.m
rename to matRad/sequencing/matRad_xiaLeafSequencing.m
index 3c3e0c2c5..aaa893324 100644
--- a/matRad_xiaLeafSequencing.m
+++ b/matRad/sequencing/matRad_xiaLeafSequencing.m
@@ -28,7 +28,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/steering/matRad_StfGeneratorBase.m b/matRad/steering/matRad_StfGeneratorBase.m
new file mode 100644
index 000000000..471473040
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorBase.m
@@ -0,0 +1,509 @@
+classdef (Abstract) matRad_StfGeneratorBase < handle
+% matRad_StfGeneratorBase: Abstract Superclass for Steering information
+% generators. Steering information is used to guide the dose calculation
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ isStfGenerator = true; % const boolean for inheritance quick check
+ end
+
+ properties (Constant, Abstract)
+ name; %Descriptive Name
+ shortName; %Short name for referencing
+ possibleRadiationModes; %Possible radiation modes for the respective StfGenerator
+ end
+
+ properties (Access = public)
+ visMode = 0; %Visualization Options
+ addMargin = true; %Add margins to target (for numerical stability and robustness)
+ multScen; %Scenario Model
+ bioParam; %Biological Model
+ radiationMode; %Radiation Mode
+ machine; %Machine
+ end
+
+ properties (Access = protected)
+ pln %matRad Plan struct
+ cubeDim %Underlying CT dimension
+ voxTargetWorldCoords %Target voxels in world coordinates
+ voiTarget %merged Target VOI cube
+ ct %ct reference during generation
+ cst %cst reference during generation
+ end
+
+ methods
+ function this = matRad_StfGeneratorBase(pln)
+ % Constructs standalone StfGenerator with or without pln
+
+ this.setDefaults();
+ if nargin == 1 && ~isempty(pln)
+ this.assignPropertiesFromPln(pln);
+ end
+
+ end
+
+ function setDefaults(this)
+ % set default values from MatRad_Config
+
+ matRad_cfg = MatRad_Config.instance();
+ defaultPropStf = matRad_cfg.defaults.propStf;
+ fields = fieldnames(defaultPropStf);
+ for i = 1:numel(fields)
+ fName = fields{i};
+ if matRad_ispropCompat(this,fName)
+ try
+ this.(fName) = defaultPropStf.(fName);
+ catch
+ matRad_cfg.dispWarning('Could not assign default property %s',fName);
+ end
+ end
+ end
+ this.machine = 'Generic';
+ end
+
+ function set.radiationMode(this,mode)
+ % radiationMode setter
+
+ if ~any(strcmp(mode,this.possibleRadiationModes))
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Radiation mode %s not supported by stf generator ''%s'' (%s)!',mode,this.name,class(this));
+ end
+ this.radiationMode = mode;
+ end
+
+ function warnDeprecatedProperty(this,oldProp,msg,newProp)
+ matRad_cfg = MatRad_Config.instance();
+ if nargin < 3 || isempty(msg)
+ msg = '';
+ end
+
+ if nargin < 4
+ dep2 = '';
+ else
+ dep2 = sprintf('Use Property ''%s'' instead!',newProp);
+ end
+
+ matRad_cfg.dispDeprecationWarning('Property ''%s'' of stf Generator ''%s'' is deprecated! %s%s',oldProp,this.name,msg,dep2);
+ end
+
+ function assignPropertiesFromPln(this,pln,warnWhenPropertyChanged)
+ % Assign properties from pln.propStf to the stf generator
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Must haves in pln struct
+ %Set/validate radiation Mode
+ if ~isfield(pln,'radiationMode') && isempty(this.radiationMode)
+ matRad_cfg.dispError('No radiation mode specified in pln struct!');
+ else
+ this.radiationMode = pln.radiationMode;
+ end
+
+ %Take machine from pln
+ if ~isfield(pln,'machine') && isempty(this.machine)
+ matRad_cfg.dispError('No machine specified in pln struct!');
+ else
+ this.machine = pln.machine;
+ end
+
+ % Defaults if not provided
+ %Set Scenario Model
+ if isfield(pln,'multScen')
+ this.multScen = pln.multScen;
+ end
+
+ %Assign biological model
+ if isfield(pln,'bioParam')
+ this.bioParam = pln.bioParam;
+ end
+
+ if nargin < 3 || ~isscalar(warnWhenPropertyChanged) || ~islogical(warnWhenPropertyChanged)
+ warnWhenPropertyChanged = false;
+ end
+
+ %Overwrite default properties within the stf generatorwith the
+ %ones given in the propStf struct
+ if isfield(pln,'propStf') && isstruct(pln.propStf)
+ plnStruct = pln.propStf; %get remaining fields
+ if isfield(plnStruct,'generator') && ~isempty(plnStruct.generator) && ~any(strcmp(plnStruct.generator,this.shortName))
+ matRad_cfg.dispWarning('Inconsistent stf generators given! pln asks for ''%s'', but you are using ''%s''!',plnStruct.generator,this.shortName);
+ end
+ if isfield(plnStruct,'generator')
+ plnStruct = rmfield(plnStruct, 'generator'); % generator field is no longer needed and would throw an exception
+ end
+ else
+ plnStruct = struct();
+ end
+
+ fields = fieldnames(plnStruct);
+
+ %Set up warning message
+ if warnWhenPropertyChanged
+ warningMsg = 'Property in stf generator overwritten from pln.propStf';
+ else
+ warningMsg = '';
+ end
+
+ % iterate over all fieldnames and try to set the
+ % corresponding properties inside the stf generator
+ if matRad_cfg.isOctave
+ c2sWarningState = warning('off','Octave:classdef-to-struct');
+ end
+
+ for i = 1:length(fields)
+ try
+ field = fields{i};
+ if matRad_ispropCompat(this,field)
+ this.(field) = matRad_recursiveFieldAssignment(this.(field),plnStruct.(field),true,warningMsg);
+ else
+ matRad_cfg.dispWarning('Not able to assign property ''%s'' from pln.propStf to stf generator!',field);
+ end
+ catch ME
+ % catch exceptions when the stf generator has no
+ % properties which are defined in the struct.
+ % When defining an engine with custom setter and getter
+ % methods, custom exceptions can be caught here. Be
+ % careful with Octave exceptions!
+ if ~isempty(warningMsg)
+ matRad_cfg = MatRad_Config.instance();
+ switch ME.identifier
+ case 'MATLAB:noPublicFieldForClass'
+ matRad_cfg.dispWarning('Not able to assign property from pln.propStf to stf generator: %s',ME.message);
+ otherwise
+ matRad_cfg.dispWarning('Problem while setting up stf generator from struct:%s %s',field,ME.message);
+ end
+ end
+ end
+ end
+
+ if matRad_cfg.isOctave
+ warning(c2sWarningState.state,'Octave:classdef-to-struct');
+ end
+ end
+ end
+
+ methods %(Abstract)
+
+ function stf = generate(this, ct, cst)
+ % Generate steering information for the given ct and cst
+ % This is a base class function performing the following tasks
+ % 1. Checking the input
+ % 2. Initializing the patient geometry (protected properties)
+ % 3. Generating the source information (and thus the "stf")
+
+ % Instance of MatRad_Config class
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('matRad: Generating stf struct with generator ''%s''... ',this.name);
+
+ this.ct = ct;
+ this.cst = cst;
+
+ this.initialize();
+ this.createPatientGeometry();
+ stf = this.generateSourceGeometry();
+ end
+ end
+
+ methods (Access = protected)
+ function initialize(this)
+ %Do nothing
+
+ % get machine
+ if ~isstruct(this.machine)
+ try
+ this.machine = matRad_loadMachine(struct('radiationMode',this.radiationMode,'machine',this.machine));
+ catch
+ matRad_cfg.dispError('Could not find the following machine file: %s_%s',this.radiationMode,this.machine);
+ end
+ end
+
+ %Make sure the coordinate system is initialized
+ this.ct = matRad_getWorldAxes(this.ct);
+
+ %Make sure we have a valid scenario model
+ if isempty(this.multScen)
+ this.multScen = matRad_NominalScenario(this.ct);
+ end
+
+ %create / validate scenario model
+ this.multScen = matRad_ScenarioModel.create(this.multScen,this.ct);
+ end
+
+ function createPatientGeometry(this)
+ % Basic Initialization of the Patient Geometry
+
+ matRad_cfg = MatRad_Config.instance();
+
+ % Initialize patient geometry
+ V = [];
+ %ct = matRad_calcWaterEqD(ct,this.radiationMode);
+
+ isTarget = cellfun(@(voiType) isequal(voiType, 'TARGET'), this.cst(:,3));
+ if ~any(isTarget)
+ matRad_cfg.dispError('No target found in cst. Please designate at least one VOI as ''TARGET''!');
+ end
+
+ hasObjective = ~cellfun(@isempty, this.cst(:,6));
+ useTargetForBixelPlacement = isTarget & hasObjective;
+
+ if ~any(useTargetForBixelPlacement)
+ matRad_cfg.dispWarning('No Objectives / Constraints assigned to targets. All targets will be considered for Bixel placement!');
+ useTargetForBixelPlacement(isTarget) = true;
+ end
+
+ % Now add all used target voxels to the voxel list
+ for i = 1:size(this.cst, 1)
+ if useTargetForBixelPlacement(i)
+ V = [V; this.cst{i,4}{1}];
+ end
+ end
+
+ % Remove double voxels
+ V = unique(V);
+ % generate voi cube for targets
+ this.voiTarget = zeros(this.ct.cubeDim);
+ this.voiTarget(V) = 1;
+
+ % Margin info
+ if this.addMargin
+ pbMargin = this.getPbMargin();
+
+ % Assumption for range uncertainty
+ assumeRangeMargin = this.multScen.maxAbsRangeShift + this.multScen.maxRelRangeShift + pbMargin;
+
+ % add margin - account for voxel resolution, the maximum shift scenario and the current bixel width.
+ margin.x = max([this.ct.resolution.x max(abs(this.multScen.isoShift(:,1)) + assumeRangeMargin)]);
+ margin.y = max([this.ct.resolution.y max(abs(this.multScen.isoShift(:,2)) + assumeRangeMargin)]);
+ margin.z = max([this.ct.resolution.z max(abs(this.multScen.isoShift(:,3)) + assumeRangeMargin)]);
+
+ this.voiTarget = matRad_addMargin(this.voiTarget, this.cst, this.ct.resolution, margin, true);
+ V = find(this.voiTarget > 0);
+ end
+
+ % throw error message if no target is found
+ if isempty(V)
+ matRad_cfg.dispError('Could not find target.');
+ end
+
+ % Convert linear indices to 3D voxel coordinates
+ this.voxTargetWorldCoords = matRad_cubeIndex2worldCoords(V, this.ct);
+
+ % take only voxels inside patient
+ V = [this.cst{:,4}];
+ V = unique(vertcat(V{:}));
+
+ % ignore densities outside of contours
+ eraseCtDensMask = ones(prod(this.ct.cubeDim), 1);
+ eraseCtDensMask(V) = 0;
+ for i = 1:this.ct.numOfCtScen
+ this.ct.cube{i}(eraseCtDensMask == 1) = 0;
+ end
+ end
+
+ function pbMargin = getPbMargin(this)
+ % Get the geometrical margin to add to the target (e.g. for safe spot placement or because of robustness)
+ pbMargin = 0;
+ end
+ end
+
+ methods (Access = protected)
+ function stf = generateSourceGeometry(this)
+ % the actual calculation method which returns the final stf struct.
+ % Needs to be implemented in non-abstract subclasses.
+ % (Internal logic is often split into multiple methods in order to
+ % make the whole calculation more modular)
+ throw(MException('MATLAB:class:AbstractMember','Abstract function generateSourceGeometry of your StfGenerator needs to be implemented!'));
+ end
+ end
+
+ methods (Static)
+ function generator = getGeneratorFromPln(pln)
+ %GETENGINE Summary of this function goes here
+ % Detailed explanation goes here
+
+ matRad_cfg = MatRad_Config.instance();
+
+ generator = [];
+
+ initDefaultGenerator = false;
+ %get all available engines for given pln struct, could be done conditional
+ classList = matRad_StfGeneratorBase.getAvailableGenerators(pln);
+
+ % Check for a valid engine, and if the given engine isn't valid set boolean
+ % to initiliaze default engine at the end of this function
+ if isfield(pln,'propStf') && isa(pln.propStf, mfilename('class'))
+ generator = pln.propStf;
+ elseif isfield(pln,'propStf') && isstruct(pln.propStf) && isfield(pln.propStf,'generator')
+ if ischar(pln.propStf.generator) || isstring(pln.propStf.generator)
+ matchGenerators = strcmpi({classList(:).shortName},pln.propStf.generator);
+ if any(matchGenerators)
+ %instantiate engine
+ generatorHandle = classList(matchGenerators).handle;
+ generator = generatorHandle(pln);
+ else
+ initDefaultGenerator = true;
+ end
+ else
+ initDefaultGenerator = true;
+ matRad_cfg.dispWarning('pln.propStf.generator field not valid!');
+ end
+ else
+ initDefaultGenerator = true;
+ end
+
+ % trying to use a default engine which fits
+ % the given radiation mode, when no valid engine was defined.
+ % Default Engines are defined in matRad_Config.
+ if initDefaultGenerator
+ matchGenerators = ismember({classList(:).shortName},matRad_cfg.defaults.propStf.generator);
+ if any(matchGenerators)
+ generatorHandle = classList(matchGenerators).handle;
+
+ % unlikely event that multiple engines fit just take the first
+ if length(generatorHandle) > 1
+ generatorHandle = generatorHandle{1};
+ end
+ generator = generatorHandle(pln);
+ matRad_cfg.dispWarning('Using default stf generator %s!', generator.name);
+ elseif ~isempty(classList)
+ generatorHandle = classList(1).handle;
+ generator = generatorHandle(pln);
+ matRad_cfg.dispWarning('Default stf generator not available! Using %s.', generator.name);
+ else
+ matRad_cfg.dispError('Default stf generator not found!');
+ end
+ end
+
+ if isempty(generator)
+ matRad_cfg.dispError('No suitable stf generator found!');
+ end
+
+ end
+
+ function classList = getAvailableGenerators(pln,optionalPaths)
+ % Returns a list of names and coresponding handle for stf
+ % generators. Returns all stf generators when no arg is
+ % given. If no generators are found return gonna be empty.
+ %
+ % call:
+ % classList = matRad_StfGeneratorBase.getAvailableGenerators(pln,optional_path)
+ %
+ % input:
+ % pln: containing proposed generator and machine file informations
+ % optionalPath: cell array of other folders to search in
+ %
+ % returns:
+ % classList: struct array containing name, shortName, className, and
+ % handle for construction of the Generator
+
+ matRad_cfg = MatRad_Config.instance();
+
+ %Parse inputs
+ if nargin < 2
+ optionalPaths = {fileparts(mfilename("fullpath"))};
+ else
+ if ~(iscellstr(optionalPaths) && all(optionalPaths))
+ matRad_cfg.dispError('Invalid path array!');
+ end
+
+ optionalPaths = horzcat(fileparts(mfilename("fullpath")),optionalPaths);
+ end
+
+ if nargin < 1
+ pln = [];
+ else
+ if ~(isstruct(pln) || isempty(pln))
+ matRad_cfg.dispError('Invalid pln!');
+ end
+ end
+
+ %Get available, valid classes through call to matRad helper function
+ %for finding subclasses
+ availableStfGenerators = matRad_findSubclasses(mfilename('class'),'folders',optionalPaths,'includeAbstract',false);
+
+ %Now filter for pln
+ ix = [];
+
+ if nargin >= 1 && ~isempty(pln)
+ machine = matRad_loadMachine(pln);
+ machineMode = machine.meta.radiationMode;
+
+ for cIx = 1:length(availableStfGenerators)
+ mc = availableStfGenerators{cIx};
+ availabilityFuncStr = [mc.Name '.isAvailable'];
+ %availabilityFunc = str2func(availabilityFuncStr); %str2func does not seem to work on static class functions in Octave 5.2.0
+ try
+ %available = availabilityFunc(pln,machine);
+ available = eval([availabilityFuncStr '(pln,machine)']);
+ catch
+ available = false;
+ mpList = mc.PropertyList;
+ if matRad_cfg.isMatlab
+ loc = find(arrayfun(@(x) strcmp('possibleRadiationModes',x.Name),mpList));
+ propValue = mpList(loc).DefaultValue;
+ else
+ loc = find(cellfun(@(x) strcmp('possibleRadiationModes',x.Name),mpList));
+ propValue = mpList{loc}.DefaultValue;
+ end
+
+ if any(strcmp(propValue, pln.radiationMode))
+ % get radiation mode from the in pln proposed basedata machine file
+ % add current class to return lists if the
+ % radiation mode is compatible
+ if(any(strcmp(propValue, machineMode)))
+ available = true;
+
+ end
+ end
+ end
+ if available
+ ix = [ix cIx];
+ end
+ end
+
+ availableStfGenerators = availableStfGenerators(ix);
+ end
+
+ classList = matRad_identifyClassesByConstantProperties(availableStfGenerators,'shortName','defaults',matRad_cfg.defaults.propStf.generator,'additionalPropertyNames',{'name'});
+
+ end
+
+ function [available,msg] = isAvailable(pln,machine)
+ % return a boolean if the generator is is available for the given pln
+ % struct. Needs to be implemented in non abstract subclasses
+ % input:
+ % - pln: matRad pln struct
+ % - machine: optional machine to avoid loading the machine from
+ % disk (makes sense to use if machine already loaded)
+ % output:
+ % - available: boolean value to check if the dose engine is
+ % available for the given pln/machine
+ % - msg: msg to elaborate on availability. If not available,
+ % a msg string indicates an error during the check
+ % if available, indicates a warning that not all
+ % information was present in the machine file and
+ % approximations need to be made
+
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('This is an Abstract Base class! Function needs to be called for instantiable subclasses!');
+ end
+
+ % Machine Loader
+ % Currently just uses the matRad function that asks for pln
+ function machine = loadMachine(radiationMode,machineName)
+ machine = matRad_loadMachine(struct('radiationMode',radiationMode,'machine',machineName));
+ end
+ end
+end
diff --git a/matRad/steering/matRad_StfGeneratorBrachy.m b/matRad/steering/matRad_StfGeneratorBrachy.m
new file mode 100644
index 000000000..3a4de97f6
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorBrachy.m
@@ -0,0 +1,270 @@
+classdef matRad_StfGeneratorBrachy < matRad_StfGeneratorBase
+
+ properties (Constant)
+ name = 'Basic Brachytherapy Template';
+ shortName = 'SimpleBrachy';
+ possibleRadiationModes = {'brachy'};
+ end
+ properties
+ needle
+ template
+ bixelWidth
+ end
+
+ methods
+ function this = matRad_StfGeneratorBrachy(pln)
+ if nargin < 1
+ pln = [];
+ end
+ this@matRad_StfGeneratorBase(pln);
+ if isempty(this.radiationMode)
+ this.radiationMode = 'brachy';
+ end
+ end
+
+ function setDefaults(this)
+ this.setDefaults@matRad_StfGeneratorBase();
+ this.machine = 'HDR';
+
+ this.needle = struct(...
+ 'seedDistance',10,...
+ 'seedsNo',6);
+
+ this.template.type = 'checkerboard';
+
+ this.bixelWidth = 5;
+ end
+ end
+
+ methods (Access = protected)
+ function initialize(this)
+ this.initialize@matRad_StfGeneratorBase();
+ if ~isa(this.multScen,'matRad_NominalScenario') && ~strcmp(this.multScen,'nomScen')
+ matRad_cfg.dispError('Brachy Therapy does only work with a single nominal scenario model!');
+ end
+ end
+
+ function stf = generateSourceGeometry(this)
+ matRad_cfg = MatRad_Config.instance();
+
+ if ~isfield(this.template,'root')
+ this.template.root = matRad_getTemplateRoot(this.ct,this.cst);
+ end
+
+ if ~isfield(this.needle,'seedDistance') || ~isfield(this.needle,'seedsNo')
+ matRad_cfg.dispError('Needle information missing!');
+ end
+
+
+ stf.targetVolume.Xvox = this.voxTargetWorldCoords(:,1);
+ stf.targetVolume.Yvox = this.voxTargetWorldCoords(:,2);
+ stf.targetVolume.Zvox = this.voxTargetWorldCoords(:,3);
+
+ if ~isfield(this.template,'type')
+ matRad_cfg.dispError('No template type specified!');
+ end
+
+ if strcmp(this.template.type, 'manual')
+ %nothing to be done
+ if ~isfield(this.template,'activeNeedles')
+ matRad_cfg.dispError('No active needle mask defined for template!');
+ end
+
+ elseif strcmp(this.template.type, 'checkerboard')
+
+ %Bounding box
+ xMin = min(stf.targetVolume.Xvox);
+ xMax = max(stf.targetVolume.Xvox);
+ yMin = min(stf.targetVolume.Yvox);
+ yMax = max(stf.targetVolume.Yvox);
+
+ %Calculate checkerboard size
+ nCheckerboardX = ceil((xMax - xMin) ./ this.bixelWidth);
+ nCheckerBoardY = ceil((yMax - yMin) ./ this.bixelWidth);
+
+ %Create checkerboard
+ this.template.activeNeedles = this.createCheckerboard(nCheckerboardX, nCheckerBoardY);
+
+
+
+ else
+ matRad_cfg.dispError('Template type ''%s'' invalid / not implemented!',this.template.type);
+ end
+
+ [row, col] = find(this.template.activeNeedles);
+ templX = col * this.bixelWidth + this.template.root(1) - (size(this.template.activeNeedles,1) + 1) / 2 * this.bixelWidth;
+ templY = row * this.bixelWidth + this.template.root(2) - (size(this.template.activeNeedles,2) + 1) / 2 * this.bixelWidth;
+ templZ = ones(size(col)) + this.template.root(3);
+
+
+ %% meta info from pln
+ stf.radiationMode = this.radiationMode;
+ stf.numOfSeedsPerNeedle = this.needle.seedsNo;
+ stf.numOfNeedles = sum(this.template.activeNeedles(:));
+ stf.totalNumOfBixels = stf.numOfSeedsPerNeedle * stf.numOfNeedles; % means total number of seeds
+
+ %% generate 2D template points
+ % the template origin is set at its center. In the image coordinate system,
+ % the center will be positioned at the bottom of the volume of interest.
+ stf.template = [templX'; templY'; templZ'];
+ stf.templateNormal = [0, 0, 1];
+
+ %% generate seed positions
+ % seed positions can be generated from needles, template and orientation
+ % needles are assumed to go through the template vertically
+
+ % needle position
+ d = this.needle.seedDistance;
+ seedsNo = this.needle.seedsNo;
+ needleDist(1, 1, :) = d .* [0:seedsNo - 1]'; % 1x1xN Array with seed positions on needle
+ needleDir = needleDist .* [0; 0; 1];
+ seedPos_coord_need_seed = needleDir + stf.template;
+ seedPos_need_seed_coord = shiftdim(seedPos_coord_need_seed, 1);
+ % the output array has the dimensions (needleNo, seedNo, coordinates)
+ X = seedPos_need_seed_coord(:, :, 1);
+ Y = seedPos_need_seed_coord(:, :, 2);
+ Z = seedPos_need_seed_coord(:, :, 3);
+
+ stf.seedPoints.x = reshape(X, 1, []);
+ stf.seedPoints.y = reshape(Y, 1, []);
+ stf.seedPoints.z = reshape(Z, 1, []);
+
+ matRad_cfg.dispInfo('Processing completed: %d%%\n', 100);
+
+ %% visualize results of visMode is nonzero
+ if this.visMode > 0
+ clf
+ SeedPoints = plot3(stf.seedPoints.x, stf.seedPoints.y, stf.seedPoints.z, '.', 'DisplayName', 'seed points', 'Color', 'black', 'markersize', 5);
+ title('3D Visualization of seed points')
+ xlabel('X (left) [mm]')
+ ylabel('Y (posterior) [mm]')
+ zlabel('Z (superior) [mm]')
+ hold on
+
+ % plot 3d VOI points
+ TargX = stf.targetVolume.Xvox;
+ TargY = stf.targetVolume.Yvox;
+ TargZ = stf.targetVolume.Zvox;
+ % Prostate = plot3(TargX, TargY, TargZ, '.', 'Color', 'b', 'DisplayName', 'prostate');
+
+ % Prepare points for boundary calculation
+ P = [TargX', TargY', TargZ'];
+
+ if ~isempty(P)
+ % Determine the environment
+ if matRad_cfg.isOctave
+ % Octave environment
+ [uni, ~] = sort(unique(TargX));
+ n = length(uni);
+ outline = zeros(2 * n, 2);
+
+ for i = 1:n
+ y_list = TargY(TargX == uni(i));
+ y_max = max(y_list);
+ y_min = min(y_list);
+ outline(i, :) = [uni(i), y_max];
+ outline(2 * n - i + 1, :) = [uni(i), y_min];
+ end
+
+ % Plot the points and the computed outline
+ figure;
+ plot(TargX, TargY, 'b+', 'DisplayName', 'VOI Points');
+ hold on;
+ plot(outline(:, 1), outline(:, 2), 'g-', 'LineWidth', 3, 'DisplayName', 'Computed Outline');
+
+ % Calculate the area enclosed by the outline
+ area = polyarea(outline(:, 1), outline(:, 2));
+ disp(['Polygon area: ', num2str(area)]);
+
+ hold off;
+ else
+ % MATLAB environment
+ k = boundary(P, 1);
+ trisurf(k, P(:, 1), P(:, 2), P(:, 3), 'FaceColor', 'red', 'FaceAlpha', 0.1, 'LineStyle', 'none')
+ end
+ else
+ matRad_cfg.dispWarning('Target volume points are empty, boundary cannot be computed.');
+ throw(MException('MATLAB:class:AbstractMember','Target Volumes need to be implemented!'));
+ end
+ end
+
+ % throw warning if seed points are more than twice the central
+ % distance outside the TARGET volume or if no seed points are in the
+ % target volume
+
+ seedPointsX = stf.seedPoints.x - this.template.root(1);
+ seedPointsY = stf.seedPoints.y - this.template.root(2);
+ seedPointsZ = stf.seedPoints.z - this.template.root(3);
+
+ targetVolumeX = stf.targetVolume.Xvox - this.template.root(1);
+ targetVolumeY = stf.targetVolume.Yvox - this.template.root(2);
+ targetVolumeZ = stf.targetVolume.Zvox - this.template.root(3);
+
+ if any([max(seedPointsX) >= 4 * max(targetVolumeX), ...
+ min(seedPointsX) <= 4 * min(targetVolumeX), ...
+ max(seedPointsY) >= 4 * max(targetVolumeY), ...
+ min(seedPointsY) <= 4 * min(targetVolumeY), ...
+ max(seedPointsZ) >= 4 * max(targetVolumeZ), ...
+ min(seedPointsZ) <= 4 * min(targetVolumeZ)])
+ matRad_cfg.dispWarning('Seeds far outside the target volume');
+ end
+
+ if all([max(targetVolumeX) <= min(seedPointsX), min(targetVolumeX) >= max(seedPointsX), ...
+ max(targetVolumeY) <= min(seedPointsY), min(targetVolumeY) >= max(seedPointsY), ...
+ max(targetVolumeZ) <= min(seedPointsZ), min(targetVolumeZ) >= max(seedPointsZ)])
+ matRad_cfg.dispWarning('No seed points in VOI')
+ end
+ end
+ end
+ methods
+ function checkerboardMatrix = createCheckerboard(this, numRows, numCols)
+ % Initialize checkerboard matrix
+ checkerboardMatrix = false(numRows, numCols);
+
+ % Fill checkerboard matrix
+ ix = 1:numel(checkerboardMatrix);
+ [i,j] = ind2sub(size(checkerboardMatrix), ix);
+ checkerboardMatrix(ix) = mod(i + j, 2) == 0;
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ msg = [];
+ available = false;
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(matRad_StfGeneratorBrachy.possibleRadiationModes, machine.meta.radiationMode)) && any(strcmp(matRad_StfGeneratorBrachy.possibleRadiationModes, pln.radiationMode));
+
+ %Sanity check compatibility
+ if checkModality
+ checkModality = strcmp(machine.meta.radiationMode,pln.radiationMode);
+ end
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ available = preCheck;
+ end
+ end
+end
+
+
+
diff --git a/matRad/steering/matRad_StfGeneratorExternalRayBixelAbstract.m b/matRad/steering/matRad_StfGeneratorExternalRayBixelAbstract.m
new file mode 100644
index 000000000..d26826ee7
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorExternalRayBixelAbstract.m
@@ -0,0 +1,479 @@
+classdef (Abstract) matRad_StfGeneratorExternalRayBixelAbstract < matRad_StfGeneratorBase
+% matRad_StfGeneratorPhotonRayBixelAbstract: Abstract Superclass for
+% external beam stf generators using the ray-bixel mechanism
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ gantryAngles = 0;
+ couchAngles = 0;
+ bixelWidth = 0;
+ isoCenter
+ fillEmptyBixels
+ centered
+ end
+
+ properties (Dependent)
+ numOfBeams;
+ end
+
+ properties (Access = protected, Hidden)
+ lockAngleUpdate = false;
+ end
+
+
+ methods
+ function this = matRad_StfGeneratorExternalRayBixelAbstract(pln)
+ % Constructs ExternalStfGenerator with or without pln
+ if nargin < 1
+ pln = [];
+ end
+ this@matRad_StfGeneratorBase(pln);
+ end
+
+ function setDefaults(this)
+ % Set default values for ExternalStfGenerator
+
+ this.setDefaults@matRad_StfGeneratorBase();
+ end
+
+ function nBeams = get.numOfBeams(this)
+ % Return number of beams obtained from angles
+
+ nBeams = numel(this.gantryAngles);
+ if nBeams ~= numel(this.couchAngles)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('For some reason, we have a different number of beam and couch angles!');
+ end
+ end
+
+ function set.gantryAngles(this,angles)
+ % Set gantry angles and update couch angles if necessary
+
+ validateattributes(angles,{'numeric'},{'vector','nonempty','nonnan'});
+ oldAngles = this.gantryAngles;
+ this.gantryAngles = angles;
+ if ~this.lockAngleUpdate
+ this.lockAngleUpdate = true;
+ if numel(this.gantryAngles) > numel(this.couchAngles)
+ %Append Couch angles with zeros
+ this.couchAngles = [this.couchAngles zeros(1,numel(this.gantryAngles)-numel(this.couchAngles))];
+ elseif numel(this.couchAngles) > numel(this.gantryAngles)
+ %Try to identify the removed beam angles
+ [removedAngles,ix] = setdiff(oldAngles,this.gantryAngles);
+
+ nRemovedAngles = numel(this.couchAngles) - numel(this.gantryAngles);
+
+ if ~isempty(ix) && numel(ix) == nRemovedAngles
+ %Remove corresponding couch angles
+ this.couchAngles(ix) = [];
+ else
+ this.couchAngles(end-nRemovedAngles+1:end) = [];
+ end
+ end
+ this.lockAngleUpdate = false;
+ end
+ end
+
+ function set.couchAngles(this,angles)
+ % Set couch angles and update gantry angles if necessary
+
+ validateattributes(angles,{'numeric'},{'vector','nonempty','nonnan'});
+ oldAngles = this.couchAngles;
+ this.couchAngles = angles;
+ if ~this.lockAngleUpdate
+ this.lockAngleUpdate = true;
+ if numel(this.couchAngles) > numel(this.gantryAngles)
+ %Append Gantry angles with zeros
+ this.gantryAngles = [this.gantryAngles zeros(1,numel(this.couchAngles)-numel(this.gantryAngles))];
+ elseif numel(this.gantryAngles) > numel(this.couchAngles)
+ %Try to identify the removed couch angles
+ [removedAngles,ix] = setdiff(oldAngles,this.couchAngles);
+
+ nRemovedAngles = numel(this.gantryAngles) - numel(this.couchAngles);
+
+ if ~isempty(ix) && numel(ix) == nRemovedAngles
+ %Remove corresponding gantry angles
+ this.gantryAngles(ix) = [];
+ else
+ this.gantryAngles(end-nRemovedAngles+1:end) = [];
+ end
+ end
+ this.lockAngleUpdate = false;
+ end
+ end
+ end
+
+ methods (Access = protected)
+ function initialize(this)
+
+ this.initialize@matRad_StfGeneratorBase();
+
+ if this.visMode > 1
+ visBool = true;
+ else
+ visBool = false;
+ end
+
+ if isempty(this.isoCenter)
+ this.isoCenter = matRad_getIsoCenter(this.cst,this.ct,visBool);
+ end
+
+ if ~isequal(size(this.isoCenter),[this.numOfBeams,3]) && ~size(this.isoCenter,1) ~= 1
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning('IsoCenter invalid, creating new one automatically!');
+ this.isoCenter = matRad_getIsoCenter(this.cst,this.ct,visBool);
+ end
+
+ if size(this.isoCenter,1) == 1
+ this.isoCenter = repmat(this.isoCenter,this.numOfBeams,1);
+ end
+ end
+
+ function rayPos = getRayPositionMatrix(this, beam)
+ % Correct for iso center position. Whit this correction Isocenter is
+ % (0,0,0) [mm]
+ isoCoords = this.voxTargetWorldCoords - beam.isoCenter;
+
+ % Get the (active) rotation matrix. We perform a passive/system
+ % rotation with row vector coordinates, which would introduce two
+ % inversions / transpositions of the matrix, thus no changes to the
+ % rotation matrix are necessary
+ rotMat_system_T = matRad_getRotationMatrix(beam.gantryAngle,beam.couchAngle);
+
+ coordsAtIsoCenterPlane = isoCoords*rotMat_system_T;
+ coordsAtIsoCenterPlane = (coordsAtIsoCenterPlane*beam.SAD)./(beam.SAD+coordsAtIsoCenterPlane(:,2));
+ coordsAtIsoCenterPlane(:,2) = 0;
+
+ rayPos = unique(beam.bixelWidth*round(coordsAtIsoCenterPlane./beam.bixelWidth),'rows');
+
+ % pad ray position array if resolution of target voxel grid not sufficient
+ maxCtResolution = max([this.ct.resolution.x this.ct.resolution.y this.ct.resolution.z]);
+ if beam.bixelWidth < maxCtResolution
+ origRayPos = rayPos;
+ for j = -floor(maxCtResolution/beam.bixelWidth):floor(maxCtResolution/beam.bixelWidth)
+ for k = -floor(maxCtResolution/beam.bixelWidth):floor(maxCtResolution/beam.bixelWidth)
+ if abs(j)+abs(k)==0
+ continue;
+ end
+ rayPos = [rayPos; origRayPos(:,1)+j*beam.bixelWidth origRayPos(:,2) origRayPos(:,3)+k*beam.bixelWidth];
+ end
+ end
+ end
+
+ % remove spaces within rows of bixels for DAO
+ %TODO Only Photons
+ if this.fillEmptyBixels
+ % create single x,y,z vectors
+ x = rayPos(:,1);
+ y = rayPos(:,2);
+ z = rayPos(:,3);
+ uniZ = unique(z);
+ for j = 1:numel(uniZ)
+ x_loc = x(z == uniZ(j));
+ x_min = min(x_loc);
+ x_max = max(x_loc);
+ x = [x; (x_min:beam.bixelWidth:x_max)'];
+ y = [y; zeros((x_max-x_min)/beam.bixelWidth+1,1)];
+ z = [z; uniZ(j)*ones((x_max-x_min)/beam.bixelWidth+1,1)];
+ end
+
+ rayPos = [x,y,z];
+ end
+
+ % remove double rays
+ rayPos = unique(rayPos,'rows');
+ end
+
+ function beam = initBeamData(this,beamIndex)
+ beam.gantryAngle = this.gantryAngles(beamIndex);
+ beam.couchAngle = this.couchAngles(beamIndex);
+ beam.isoCenter = this.isoCenter(beamIndex,:);
+ beam.bixelWidth = this.bixelWidth;
+ beam.radiationMode = this.radiationMode;
+ beam.machine = this.machine.meta.machine;
+ beam.SAD = this.machine.meta.SAD;
+ end
+
+
+ function stf = generateSourceGeometry(this)
+ % Generate basic source geometry (beams & rays) for external beam therapy
+
+ matRad_cfg = MatRad_Config.instance;
+
+ % loop over all angles
+ for i = 1:length(this.gantryAngles)
+ % Save meta information for treatment plan
+ beam = this.initBeamData(i);
+
+ beam = this.initRays(beam);
+
+ beam = this.setBeamletEnergies(beam);
+
+ if ~isfield(beam.ray,'energy')
+ matRad_cfg.dispError('Error generating stf struct: no suitable energies found. Check if bixelwidth is too large.');
+ end
+
+ beam = this.finalizeBeam(beam); % Post Processing for Ions
+
+ stf(i) = beam;
+
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(i,length(this.gantryAngles));
+ end
+
+ %% visualization
+ if this.visMode > 0
+
+ %center coordinates
+ x = this.ct.x - stf(i).isoCenter(1);
+ y = this.ct.y - stf(i).isoCenter(2);
+ z = this.ct.z - stf(i).isoCenter(3);
+ d = sqrt(sum(([x(1) y(2) z(3)] - [x(end) y(end) z(end)]).^2));
+ limits = [-d/2 d/2 -d/2 d/2 -d/2 d/2];
+
+ clf;
+ % first subplot: visualization in bev
+ hAxBEV = subplot(1,2,1);
+ set(hAxBEV,'DataAspectRatioMode','manual');
+ hold on;
+
+ isoCoords = this.voxTargetWorldCoords - stf(i).isoCenter;
+
+ % Get the (active) rotation matrix. We perform a passive/system
+ % rotation with row vector coordinates, which would introduce two
+ % inversions / transpositions of the matrix, thus no changes to the
+ % rotation matrix are necessary
+ rotMat_system_T = matRad_getRotationMatrix(stf(i).gantryAngle,stf(i).couchAngle);
+
+ rot_coords = isoCoords*rotMat_system_T;
+
+ % plot rotated target coordinates
+ plot3(rot_coords(:,1),rot_coords(:,2),rot_coords(:,3),'r.')
+
+ % surface rendering
+ if this.visMode == 2
+
+ % computes surface
+ patSurfCube = 0*this.ct.cube{1};
+ idx = [this.cst{:,4}];
+ idx = unique(vertcat(idx{:}));
+ patSurfCube(idx) = 1;
+
+ [f,v] = isosurface(x,y,z,patSurfCube,.5);
+
+ vRot = v*rotMat_system_T;
+
+ % rotate surface
+ rotated_surface = v*rotMat_system_T;
+
+ % surface rendering
+ surface = patch('Faces',f,'Vertices',rotated_surface);
+ set(surface,'FaceColor',[0 0 1],'EdgeColor','none','FaceAlpha',.4);
+ lighting gouraud;
+
+ end
+
+ % plot projection matrix: coordinates at isocenter
+ plot3(rayPos_bev(:,1),rayPos_bev(:,2),rayPos_bev(:,3),'k.');
+
+ % Plot matrix border of matrix at isocenter
+ for j = 1:stf(i).numOfRays
+
+ % Compute border for every bixels
+ targetPoint_vox_X_1 = stf(i).ray(j).targetPoint_bev(:,1) + stf(i).bixelWidth;
+ targetPoint_vox_Y_1 = stf(i).ray(j).targetPoint_bev(:,2);
+ targetPoint_vox_Z_1 = stf(i).ray(j).targetPoint_bev(:,3) + stf(i).bixelWidth;
+
+ targetPoint_vox_X_2 = stf(i).ray(j).targetPoint_bev(:,1) + stf(i).bixelWidth;
+ targetPoint_vox_Y_2 = stf(i).ray(j).targetPoint_bev(:,2);
+ targetPoint_vox_Z_2 = stf(i).ray(j).targetPoint_bev(:,3) - stf(i).bixelWidth;
+
+ targetPoint_vox_X_3 = stf(i).ray(j).targetPoint_bev(:,1) - stf(i).bixelWidth;
+ targetPoint_vox_Y_3 = stf(i).ray(j).targetPoint_bev(:,2);
+ targetPoint_vox_Z_3 = stf(i).ray(j).targetPoint_bev(:,3) - stf(i).bixelWidth;
+
+ targetPoint_vox_X_4 = stf(i).ray(j).targetPoint_bev(:,1) - stf(i).bixelWidth;
+ targetPoint_vox_Y_4 = stf(i).ray(j).targetPoint_bev(:,2);
+ targetPoint_vox_Z_4 = stf(i).ray(j).targetPoint_bev(:,3) + stf(i).bixelWidth;
+
+ % plot
+ plot3([stf(i).sourcePoint_bev(1) targetPoint_vox_X_1],[stf(i).sourcePoint_bev(2) targetPoint_vox_Y_1],[stf(i).sourcePoint_bev(3) targetPoint_vox_Z_1],'g')
+ plot3([stf(i).sourcePoint_bev(1) targetPoint_vox_X_2],[stf(i).sourcePoint_bev(2) targetPoint_vox_Y_2],[stf(i).sourcePoint_bev(3) targetPoint_vox_Z_2],'g')
+ plot3([stf(i).sourcePoint_bev(1) targetPoint_vox_X_3],[stf(i).sourcePoint_bev(2) targetPoint_vox_Y_3],[stf(i).sourcePoint_bev(3) targetPoint_vox_Z_3],'g')
+ plot3([stf(i).sourcePoint_bev(1) targetPoint_vox_X_4],[stf(i).sourcePoint_bev(2) targetPoint_vox_Y_4],[stf(i).sourcePoint_bev(3) targetPoint_vox_Z_4],'g')
+
+ end
+
+ % Plot properties
+ view(hAxBEV,0,-90);
+ xlabel(hAxBEV,'X [mm]');
+ ylabel(hAxBEV,'Y [mm]');
+ zlabel(hAxBEV,'Z [mm]');
+ title(hAxBEV,'Beam''s eye view');
+
+ axis(hAxBEV,limits);
+
+ % second subplot: visualization in lps coordinate system
+ hAxLPS = subplot(1,2,2);
+
+ % Plot target coordinates whitout any rotation
+ plot3(isoCoords(:,1),isoCoords(:,2),isoCoords(:,3),'r.')
+ hold on;
+
+ % Rotated projection matrix at isocenter
+ isocenter_plane_coor = rayPos_bev*rotMat_vectors_T;
+
+ % Plot isocenter plane
+ plot3(isocenter_plane_coor(:,1),isocenter_plane_coor(:,2),isocenter_plane_coor(:,3),'y.');
+
+ % Plot rotated bixels border.
+ for j = 1:stf(i).numOfRays
+ % Generate rotated projection target points.
+ targetPoint_vox_1_rotated = [stf(i).ray(j).targetPoint_bev(:,1) + stf(i).bixelWidth,stf(i).ray(j).targetPoint_bev(:,2),stf(i).ray(j).targetPoint_bev(:,3) + stf(i).bixelWidth]*rotMat_vectors_T;
+ targetPoint_vox_2_rotated = [stf(i).ray(j).targetPoint_bev(:,1) + stf(i).bixelWidth,stf(i).ray(j).targetPoint_bev(:,2),stf(i).ray(j).targetPoint_bev(:,3) - stf(i).bixelWidth]*rotMat_vectors_T;
+ targetPoint_vox_3_rotated = [stf(i).ray(j).targetPoint_bev(:,1) - stf(i).bixelWidth,stf(i).ray(j).targetPoint_bev(:,2),stf(i).ray(j).targetPoint_bev(:,3) - stf(i).bixelWidth]*rotMat_vectors_T;
+ targetPoint_vox_4_rotated = [stf(i).ray(j).targetPoint_bev(:,1) - stf(i).bixelWidth,stf(i).ray(j).targetPoint_bev(:,2),stf(i).ray(j).targetPoint_bev(:,3) + stf(i).bixelWidth]*rotMat_vectors_T;
+
+ % Plot rotated target points.
+ plot3([stf(i).sourcePoint(1) targetPoint_vox_1_rotated(:,1)],[stf(i).sourcePoint(2) targetPoint_vox_1_rotated(:,2)],[stf(i).sourcePoint(3) targetPoint_vox_1_rotated(:,3)],'g')
+ plot3([stf(i).sourcePoint(1) targetPoint_vox_2_rotated(:,1)],[stf(i).sourcePoint(2) targetPoint_vox_2_rotated(:,2)],[stf(i).sourcePoint(3) targetPoint_vox_2_rotated(:,3)],'g')
+ plot3([stf(i).sourcePoint(1) targetPoint_vox_3_rotated(:,1)],[stf(i).sourcePoint(2) targetPoint_vox_3_rotated(:,2)],[stf(i).sourcePoint(3) targetPoint_vox_3_rotated(:,3)],'g')
+ plot3([stf(i).sourcePoint(1) targetPoint_vox_4_rotated(:,1)],[stf(i).sourcePoint(2) targetPoint_vox_4_rotated(:,2)],[stf(i).sourcePoint(3) targetPoint_vox_4_rotated(:,3)],'g')
+ end
+
+ % surface rendering
+ if this.visMode == 2
+ surface = patch('Faces',f,'Vertices',v);
+ set(surface,'FaceColor',[0 0 1],'EdgeColor','none','FaceAlpha',.4);
+ lighting gouraud;
+ end
+
+ % labels etc.
+ daspect([1 1 1]);
+ view(0,-90);
+ xlabel(hAxBEV,'X [mm]');
+ ylabel(hAxBEV,'Y [mm]');
+ zlabel(hAxBEV,'Z [mm]');
+ title('lps coordinate system');
+ axis(limits);
+ drawnow();
+ pause(1);
+ end
+
+
+ % Show progress
+ if matRad_cfg.logLevel > 2
+ matRad_progress(i,length(this.gantryAngles));
+ end
+ end
+ end
+
+ function beam = initRays(this,beam)
+ % source position in bev
+ beam.sourcePoint_bev = [0 -beam.SAD 0];
+
+ rayPos_bev = this.getRayPositionMatrix(beam);
+
+ % Save the number of rays
+ beam.numOfRays = size(rayPos_bev,1);
+
+ % Save ray and target position in beam eye's view (bev)
+ for j = 1:beam.numOfRays
+ beam.ray(j).rayPos_bev = rayPos_bev(j,:);
+ beam.ray(j).targetPoint_bev = [2*beam.ray(j).rayPos_bev(1) ...
+ beam.SAD ...
+ 2*beam.ray(j).rayPos_bev(3)];
+ end
+
+ % get (active) rotation matrix
+ % transpose matrix because we are working with row vectors
+ rotMat_vectors_T = transpose(matRad_getRotationMatrix(beam.gantryAngle,beam.couchAngle));
+
+ %Source point in LPS system
+ beam.sourcePoint = beam.sourcePoint_bev*rotMat_vectors_T;
+
+ % Save ray and target position in lps system.
+ for j = 1:beam.numOfRays
+ beam.ray(j).rayPos = beam.ray(j).rayPos_bev*rotMat_vectors_T;
+ beam.ray(j).targetPoint = beam.ray(j).targetPoint_bev*rotMat_vectors_T;
+ end
+ end
+
+ function beam = setBeamletEnergies(this,beam)
+ % Abstract method to set source energy on beam. Implemented in Subclasses
+
+ throw(MException('MATLAB:class:AbstractMember','This method is not implemented in the base class'));
+ end
+
+
+ function beam = finalizeBeam(this,beam)
+ %Finalize meta data of beam
+
+ matRad_cfg = MatRad_Config.instance();
+
+ numOfRays = numel(beam.ray);
+ if ~isfield(beam,'numOfRays')
+ beam.numOfRays = numOfRays;
+ end
+ if ~isequal(numOfRays,beam.numOfRays)
+ matRad_cfg.dispError('Validation of number of rays failed!');
+ end
+
+ % Calculate the number of bixels per ray
+ numOfBixelsPerRay = arrayfun(@(r) numel(r.energy),beam.ray);
+ if ~isfield(beam,'numOfBixelsPerRay')
+ beam.numOfBixelsPerRay = numOfBixelsPerRay;
+ end
+ if ~isequal(numOfBixelsPerRay,beam.numOfBixelsPerRay)
+ matRad_cfg.dispError('Validation of number of bixels per ray failed!');
+ end
+
+ % Calculate the total number of bixels
+ totalNumOfBixels = sum(beam.numOfBixelsPerRay);
+ if ~isfield(beam,'totalNumOfBixels')
+ beam.totalNumOfBixels = totalNumOfBixels;
+ end
+ if ~isequal(totalNumOfBixels,beam.totalNumOfBixels)
+ matRad_cfg.dispError('Validation of total number of bixels failed!');
+ end
+
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ %checkBasic
+ available = isfield(machine,'meta') && isfield(machine,'data');
+
+ available = available && isfield(machine.meta,'machine');
+
+ available = available && isfield(machine.meta,'SAD') && isscalar(machine.meta.SAD);
+
+ if ~available
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ else
+ msg = [];
+ end
+ end
+ end
+end
+
diff --git a/matRad/steering/matRad_StfGeneratorParticleIMPT.m b/matRad/steering/matRad_StfGeneratorParticleIMPT.m
new file mode 100644
index 000000000..247c14770
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorParticleIMPT.m
@@ -0,0 +1,315 @@
+classdef matRad_StfGeneratorParticleIMPT < matRad_StfGeneratorParticleRayBixelAbstract
+% matRad_ParticleStfGenerator: Abstract Superclass for Steering information
+% generators. Steering information is used to guide the dose calculation
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ name = 'Particle IMPT stf Generator';
+ shortName = 'ParticleIMPT';
+ possibleRadiationModes = {'protons','helium','carbon'};
+ end
+
+ properties
+ longitudinalSpotSpacing;
+ end
+
+ methods
+ function this = matRad_StfGeneratorParticleIMPT(pln)
+ if nargin < 1
+ pln = [];
+ end
+ this@matRad_StfGeneratorParticleRayBixelAbstract(pln);
+ end
+ end
+
+
+ methods (Access = protected)
+
+ function beam = initBeamData(this,beam)
+ beam = this.initBeamData@matRad_StfGeneratorParticleRayBixelAbstract(beam);
+ beam.longitudinalSpotSpacing = this.longitudinalSpotSpacing;
+ end
+
+ function beam = setBeamletEnergies(this,beam)
+ %Assigns the max particle machine energy layers to all rays
+
+ isoCenterInCubeCoords = matRad_world2cubeCoords(beam.isoCenter,this.ct);
+
+ beam.numOfBixelsPerRay = zeros(1,beam.numOfRays);
+
+ for j = beam.numOfRays:-1:1
+
+ for shiftScen = 1:this.multScen.totNumShiftScen
+ % ray tracing necessary to determine depth of the target
+ [alphas,l{shiftScen},rho{shiftScen},d12,~] = matRad_siddonRayTracer(isoCenterInCubeCoords + this.multScen.isoShift(shiftScen,:), ...
+ this.ct.resolution, ...
+ beam.sourcePoint, ...
+ beam.ray(j).targetPoint, ...
+ [this.ct.cube {this.voiTarget}]);
+
+ %Used for generic range-shifter placement
+ ctEntryPoint = alphas(1) * d12;
+ end
+
+ % target hit
+ rhoVOITarget = [];
+ for shiftScen = 1:this.multScen.totNumShiftScen
+ rhoVOITarget = [rhoVOITarget, rho{shiftScen}{end}];
+ end
+
+ if any(rhoVOITarget)
+ counter = 0;
+
+ %Here we iterate through scenarios to check the required
+ %energies w.r.t lateral position.
+ %TODO: iterate over the linear scenario mask instead?
+ for ctScen = 1:this.multScen.numOfCtScen
+ for shiftScen = 1:this.multScen.totNumShiftScen
+ for rangeShiftScen = 1:this.multScen.totNumRangeScen
+ if this.multScen.scenMask(ctScen,shiftScen,rangeShiftScen)
+ counter = counter+1;
+
+ % compute radiological depths
+ % http://www.ncbi.nlm.nih.gov/pubmed/4000088, eq 14
+ rSP = l{shiftScen} .* rho{shiftScen}{ctScen};
+ radDepths = cumsum(rSP) - 0.5*rSP;
+
+ if this.multScen.relRangeShift(rangeShiftScen) ~= 0 || this.multScen.absRangeShift(rangeShiftScen) ~= 0
+ radDepths = radDepths +... % original cube
+ rho{shiftScen}{ctScen}*this.multScen.relRangeShift(rangeShiftScen) +... % rel range shift
+ this.multScen.absRangeShift(rangeShiftScen); % absolute range shift
+ radDepths(radDepths < 0) = 0;
+ end
+
+ % find target entry & exit
+ diff_voi = [diff([rho{shiftScen}{end}])];
+ entryIx = find(diff_voi == 1);
+ exitIx = find(diff_voi == -1);
+
+ %We approximate the interface using the rad depth between the last voxel before and the first voxel after the interface
+ % This captures the case that the first relevant voxel is a target voxel
+ targetEntry(counter,1:length(entryIx)) = (radDepths(entryIx) + radDepths(entryIx+1)) ./ 2;
+ targetExit(counter,1:length(exitIx)) = (radDepths(exitIx) + radDepths(exitIx+1)) ./ 2;
+ end
+ end
+ end
+ end
+
+ targetEntry(targetEntry == 0) = NaN;
+ targetExit(targetExit == 0) = NaN;
+
+ targetEntry = min(targetEntry);
+ targetExit = max(targetExit);
+
+ %check that each energy appears only once in stf
+ if(numel(targetEntry)>1)
+ m = numel(targetEntry);
+ while(m>1)
+ if(targetEntry(m) < targetExit(m-1))
+ targetExit(m-1) = max(targetExit(m-1:m));
+ targetExit(m)=[];
+ targetEntry(m-1) = min(targetEntry(m-1:m));
+ targetEntry(m)=[];
+ m = numel(targetEntry)+1;
+ end
+ m=m-1;
+ end
+ end
+
+ if numel(targetEntry) ~= numel(targetExit)
+ matRad_cfg.dispError('Inconsistency during ray tracing. Please check correct assignment and overlap priorities of structure types OAR & TARGET.');
+ end
+
+ beam.ray(j).energy = [];
+ beam.ray(j).rangeShifter = [];
+
+ % Save energies in stf struct
+ for k = 1:numel(targetEntry)
+
+ %If we need lower energies than available, consider
+ %range shifter (if asked for)
+ if any(targetEntry < min(this.availablePeakPos)) && this.useRangeShifter
+ %Get Energies to use with range shifter to fill up
+ %non-reachable low-range spots
+ raShiEnergies = this.availableEnergies(this.availablePeakPosRaShi >= targetEntry(k) & min(this.availablePeakPos) > this.availablePeakPosRaShi);
+
+ raShi.ID = 1;
+ raShi.eqThickness = rangeShifterEqD;
+ raShi.sourceRashiDistance = round(ctEntryPoint - 2*rangeShifterEqD,-1); %place a little away from entry, round to cms to reduce number of unique settings
+
+ beam.ray(j).energy = [beam.ray(j).energy raShiEnergies];
+ beam.ray(j).rangeShifter = [beam.ray(j).rangeShifter repmat(raShi,1,length(raShiEnergies))];
+ end
+
+ %Normal placement without rangeshifter
+ newEnergies = this.availableEnergies(this.availablePeakPos>=targetEntry(k)&this.availablePeakPos<=targetExit(k));
+
+
+ beam.ray(j).energy = [beam.ray(j).energy newEnergies];
+
+
+ raShi.ID = 0;
+ raShi.eqThickness = 0;
+ raShi.sourceRashiDistance = 0;
+ beam.ray(j).rangeShifter = [beam.ray(j).rangeShifter repmat(raShi,1,length(newEnergies))];
+ end
+
+
+ targetEntry = [];
+ targetExit = [];
+
+
+ % book keeping & calculate focus index
+ beam.numOfBixelsPerRay(j) = numel([beam.ray(j).energy]);
+ currentMinimumFWHM = matRad_interp1(this.machine.meta.LUT_bxWidthminFWHM(1,:)',...
+ this.machine.meta.LUT_bxWidthminFWHM(2,:)',...
+ this.bixelWidth, ...
+ this.machine.meta.LUT_bxWidthminFWHM(2,end));
+ focusIx = ones(beam.numOfBixelsPerRay(j),1);
+ [~, vEnergyIx] = min(abs(bsxfun(@minus,[this.machine.data.energy]',...
+ repmat(beam.ray(j).energy,length([this.machine.data]),1))));
+
+ % get for each spot the focus index
+ for k = 1:beam.numOfBixelsPerRay(j)
+ focusIx(k) = find(this.machine.data(vEnergyIx(k)).initFocus.SisFWHMAtIso > currentMinimumFWHM,1,'first');
+ end
+
+ beam.ray(j).focusIx = focusIx';
+
+ %Get machine bounds
+ numParticlesPerMU = 1e6*ones(1,beam.numOfBixelsPerRay(j));
+ minMU = zeros(1,beam.numOfBixelsPerRay(j));
+ maxMU = Inf(1,beam.numOfBixelsPerRay(j));
+ for k = 1:beam.numOfBixelsPerRay(j)
+ if isfield(this.machine.data(vEnergyIx(k)),'MUdata')
+ MUdata = this.machine.data(vEnergyIx(k)).MUdata;
+ if isfield(MUdata,'numParticlesPerMU')
+ numParticlesPerMU(k) = MUdata.numParticlesPerMU;
+ end
+
+ if isfield(MUdata,'minMU')
+ minMU(k) = MUdata.minMU;
+ end
+
+ if isfield(MUdata,'maxMU')
+ maxMU(k) = MUdata.maxMU;
+ end
+ end
+ end
+
+ beam.ray(j).numParticlesPerMU = numParticlesPerMU;
+ beam.ray(j).minMU = minMU;
+ beam.ray(j).maxMU = maxMU;
+
+ else % target not hit
+ beam.ray(j) = [];
+ beam.numOfBixelsPerRay(j) = [];
+ end
+ end
+ beam.numOfRays = numel(beam.ray);
+ end
+
+ function beam = finalizeBeam(this,beam)
+
+ % get minimum energy per field
+ minEnergy = min([beam.ray.energy]);
+ maxEnergy = max([beam.ray.energy]);
+
+ % get corresponding peak position
+ minPeakPos = this.machine.data(minEnergy == this.availableEnergies).peakPos;
+ maxPeakPos = this.machine.data(maxEnergy == this.availableEnergies).peakPos;
+
+ % find set of energyies with adequate spacing
+ tolerance = beam.longitudinalSpotSpacing/10;
+
+ useEnergyBool = this.availablePeakPos >= minPeakPos & this.availablePeakPos <= maxPeakPos;
+
+ ixCurr = find(useEnergyBool,1,'first');
+ ixRun = ixCurr + 1;
+ ixEnd = find(useEnergyBool,1,'last');
+
+ while ixRun <= ixEnd
+ if abs(this.availablePeakPos(ixRun)-this.availablePeakPos(ixCurr)) < ...
+ this.longitudinalSpotSpacing - tolerance
+ useEnergyBool(ixRun) = 0;
+ else
+ ixCurr = ixRun;
+ end
+ ixRun = ixRun + 1;
+ end
+
+ for j = beam.numOfRays:-1:1
+ for k = beam.numOfBixelsPerRay(j):-1:1
+ maskEnergy = beam.ray(j).energy(k) == this.availableEnergies;
+ if ~useEnergyBool(maskEnergy)
+ beam.ray(j).energy(k) = [];
+ beam.ray(j).focusIx(k) = [];
+ beam.ray(j).rangeShifter(k) = [];
+ beam.numOfBixelsPerRay(j) = beam.numOfBixelsPerRay(j) - 1;
+ end
+ end
+ if isempty(beam.ray(j).energy)
+ beam.ray(j) = [];
+ beam.numOfBixelsPerRay(j) = [];
+ beam.numOfRays = beam.numOfRays - 1;
+ end
+ end
+
+ beam = this.finalizeBeam@matRad_StfGeneratorParticleRayBixelAbstract(beam);
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ % Check superclass availability
+ [available,msg] = matRad_StfGeneratorParticleRayBixelAbstract.isAvailable(pln,machine);
+
+ if ~available
+ return;
+ else
+ available = false;
+ msg = [];
+ end
+
+ %checkBasic
+ try
+ %check modality
+ checkModality = any(strcmp(matRad_StfGeneratorParticleIMPT.possibleRadiationModes, machine.meta.radiationMode)) && any(strcmp(matRad_StfGeneratorParticleIMPT.possibleRadiationModes, pln.radiationMode));
+
+ %Sanity check compatibility
+ if checkModality
+ checkModality = strcmp(machine.meta.radiationMode,pln.radiationMode);
+ end
+
+ preCheck = checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ available = preCheck;
+ end
+ end
+end
diff --git a/matRad/steering/matRad_StfGeneratorParticleRayBixelAbstract.m b/matRad/steering/matRad_StfGeneratorParticleRayBixelAbstract.m
new file mode 100644
index 000000000..1118d4fe0
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorParticleRayBixelAbstract.m
@@ -0,0 +1,108 @@
+classdef (Abstract) matRad_StfGeneratorParticleRayBixelAbstract < matRad_StfGeneratorExternalRayBixelAbstract
+% matRad_StfGeneratorParticleRayBixelAbstract: Abstract Superclass for
+% Particle Stf Generators using the ray-bixel mechanism
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+ properties
+ useRangeShifter = false;
+ end
+
+ properties (Access = protected)
+ availableEnergies
+ availablePeakPos
+ availablePeakPosRaShi
+ maxPBwidth
+ pbMargin
+ end
+
+ methods
+ function this = matRad_StfGeneratorParticleRayBixelAbstract(pln)
+ % Constructs ExternalStfGenerator with or without pln
+ if nargin < 1
+ pln = [];
+ end
+ this@matRad_StfGeneratorExternalRayBixelAbstract(pln);
+
+ if isempty(this.radiationMode)
+ this.radiationMode = 'protons';
+ end
+ end
+
+ function setDefaults(this)
+ % Set default values for ExternalStfGenerator
+ this.setDefaults@matRad_StfGeneratorExternalRayBixelAbstract();
+ end
+ end
+
+ methods (Access = protected)
+ function initialize(this)
+ this.initialize@matRad_StfGeneratorExternalRayBixelAbstract();
+
+ %Initialize Metadata needed for stf generators
+ this.availableEnergies = [this.machine.data.energy];
+ this.availablePeakPos = [this.machine.data.peakPos] + [this.machine.data.offset];
+ availableWidths = [this.machine.data.initFocus];
+ availableWidths = [availableWidths.SisFWHMAtIso];
+ this.maxPBwidth = max(availableWidths) / 2.355;
+
+ matRad_cfg = MatRad_Config.instance();
+ if this.useRangeShifter
+ %For now only a generic range shifter is used whose thickness is
+ %determined by the minimum peak width to play with
+ rangeShifterEqD = round(min(this.availablePeakPos)* 1.25);
+ this.availablePeakPosRaShi = this.availablePeakPos - rangeShifterEqD;
+
+ matRad_cfg.dispWarning('Use of range shifter enabled. matRad will generate a generic range shifter with WEPL %f to enable ranges below the shortest base data entry.',rangeShifterEqD);
+ end
+
+ if sum(this.availablePeakPos<0)>0
+ matRad_cfg.dispError('at least one available peak position is negative - inconsistent machine file')
+ end
+
+ %Create Water equivalent cube in ct
+ this.ct = matRad_calcWaterEqD(this.ct,this.radiationMode);
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ % Check superclass availability
+ [available,msg] = matRad_StfGeneratorExternalRayBixelAbstract.isAvailable(pln,machine);
+
+ if ~available
+ return;
+ end
+
+ available = available && isstruct(machine.data);
+
+ available = available && all(isfield(machine.data,{'energy','peakPos','initFocus','offset'}));
+
+
+ if ~available
+ msg = 'Your machine file is invalid and does not contain the basic fields required for photon machines!';
+ else
+ msg = [];
+ end
+ end
+ end
+end
+
diff --git a/matRad/steering/matRad_StfGeneratorParticleSingleBeamlet.m b/matRad/steering/matRad_StfGeneratorParticleSingleBeamlet.m
new file mode 100644
index 000000000..50151b6e6
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorParticleSingleBeamlet.m
@@ -0,0 +1,245 @@
+classdef matRad_StfGeneratorParticleSingleBeamlet < matRad_StfGeneratorParticleRayBixelAbstract
+% matRad_StfGeneratorPhotonSingleBeamlet:
+% Creates a single beamlet for particles
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties
+ energy;
+ raShiThickness = 50; %Range shifter to be used if useRangeShifter = true;
+ end
+
+ properties (Constant)
+ name = 'Particle Single Spot';
+ shortName = 'ParticleSingleSpot';
+ possibleRadiationModes = {'protons','helium','carbon'};
+ end
+
+ methods
+ function this = matRad_StfGeneratorParticleSingleBeamlet(pln)
+ if nargin < 1
+ pln = [];
+ end
+ this@matRad_StfGeneratorParticleRayBixelAbstract(pln);
+ end
+ end
+
+ methods (Access = protected)
+ function pbMargin = getPbMargin(this)
+ pbMargin = 0;
+ end
+
+ function rayPos = getRayPositionMatrix(this,beam)
+ % see superclass for information
+ rayPos = [0 0 0];
+ end
+
+ function createPatientGeometry(this)
+ % Simple patient geometry with isocenter target
+ matRad_cfg = MatRad_Config.instance();
+
+ if isempty(this.isoCenter)
+ this.isoCenter = matRad_getIsoCenter(this.cst,this.ct,visBool);
+ end
+
+ if ~isequal(size(this.isoCenter),[this.numOfBeams,3]) && ~size(this.isoCenter,1) ~= 1
+ matRad_cfg.dispWarning('IsoCenter invalid, creating new one automatically!');
+ this.isoCenter = matRad_getIsoCenter(this.cst,this.ct,visBool);
+ end
+
+ if size(this.isoCenter,1) == 1
+ this.isoCenter = repmat(this.isoCenter,this.numOfBeams,1);
+ end
+
+ %Voxel index of Isocenter
+ isoIx = matRad_world2cubeIndex(this.isoCenter,this.ct,true);
+ isoIx(isoIx < 1) = 1;
+ for i = 1:size(isoIx,1)
+ isoIx(i,isoIx(i,:) > this.ct.cubeDim) = this.ct.cubeDim(isoIx(i,:) > this.ct.cubeDim);
+ end
+
+ % generate voi cube for targets
+ this.voiTarget = zeros(this.ct.cubeDim);
+ for i = 1:size(isoIx,1)
+ this.voiTarget(isoIx(i,1),isoIx(i,2),isoIx(i,3)) = 1;
+ end
+
+ % Margin info
+ if this.addMargin
+ adds = unique(perms([1 0 0]),'rows');
+ adds = [adds; -adds];
+ for i = 1:size(isoIx,1)
+ for p = 1:size(adds,1)
+ padIx = isoIx(i,:) + adds(p,:);
+ if any(padIx < 1) || any(padIx > this.ct.cubeDim)
+ continue;
+ end
+ this.voiTarget(padIx(1),padIx(2),padIx(3)) = 1;
+ end
+ end
+ end
+
+ V = find(this.voiTarget > 0);
+
+ % throw error message if no target is found
+ if isempty(V)
+ matRad_cfg.dispError('Could not identify find isocenter target.');
+ end
+
+ % Convert linear indices to 3D voxel coordinates
+ this.voxTargetWorldCoords = matRad_cubeIndex2worldCoords(V, this.ct);
+
+ % take only voxels inside patient
+ V = [this.cst{:,4}];
+ V = unique(vertcat(V{:}));
+
+ % ignore densities outside of contours
+ eraseCtDensMask = ones(prod(this.ct.cubeDim), 1);
+ eraseCtDensMask(V) = 0;
+ for i = 1:this.ct.numOfCtScen
+ this.ct.cube{i}(eraseCtDensMask == 1) = 0;
+ end
+ end
+
+ function beam = setBeamletEnergies(this,beam)
+ isoCenterCubeSystem = matRad_world2cubeCoords(beam.isoCenter,this.ct);
+
+ % ray tracing necessary to determine depth of the target
+ [alphas,l,rho,d12,~] = matRad_siddonRayTracer(isoCenterCubeSystem, ...
+ this.ct.resolution, ...
+ beam.sourcePoint, ...
+ beam.ray.targetPoint, ...
+ [{this.ct.cube{1}} {this.voiTarget}]);
+
+ if isempty(alphas)
+ matRad_cfg.dispError('Beam seems to not hit the CT! Check Isocenter placement!');
+ end
+
+ ctEntryPoint = alphas(1) * d12;
+
+ if sum(rho{2}) <= 0 && isempty(this.energy)
+ availSorted = sort(this.availableEnergies);
+ useEnergy = ceil(availSorted(numel(availSorted)/2));
+ matRad_cfg.dispWarning('Could not obtain suitable energy based on isoCenter. Taking median energy of %g MeV',useEnergy);
+ elseif ~isempty(this.energy)
+ [~,ix] = min(abs(this.energy-this.availableEnergies));
+ useEnergy = this.availableEnergies(ix);
+ else
+ % compute radiological depths
+ % http://www.ncbi.nlm.nih.gov/pubmed/4000088, eq 14
+ radDepths = cumsum(l .* rho{1});
+
+ % find target entry & exit
+ diff_voi = diff([rho{2}]);
+ if rho{2}(1) > 0
+ targetEntry = 1;
+ else
+ targetEntry = radDepths(diff_voi == 1);
+ end
+
+ targetExit = radDepths(diff_voi == -1);
+
+ if numel(targetEntry) ~= numel(targetExit)
+ matRad_cfg.dispError('Inconsistency during ray tracing. Please check correct assignment and overlap priorities of structure types OAR & TARGET.');
+ end
+
+ % Save energies in stf struct
+ bestPeakPos = mean([targetExit,targetEntry]);
+ if this.useRangeShifter
+ bestPeakPos = bestPeakPos + this.raShiThickness;
+ end
+
+ [~,closest] = min(abs(this.availablePeakPos - bestPeakPos));
+ useEnergy = this.availableEnergies(closest);
+ end
+
+ beam.ray.energy = useEnergy;
+ % book keeping energy index
+ [~, vEnergyIx] = min(abs(beam.ray.energy-this.availableEnergies));
+
+ % get the focus index
+ if isfield(this.machine.meta,'LUT_bxWidthminFWHM')
+ currentMinimumFWHM = matRad_interp1(this.machine.meta.LUT_bxWidthminFWHM(1,:)',...
+ this.machine.meta.LUT_bxWidthminFWHM(2,:)',...
+ beam.bixelWidth, ...
+ this.machine.meta.LUT_bxWidthminFWHM(2,end));
+
+ beam.ray.focusIx = find(this.machine.data(vEnergyIx).initFocus.SisFWHMAtIso > currentMinimumFWHM,1,'first');
+ else
+ beam.ray.focusIx = 1;
+ end
+
+ % Get Range shifter
+ if this.useRangeShifter
+
+ %Include range shifter data
+ beam.ray.rangeShifter.ID = 1;
+ beam.ray.rangeShifter.eqThickness = this.raShiThickness;
+
+ %Place range shifter 2 times the range away from isocenter, but
+ %at least 10 cm
+ sourceRaShi = round(ctEntryPoint - 2*this.raShiThickness,-1); %place a little away from entry, round to cms to reduce number of unique settings;
+ beam.ray.rangeShifter.sourceRashiDistance = sourceRaShi;
+ else
+ beam.ray.rangeShifter.ID = 0;
+ beam.ray.rangeShifter.eqThickness = 0;
+ beam.ray.rangeShifter.sourceRashiDistance = 0;
+ end
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ % Check superclass availability
+ [available,msg] = matRad_StfGeneratorParticleRayBixelAbstract.isAvailable(pln,machine);
+
+ if ~available
+ return;
+ else
+ available = false;
+ msg = [];
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(matRad_StfGeneratorParticleSingleBeamlet.possibleRadiationModes, machine.meta.radiationMode)) && any(strcmp(matRad_StfGeneratorParticleSingleBeamlet.possibleRadiationModes, pln.radiationMode));
+
+ %Sanity check compatibility
+ if checkModality
+ checkModality = strcmp(machine.meta.radiationMode,pln.radiationMode);
+ end
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ available = preCheck;
+ end
+ end
+end
diff --git a/matRad/steering/matRad_StfGeneratorPhotonIMRT.m b/matRad/steering/matRad_StfGeneratorPhotonIMRT.m
new file mode 100644
index 000000000..1462b021f
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorPhotonIMRT.m
@@ -0,0 +1,73 @@
+classdef matRad_StfGeneratorPhotonIMRT < matRad_StfGeneratorPhotonRayBixelAbstract
+
+ properties (Constant)
+ name = 'Photon IMRT stf Generator';
+ shortName = 'PhotonIMRT';
+ possibleRadiationModes = {'photons'};
+ end
+
+
+
+ methods
+ function this = matRad_StfGeneratorPhotonIMRT(pln)
+ if nargin < 1
+ pln = [];
+ end
+ this@matRad_StfGeneratorPhotonRayBixelAbstract(pln);
+
+ if isempty(this.radiationMode)
+ this.radiationMode = 'photons';
+ end
+ end
+ end
+
+ methods (Access = protected)
+ function pbMargin = getPbMargin(this)
+ pbMargin = this.bixelWidth;
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ % Check superclass availability
+ [available,msg] = matRad_StfGeneratorPhotonRayBixelAbstract.isAvailable(pln,machine);
+
+ if ~available
+ return;
+ else
+ available = false;
+ msg = [];
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(matRad_StfGeneratorPhotonIMRT.possibleRadiationModes, machine.meta.radiationMode)) && any(strcmp(matRad_StfGeneratorPhotonIMRT.possibleRadiationModes, pln.radiationMode));
+
+ %Sanity check compatibility
+ if checkModality
+ checkModality = strcmp(machine.meta.radiationMode,pln.radiationMode);
+ end
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ available = preCheck;
+ end
+ end
+end
diff --git a/matRad/steering/matRad_StfGeneratorPhotonRayBixelAbstract.m b/matRad/steering/matRad_StfGeneratorPhotonRayBixelAbstract.m
new file mode 100644
index 000000000..44a4d4b6a
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorPhotonRayBixelAbstract.m
@@ -0,0 +1,101 @@
+classdef (Abstract) matRad_StfGeneratorPhotonRayBixelAbstract < matRad_StfGeneratorExternalRayBixelAbstract
+% matRad_StfGeneratorPhotonRayBixelAbstract: Abstract Superclass for Photon
+% Stf Generators using the ray-bixel mechanism
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ methods
+ function this = matRad_StfGeneratorPhotonRayBixelAbstract(pln)
+ % Constructs ExternalStfGenerator with or without pln
+ if nargin < 1
+ pln = [];
+ end
+ this@matRad_StfGeneratorExternalRayBixelAbstract(pln);
+ end
+
+ function setDefaults(this)
+ % Set default values for ExternalStfGenerator
+ this.setDefaults@matRad_StfGeneratorExternalRayBixelAbstract();
+ end
+ end
+
+ methods (Access = protected)
+
+ function beam = initBeamData(this,beam)
+ beam = this.initBeamData@matRad_StfGeneratorExternalRayBixelAbstract(beam);
+ beam.SCD = this.machine.meta.SCD;
+ end
+
+ function beam = setBeamletEnergies(this,beam)
+ %Assigns the max photon machine energy to all rays
+ numOfRays = numel(beam.ray);
+
+ for j = numOfRays:-1:1
+ beam.ray(j).energy = this.machine.data.energy;
+ end
+ end
+
+ function beam = initRays(this,beam)
+ %Initializes the geometrical beamlet information for photon bixels (ray corners at isocenter and collimator plane)
+
+ beam = this.initRays@matRad_StfGeneratorExternalRayBixelAbstract(beam);
+
+ rotMat_vectors_T = transpose(matRad_getRotationMatrix(beam.gantryAngle,beam.couchAngle));
+
+ numOfRays = numel(beam.ray);
+
+ %photon ray-target position
+ for j = 1:numOfRays
+ beam.ray(j).beamletCornersAtIso = [beam.ray(j).rayPos_bev + [+beam.bixelWidth/2,0,+beam.bixelWidth/2];...
+ beam.ray(j).rayPos_bev + [-beam.bixelWidth/2,0,+beam.bixelWidth/2];...
+ beam.ray(j).rayPos_bev + [-beam.bixelWidth/2,0,-beam.bixelWidth/2];...
+ beam.ray(j).rayPos_bev + [+beam.bixelWidth/2,0,-beam.bixelWidth/2]]*rotMat_vectors_T;
+ beam.ray(j).rayCorners_SCD = (repmat([0, beam.SCD - beam.SAD, 0],4,1)+ (beam.SCD/beam.SAD) * ...
+ [beam.ray(j).rayPos_bev + [+beam.bixelWidth/2,0,+beam.bixelWidth/2];...
+ beam.ray(j).rayPos_bev + [-beam.bixelWidth/2,0,+beam.bixelWidth/2];...
+ beam.ray(j).rayPos_bev + [-beam.bixelWidth/2,0,-beam.bixelWidth/2];...
+ beam.ray(j).rayPos_bev + [+beam.bixelWidth/2,0,-beam.bixelWidth/2]])*rotMat_vectors_T;
+ end
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ % Check superclass availability
+ [available,msg] = matRad_StfGeneratorExternalRayBixelAbstract.isAvailable(pln,machine);
+
+ if ~available
+ return;
+ end
+
+ available = available && isfield(machine.data,'energy') && isscalar(machine.data.energy);
+
+ available = available && isfield(machine.meta,'SCD') && isscalar(machine.meta.SCD);
+
+
+ if ~available
+ msg = 'Your machine file is invalid and does not contain the basic fields required for photon machines!';
+ else
+ msg = [];
+ end
+ end
+ end
+end
+
diff --git a/matRad/steering/matRad_StfGeneratorPhotonSingleBeamlet.m b/matRad/steering/matRad_StfGeneratorPhotonSingleBeamlet.m
new file mode 100644
index 000000000..8d158ce60
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorPhotonSingleBeamlet.m
@@ -0,0 +1,91 @@
+classdef matRad_StfGeneratorPhotonSingleBeamlet < matRad_StfGeneratorPhotonRayBixelAbstract
+% matRad_StfGeneratorPhotonSingleBeamlet:
+% Creates a single beamlet for photons
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ properties (Constant)
+ name = 'Photon Single Bixel';
+ shortName = 'PhotonSingleBixel';
+ possibleRadiationModes = {'photons'};
+ end
+
+ methods
+ function this = matRad_StfGeneratorPhotonSingleBeamlet(pln)
+ if nargin < 1
+ pln = [];
+ end
+ this@matRad_StfGeneratorPhotonRayBixelAbstract(pln);
+
+ if isempty(this.radiationMode)
+ this.radiationMode = 'photons';
+ end
+ end
+ end
+
+ methods (Access = protected)
+ function pbMargin = getPbMargin(this)
+ pbMargin = 0;
+ end
+
+ function rayPos = getRayPositionMatrix(this,beam)
+ % see superclass for information
+ rayPos = [0 0 0];
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ % Check superclass availability
+ [available,msg] = matRad_StfGeneratorPhotonRayBixelAbstract.isAvailable(pln,machine);
+
+ if ~available
+ return;
+ else
+ available = false;
+ msg = [];
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(matRad_StfGeneratorPhotonSingleBeamlet.possibleRadiationModes, machine.meta.radiationMode)) && any(strcmp(matRad_StfGeneratorPhotonSingleBeamlet.possibleRadiationModes, pln.radiationMode));
+
+ %Sanity check compatibility
+ if checkModality
+ checkModality = strcmp(machine.meta.radiationMode,pln.radiationMode);
+ end
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ available = preCheck;
+ end
+ end
+end
diff --git a/matRad/util/MatRad_spotRemovalDij.m b/matRad/util/MatRad_spotRemovalDij.m
new file mode 100644
index 000000000..6bca68c67
--- /dev/null
+++ b/matRad/util/MatRad_spotRemovalDij.m
@@ -0,0 +1,209 @@
+classdef MatRad_spotRemovalDij < handle
+ % MatRad_spotRemovalDij class definition
+ %
+ %
+ % References
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % Copyright 2019 the matRad development team.
+ %
+ % This file is part of the matRad project. It is subject to the license
+ % terms in the LICENSE file found in the top-level directory of this
+ % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+ % of the matRad project, including this file, may be copied, modified,
+ % propagated, or distributed except according to the terms contained in the
+ % LICENSE file.
+ %
+ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ properties
+
+ matRad_cfg = MatRad_Config.instance();
+
+ removalMode = 'relative'; % 'relative' is the only mode for now
+ propSpotRemoval;
+
+ dij;
+ cst;
+ pln;
+ stf;
+
+ weights;
+ newSpots;
+ newWeights;
+
+ numOfRemovedSpots;
+
+ end
+
+ methods
+ function obj = MatRad_spotRemovalDij(dij,w)
+
+ obj.reset();
+
+ % I don't know if this is good practice to copy a very large struct
+ obj.dij = dij;
+ obj.weights = w;
+
+ end
+
+
+ function reset(obj)
+
+ %Set all default properties for spot removal
+ obj.setDefaultProperties();
+
+ end
+
+
+ function obj = setDefaultProperties(obj)
+
+ obj.propSpotRemoval.relativeThreshold = 0.03;
+ obj.propSpotRemoval.absoluteThreshold = 2;
+
+ end
+
+
+ function resultGUI = reoptimize(obj,cst,pln)
+
+ if isempty(obj.cst) || exist('cst','var')
+ obj.cst = cst;
+ end
+ if isempty(obj.pln) || exist('pln','var')
+ obj.pln = pln;
+ end
+
+ if isempty(obj.newWeights)
+ obj.calcNewSpots();
+ end
+
+ resultGUI = matRad_fluenceOptimization(obj.getDij,obj.cst,obj.pln,obj.newWeights);
+
+ end
+
+
+ function obj = calcNewSpots(obj)
+
+ switch obj.removalMode
+ case 'relative'
+ obj.newSpots = obj.weights > obj.propSpotRemoval.relativeThreshold * mean(obj.weights);
+ obj.matRad_cfg.dispInfo([num2str(sum(~obj.newSpots)),'/',num2str(numel(obj.newSpots)) ,' spots have been removed below ',num2str(100*obj.propSpotRemoval.relativeThreshold),'% of the mean weight.\n'])
+ case 'absolute'
+ obj.newSpots = obj.weights > obj.propSpotRemoval.absoluteThreshold;
+ obj.matRad_cfg.dispInfo([num2str(sum(~obj.newSpots)),'/',num2str(numel(obj.newSpots)) ,' spots have been removed below thres=',num2str(obj.propSpotRemoval.absoluteThreshold),'.\n'])
+ otherwise
+ obj.matRad_cfg.dispWarning(['Removal mode ' obj.removalMode ' not implemented, no spots have been removed.']);
+ end
+
+ obj.numOfRemovedSpots = sum(~obj.newSpots);
+ obj.newWeights = obj.weights(obj.newSpots);
+
+ end
+
+
+ function stf = getStf(obj,stf)
+
+ if isempty(obj.stf) || exist('stf','var')
+ obj.stf = stf;
+ end
+
+ if ~isempty(obj.newWeights) && ~isempty(obj.stf)
+ stf = obj.stf;
+ [~,beamNumIdx] = unique(obj.dij.beamNum);
+ beamNumIdx = [0;beamNumIdx(2:end)-1;obj.dij.totalNumOfBixels];
+
+ for b = 1:obj.dij.numOfBeams
+ currRaysInBeam = obj.dij.rayNum(beamNumIdx(b)+1:beamNumIdx(b+1));
+ currBixelsInRay = obj.dij.bixelNum(beamNumIdx(b)+1:beamNumIdx(b+1));
+ [rayCnt,rayIdx] = unique(currRaysInBeam);
+
+ numOfBixelsPerRay = groupcounts(currRaysInBeam);
+ cutRays = ismember([1:obj.dij.numOfRaysPerBeam(b)]',rayCnt);
+ if any(~cutRays)
+ stf(b).ray = stf(b).ray(cutRays);
+ stf(b).numOfRays = sum(cutRays);
+ end
+ bixelCurrRay = cell(1,stf(b).numOfRays);
+ for i = 1:stf(b).numOfRays
+ bixelCurrRay{i} = currBixelsInRay(rayIdx(i):rayIdx(i)+numOfBixelsPerRay(i)-1);
+ end
+ for f = 1:stf(b).numOfRays
+ stf(b).ray(f).energy = stf(b).ray(f).energy(bixelCurrRay{f});
+ stf(b).ray(f).focusIx = stf(b).ray(f).focusIx(bixelCurrRay{f});
+ stf(b).ray(f).rangeShifter = stf(b).ray(f).rangeShifter(bixelCurrRay{f});
+ end
+ stf(b).numOfBixelsPerRay = numOfBixelsPerRay';
+ stf(b).totalNumOfBixels = sum(stf(b).numOfBixelsPerRay);
+ end
+ end
+
+ end
+
+
+ function dij = getDij(obj)
+
+ if isempty(obj.newWeights)
+ obj.calcNewSpots();
+ end
+
+ dij = obj.dij;
+ if ~isempty(obj.newWeights)
+ dij.cutWeights = obj.newWeights;
+
+ dij.bixelNum = dij.bixelNum(obj.newSpots);
+ dij.rayNum = dij.rayNum(obj.newSpots);
+ dij.beamNum = dij.beamNum(obj.newSpots);
+ dij.totalNumOfBixels = sum(obj.newSpots);
+
+ dij.physicalDose{1} = dij.physicalDose{1}(:,obj.newSpots);
+ if isfield(dij,'mAlphaDose')
+ dij.mAlphaDose{1} = dij.mAlphaDose{1}(:,obj.newSpots);
+ dij.mSqrtBetaDose{1} = dij.mSqrtBetaDose{1}(:,obj.newSpots);
+ end
+ if isfield(dij,'mLETDose')
+ dij.mLETDose{1} = dij.mLETDose{1}(:,obj.newSpots);
+ end
+ [~,beamNumIdx] = unique(dij.beamNum);
+ beamNumIdx = [0;beamNumIdx(2:end)-1;dij.totalNumOfBixels];
+
+ for b = 1:dij.numOfBeams
+ currRaysInBeam = dij.rayNum(beamNumIdx(b)+1:beamNumIdx(b+1));
+ [rayCnt,~] = unique(currRaysInBeam);
+
+ dij.numOfRaysPerBeam(b) = numel(rayCnt);
+ end
+
+ dij.totalNumOfRays = sum(dij.numOfRaysPerBeam);
+ dij.numOfRemovedSpots = sum(~obj.newSpots);
+ end
+
+ end
+
+
+ function weights = getWeights(obj)
+
+ if isempty(obj.newWeights)
+ obj.calcNewSpots();
+ end
+ if ~isempty(obj.newWeights)
+ weights = obj.newWeights;
+ end
+
+ end
+
+
+ function weightsLogic = getLogical(obj)
+
+ if isempty(obj.newWeights)
+ obj.calcNewSpots();
+ end
+ if ~isempty(obj.newWeights)
+ weightsLogic = obj.newSpots;
+ end
+
+ end
+
+ end
+end
+
+
diff --git a/matRad/util/matRad_addMUdataFromMachine.m b/matRad/util/matRad_addMUdataFromMachine.m
new file mode 100644
index 000000000..0e6851bf5
--- /dev/null
+++ b/matRad/util/matRad_addMUdataFromMachine.m
@@ -0,0 +1,83 @@
+function [stf, dij] = matRad_addMUdataFromMachine(machine, stf, dij)
+% helper function to add MU data included in machine file to dij and stf
+% computed with earlier versions of matRad
+%
+% call
+% [stf, dij] = matRad_addMUdataFromMachine(machine, stf, dij)
+% stf = matRad_addMUdataFromMachine(machine, stf)
+%
+% input
+% machine: machine struct as stored in basedata file
+% stf: matRad steering information struct
+% dij: matRad dose influence matrix struct
+%
+% output
+% stf: matRad steering information struct with MUdata
+% dij: matRad dose influence matrix struct with MUdata
+%
+% References
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2012 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%Each base data entry should have a field called MUdata which contains
+%minMU, maxMU and numParticlesPerMU
+MUdata = [machine.data.MUdata];
+
+bdMinMU = [MUdata.minMU];
+bdMaxMU = [MUdata.maxMU];
+bdNumParticlesPerMU = [MUdata.numParticlesPerMU];
+bdEnergies = [machine.data.energy];
+
+%Adding info to stf file
+for iBeam = 1:numel(stf)
+ for iRay = 1:stf(iBeam).numOfRays
+ energy = stf(iBeam).ray(iRay).energy;
+ minMUSpot = spline(bdEnergies,bdMinMU,energy); %Will change this
+ maxMUSpot = spline(bdEnergies,bdMaxMU,energy);
+ NumParticlesPerMUspot = spline(bdEnergies,bdNumParticlesPerMU,energy);
+
+ [stf.ray(iRay).minMU] = minMUSpot;
+ [stf.ray(iRay).maxMU] = maxMUSpot;
+ [stf.ray(iRay).numParticlesPerMU] = NumParticlesPerMUspot;
+
+ end
+end
+
+if nargin > 2 && nargout == 2
+ %Adding info to dij
+ dij.minMU = zeros(dij.totalNumOfBixels,1);
+ dij.maxMU = Inf(dij.totalNumOfBixels,1);
+ dij.numParticlesPerMU = 1e6*ones(dij.totalNumOfBixels,1);
+ for iBixel = 1:dij.totalNumOfBixels
+
+ beamNum = dij.beamNum(iBixel);
+ rayNum = dij.rayNum(iBixel);
+ spotNum = dij.bixelNum(iBixel);
+
+ minMUSpot = stf(beamNum).ray(rayNum).minMU(spotNum);
+ maxMUSpot = stf(beamNum).ray(rayNum).maxMU(spotNum);
+ numParticlesPerMUspot = stf(beamNum).ray(rayNum).numParticlesPerMU(spotNum);
+
+ dij.minMU(iBixel,1) = minMUSpot;
+ dij.mxaMU(iBixel,1) = maxMUSpot;
+ dij.numParticlesPerMU(iBixel) = numParticlesPerMUspot;
+ end
+end
+
+
+
+
+
+
+
diff --git a/matRad/util/matRad_appendResultGUI.m b/matRad/util/matRad_appendResultGUI.m
new file mode 100644
index 000000000..fb042c692
--- /dev/null
+++ b/matRad/util/matRad_appendResultGUI.m
@@ -0,0 +1,60 @@
+function resultGUI = matRad_appendResultGUI(resultGUI,resultGUItoAppend,boolOverwrite,Identifier)
+% function to merge two seperate resultGUI structs into one for
+% visualisation
+%
+% call
+% resultGUI = matRad_mergeResultGUIs(resultGUI,resultGUIrob)
+%
+% input
+% resultGUI: matRads resultGUI struct
+% resultGUItoAppend: resultGUI struct which will be appendet
+% boolOverwrite: if true existing fields be overwritten in case
+% they already exist
+%
+% output
+% resultGUI: matRads resultGUI struct
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if ~exist('Identifier','var')
+ Identifier = '';
+else
+ Identifier = ['_' Identifier];
+end
+
+if ~exist('boolOverwrite','var')
+ boolOverwrite = false;
+end
+
+matRad_cfg = MatRad_Config.instance();
+
+caFieldnames = fieldnames(resultGUItoAppend);
+
+for i = 1:numel(caFieldnames)
+
+ currFieldName = caFieldnames{i,1};
+ fullFieldName = [caFieldnames{i,1} Identifier];
+
+ if boolOverwrite || ~isfield(resultGUI,fullFieldName)
+ resultGUI.(fullFieldName) = resultGUItoAppend.(currFieldName);
+ else
+ matRad_cfg.dispWarning('Field ''%s'' exists and overwriting was disabled. Results will not be appended to resultGUI!',fullFieldName);
+ end
+end
+
+% group similar fields together
+resultGUI = orderfields(resultGUI);
+
+end
+
diff --git a/matRad/util/matRad_calcCubes.m b/matRad/util/matRad_calcCubes.m
new file mode 100644
index 000000000..de9a9bc02
--- /dev/null
+++ b/matRad/util/matRad_calcCubes.m
@@ -0,0 +1,236 @@
+function resultGUI = matRad_calcCubes(w,dij,scenNum)
+% matRad computation of all cubes for the resultGUI struct
+% which is used as result container and for visualization in matRad's GUI
+%
+% call
+% resultGUI = matRad_calcCubes(w,dij)
+% resultGUI = matRad_calcCubes(w,dij,scenNum)
+%
+% input
+% w: bixel weight vector
+% dij: dose influence matrix
+% scenNum: optional: number of scenario to calculated (default 1)
+%
+% output
+% resultGUI: matRad result struct
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+if nargin < 3
+ scenNum = 1;
+end
+
+resultGUI.w = w;
+
+if isfield(dij,'numParticlesPerMU')
+ resultGUI.MU = (w.*1e6) ./ dij.numParticlesPerMU;
+end
+
+% get bixel - beam correspondence
+for i = 1:dij.numOfBeams
+ beamInfo(i).suffix = ['_beam', num2str(i)];
+ beamInfo(i).logIx = (dij.beamNum == i);
+end
+beamInfo(dij.numOfBeams+1).suffix = '';
+beamInfo(dij.numOfBeams+1).logIx = true(size(resultGUI.w,1),1);
+
+[ctScen,~] = ind2sub(size(dij.physicalDose),scenNum);
+
+
+%% Physical Dose
+doseFields = {'physicalDose','doseToWater'};
+doseQuantities = {'','_std','_batchStd'};
+% compute physical dose for all beams individually and together
+for j = 1:length(doseFields)
+ for k = 1:length(doseQuantities)
+ % Check if combination is a field in dij, otherwise skip
+ if isfield(dij,[doseFields{j} doseQuantities{k}])
+ % Handle standard deviation fields and add quadratically
+ if ~isempty(strfind(lower(doseQuantities{1}),'std'))
+ for i = 1:length(beamInfo)
+ resultGUI.([doseFields{j}, doseQuantities{k}, beamInfo(i).suffix]) = sqrt(reshape(full(dij.([doseFields{j} doseQuantities{k}]){scenNum}.^2 * (resultGUI.w .* beamInfo(i).logIx)),dij.doseGrid.dimensions));
+ resultGUI.([doseFields{j}, doseQuantities{k}, beamInfo(i).suffix])(isnan(resultGUI.([doseFields{j}, doseQuantities{k}, beamInfo(i).suffix]))) = 0;
+ end
+ % Handle normal fields as usual
+ else
+ for i = 1:length(beamInfo)
+ resultGUI.([doseFields{j}, doseQuantities{k}, beamInfo(i).suffix]) = reshape(full(dij.([doseFields{j} doseQuantities{k}]){scenNum} * (resultGUI.w .* beamInfo(i).logIx)),dij.doseGrid.dimensions);
+ end
+ end
+ end
+ end
+end
+
+if ~isfield(dij,'doseWeightingThreshold')
+ dij.doseWeightingThreshold = 0.01;
+end
+absoluteDoseWeightingThreshold = dij.doseWeightingThreshold*max(resultGUI.physicalDose(:));
+
+
+
+%% LET
+% consider LET
+if isfield(dij,'mLETDose')
+ for i = 1:length(beamInfo)
+ LETDoseCube = reshape(full(dij.mLETDose{scenNum} * (resultGUI.w .* beamInfo(i).logIx)),dij.doseGrid.dimensions);
+ resultGUI.(['LET', beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
+ ix = resultGUI.(['physicalDose', beamInfo(i).suffix]) > absoluteDoseWeightingThreshold;
+ resultGUI.(['LET', beamInfo(i).suffix])(ix) = LETDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix);
+ end
+end
+
+
+%% RBE weighted dose
+% consider RBE for protons and skip varRBE calculation
+if isfield(dij,'RBE') && isscalar(dij.RBE)
+ for i = 1:length(beamInfo)
+ resultGUI.(['RBExD', beamInfo(i).suffix]) = resultGUI.(['physicalDose', beamInfo(i).suffix]) * dij.RBE;
+ end
+elseif any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'alpha')), fieldnames(dij)))
+ % Load RBE models if MonteCarlo was calculated for multiple models
+ if isfield(dij,'RBE_models')
+ RBE_model = cell(1,length(dij.RBE_models));
+ for i = 1:length(dij.RBE_models)
+ RBE_model{i} = ['_' dij.RBE_models{i}];
+ end
+ else
+ RBE_model = {''};
+ end
+
+ % Loop through RBE models
+ for j = 1:length(RBE_model)
+ % Check if combination is a field in dij, otherwise skip
+ if isfield(dij,['mAlphaDose' RBE_model{j}])
+ for i = 1:length(beamInfo)
+ % Get weights of current beam
+ wBeam = (resultGUI.w .* beamInfo(i).logIx);
+
+ % consider biological optimization
+ ix = dij.bx{ctScen} ~= 0 & resultGUI.(['physicalDose', beamInfo(i).suffix])(:) > 0;
+ ixWeighted = dij.bx{ctScen} ~= 0 & resultGUI.(['physicalDose', beamInfo(i).suffix])(:) > absoluteDoseWeightingThreshold;
+
+ % Calculate effect from alpha- and sqrtBetaDose
+ resultGUI.(['effect', RBE_model{j}, beamInfo(i).suffix]) = full(dij.(['mAlphaDose' RBE_model{j}]){scenNum} * wBeam + (dij.(['mSqrtBetaDose' RBE_model{j}]){scenNum} * wBeam).^2);
+ resultGUI.(['effect', RBE_model{j}, beamInfo(i).suffix]) = reshape(resultGUI.(['effect', RBE_model{j}, beamInfo(i).suffix]),dij.doseGrid.dimensions);
+
+ % Calculate RBExD from the effect
+ resultGUI.(['RBExD', RBE_model{j}, beamInfo(i).suffix]) = zeros(size(resultGUI.(['effect', RBE_model{j}, beamInfo(i).suffix])));
+ resultGUI.(['RBExD', RBE_model{j}, beamInfo(i).suffix])(ix) = (sqrt(dij.ax{ctScen}(ix).^2 + 4 .* dij.bx{ctScen}(ix) .* resultGUI.(['effect', RBE_model{j}, beamInfo(i).suffix])(ix)) - dij.ax{ctScen}(ix))./(2.*dij.bx{ctScen}(ix));
+
+ % Divide RBExD with the physicalDose to get the plain RBE cube
+ resultGUI.(['RBE', RBE_model{j}, beamInfo(i).suffix]) = zeros(size(resultGUI.(['effect', RBE_model{j}, beamInfo(i).suffix])));
+ resultGUI.(['RBE', RBE_model{j}, beamInfo(i).suffix])(ixWeighted) = resultGUI.(['RBExD', RBE_model{j}, beamInfo(i).suffix])(ixWeighted)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ixWeighted);
+
+ % Initialize alpha/beta cubes
+ resultGUI.(['alpha', RBE_model{j}, beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
+ resultGUI.(['beta', RBE_model{j}, beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
+ resultGUI.(['alphaDoseCube', RBE_model{j}, beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
+ resultGUI.(['SqrtBetaDoseCube', RBE_model{j}, beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
+
+ % Calculate alpha and weighted alphaDose
+ AlphaDoseCube = full(dij.(['mAlphaDose' RBE_model{j}]){scenNum} * wBeam);
+ resultGUI.(['alpha', RBE_model{j}, beamInfo(i).suffix])(ix) = AlphaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix);
+ resultGUI.(['alphaDoseCube', RBE_model{j}, beamInfo(i).suffix])(ix) = AlphaDoseCube(ix);
+
+ % Calculate beta and weighted sqrtBetaDose
+ SqrtBetaDoseCube = full(dij.(['mSqrtBetaDose' RBE_model{j}]){scenNum} * wBeam);
+ resultGUI.(['beta', RBE_model{j}, beamInfo(i).suffix])(ix) = (SqrtBetaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix)).^2;
+ resultGUI.(['SqrtBetaDoseCube', RBE_model{j}, beamInfo(i).suffix])(ix) = SqrtBetaDoseCube(ix);
+ end
+ end
+ end
+end
+
+%% Calculate Biological Effective Dose (BED)
+
+% When depth Dependent alpha beta values are calculated in dij calculation
+if isfield(dij,'ax') && isfield(dij,'bx')
+ ixWeighted = dij.ax{ctScen} > 0 & dij.bx{ctScen} > 0 & resultGUI.(['physicalDose', beamInfo(i).suffix])(:) > absoluteDoseWeightingThreshold;
+
+ if isfield(dij,'mAlphaDose') && isfield(dij,'mSqrtBetaDose')
+ for i = 1:length(beamInfo)
+ % photon equivaluent BED = n * effect / alphax
+ resultGUI.(['BED', beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
+ resultGUI.(['BED', beamInfo(i).suffix])(ixWeighted) = full(resultGUI.(['effect', beamInfo(i).suffix])(ixWeighted) ./dij.ax{ctScen}(ixWeighted));
+ resultGUI.(['BED', beamInfo(i).suffix]) = reshape(resultGUI.(['BED', beamInfo(i).suffix]), dij.doseGrid.dimensions);
+ end
+ matRad_cfg.dispWarning('Photon Equiavlent BED calculated');
+ else
+ % Get Alpha and Beta Values form dij.ax and dij.bx
+ for i = 1:length(beamInfo)
+ % ix = ~isnan(dij.ax{1}./dij.bx{1});
+ if isfield(resultGUI, 'RBExDose')
+ Dose = resultGUI.(['RBExDose', beamInfo(i).suffix]);
+ else
+ Dose = resultGUI.(['physicalDose', beamInfo(i).suffix]);
+ end
+ effect = dij.ax{ctScen}.* Dose + dij.bx{ctScen}.*Dose.^2;
+ resultGUI.(['BED', beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
+ resultGUI.(['BED', beamInfo(i).suffix])(ix) = effect(ixWeighted)./alphaX(ixWeighted);
+ resultGUI.(['BED', beamInfo(i).suffix]) = reshape(resultGUI.(['BED', beamInfo(i).suffix]), dij.doseGrid.dimensions);
+ end
+ if isfield(resultGUI, 'RBExDose')
+ matRad_cfg.dispWarning('Photon Equiavlent BED calculated');
+ end
+ end
+end
+
+%add some dij meta
+if isfield(dij,'meta') && isstruct(dij.meta)
+ resultGUI.meta = dij.meta;
+end
+
+
+%% Final processing
+% Remove suffix for RBExD if there's only one available
+if any(cellfun(@(teststr) ~isempty(strfind(lower(teststr),'alpha')), fieldnames(dij))) && isfield(dij,'RBE_models') && length(dij.RBE_models) == 1
+ % Get fieldnames that include the specified RBE model
+ fnames = fieldnames(resultGUI);
+ fnames = fnames(cellfun(@(teststr) ~isempty(strfind(lower(teststr),lower(dij.RBE_models{1}))), fnames));
+
+ % Rename fields and remove model specifier if there's only one
+ for f = 1:length(fnames)
+ resultGUI.(erase(fnames{f},['_',dij.RBE_models{1}])) = resultGUI.(fnames{f});
+ end
+
+ % Remove old fields
+ resultGUI = rmfield(resultGUI,fnames);
+end
+
+% group similar fields together
+resultGUI = orderfields(resultGUI);
+
+% interpolation if dose grid does not match ct grid
+if isfield(dij,'ctGrid') && any(dij.ctGrid.dimensions~=dij.doseGrid.dimensions)
+ myFields = fieldnames(resultGUI);
+ for i = 1:numel(myFields)
+ if numel(resultGUI.(myFields{i})) == dij.doseGrid.numOfVoxels
+
+ % interpolate!
+ resultGUI.(myFields{i}) = matRad_interp3(dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z, ...
+ resultGUI.(myFields{i}), ...
+ dij.ctGrid.x,dij.ctGrid.y',dij.ctGrid.z,'linear',0);
+
+ end
+ end
+end
+
+
+end
+
diff --git a/tools/matRad_calcIntEnergy.m b/matRad/util/matRad_calcIntEnergy.m
similarity index 95%
rename from tools/matRad_calcIntEnergy.m
rename to matRad/util/matRad_calcIntEnergy.m
index a700273da..ae8dd28ad 100644
--- a/tools/matRad_calcIntEnergy.m
+++ b/matRad/util/matRad_calcIntEnergy.m
@@ -21,7 +21,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/tools/matRad_checkMexFileExists.m b/matRad/util/matRad_checkMexFileExists.m
similarity index 96%
rename from tools/matRad_checkMexFileExists.m
rename to matRad/util/matRad_checkMexFileExists.m
index 6e379bdd6..87fbb340d 100644
--- a/tools/matRad_checkMexFileExists.m
+++ b/matRad/util/matRad_checkMexFileExists.m
@@ -27,7 +27,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -41,7 +41,7 @@
end
%Explicitly check for matching mex file (id 3)
-fileExists = (exist(filename,'file') == 3);
+fileExists = (exist([filename '.' mexext],'file') == 3);
%For octave we have experimental precompiled files for Octave 5 64 bit
[env,ver] = matRad_getEnvironment();
diff --git a/matRad/util/matRad_compareDijStf.m b/matRad/util/matRad_compareDijStf.m
new file mode 100644
index 000000000..ec27c5091
--- /dev/null
+++ b/matRad/util/matRad_compareDijStf.m
@@ -0,0 +1,47 @@
+function [allMatch, msg] = matRad_compareDijStf(dij,stf)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% call
+% matching = matRad_comparePlnDijStf(pln,stf,dij)
+%
+% input
+% dij: matRad dij struct
+% stf: matRad steering information struct
+%
+% output
+%
+% allMatch: flag is true if they all match
+% matching: message to display
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+allMatch=true;
+msg = [];
+
+ %% compare number of rays per beam in dij and stf
+ stf_RaysPerBeam=[stf.numOfRays];
+ if numel(stf_RaysPerBeam) ~= numel(dij.numOfRaysPerBeam) ... % different size
+ || ~isempty(find(stf_RaysPerBeam-dij.numOfRaysPerBeam,1)) % different values
+ msg= 'Number of rays do not match';
+ allMatch=false;
+ return
+ end
+ stf_gantryAngles=[stf.gantryAngle];
+ if dij.numOfBeams ~= numel(stf_gantryAngles)
+ msg= 'Number of beams do not match';
+ allMatch=false;
+ return
+ end
+
+end
\ No newline at end of file
diff --git a/matRad/util/matRad_comparePlnStf.m b/matRad/util/matRad_comparePlnStf.m
new file mode 100644
index 000000000..6447019d8
--- /dev/null
+++ b/matRad/util/matRad_comparePlnStf.m
@@ -0,0 +1,98 @@
+function [allMatch, msg] = matRad_comparePlnStf(pln,stf)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% call
+% matching = matRad_comparePlnDijStf(pln,stf,dij)
+%
+% input
+% dij: matRad dij struct
+% stf: matRad steering information struct
+% pln: matRad plan meta information struct
+%
+% output
+%
+% allMatch: flag is true if they all match
+% matching: message to display
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+allMatch=true;
+msg = [];
+
+%% check if steering information is available in plan from the begining
+if ~isfield(pln,'propStf')
+ allMatch=false;
+ msg= 'No steering information in plan';
+ return
+end
+
+%% compare number of gantry angles, but ignore if numOfBeams not set
+if isfield(pln.propStf,'numOfBeams') && pln.propStf.numOfBeams ~= numel(stf)
+ msg= 'Number of beams do not match';
+ allMatch=false;
+ return
+end
+
+%% compare gantry angles in stf and pln
+stf_gantryAngles=[stf.gantryAngle];
+if ~isfield(pln.propStf,'gantryAngles') || numel(stf_gantryAngles) ~= numel(pln.propStf.gantryAngles) ... % different size
+ || ~isempty(find(stf_gantryAngles-pln.propStf.gantryAngles, 1)) % values in stf and pln do not match % values in stf and pln do not match
+ allMatch=false;
+ msg= 'Gantry angles do not match';
+ return
+end
+
+%% compare couch angles in stf and pln
+stf_couchAngles=[stf.couchAngle];
+if ~isfield(pln.propStf,'couchAngles') || numel(stf_couchAngles) ~= numel(pln.propStf.couchAngles) ... % different size
+ || ~isempty(find(stf_couchAngles-pln.propStf.couchAngles, 1)) % values in stf and pln do not match
+ allMatch=false;
+ msg= 'Couch angles do not match';
+ return
+end
+
+%% compare Bixel width in stf and pln
+bixelMatch = false;
+if isfield(pln.propStf,'bixelWidth') && isfield(stf(1),'bixelWidth')
+ if isnumeric(stf(1).bixelWidth) && isequal(stf(1).bixelWidth,pln.propStf.bixelWidth)
+ bixelMatch = true;
+ elseif ischar(stf(1).bixelWidth) && strcmp(stf(1).bixelWidth,'field')
+ bixelMatch = true;
+ end
+end
+
+if ~bixelMatch
+ allMatch=false;
+ msg= 'Bixel width does not match';
+ return
+end
+
+%% compare radiation mode in stf and pln
+if ~isfield(pln,'radiationMode') || ~strcmp(stf(1).radiationMode, pln.radiationMode)
+ allMatch=false;
+ msg= 'Radiation mode does not match';
+ return
+end
+
+%% compare isocenter in stf and pln for each gantry angle
+for i = 1:numel(pln.propStf.gantryAngles)
+ if ~isempty(find(stf(i).isoCenter - pln.propStf.isoCenter(i,:) ,1))
+ allMatch=false;
+ msg= 'Isocenters do not match';
+ return
+ end
+end
+
+end
\ No newline at end of file
diff --git a/tools/matRad_convertOldCstToNewCstObjectives.m b/matRad/util/matRad_convertOldCstToNewCstObjectives.m
similarity index 95%
rename from tools/matRad_convertOldCstToNewCstObjectives.m
rename to matRad/util/matRad_convertOldCstToNewCstObjectives.m
index 2fab9f854..1322ea247 100644
--- a/tools/matRad_convertOldCstToNewCstObjectives.m
+++ b/matRad/util/matRad_convertOldCstToNewCstObjectives.m
@@ -26,7 +26,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/util/matRad_findSubclasses.m b/matRad/util/matRad_findSubclasses.m
new file mode 100644
index 000000000..c5b943606
--- /dev/null
+++ b/matRad/util/matRad_findSubclasses.m
@@ -0,0 +1,197 @@
+function classList = matRad_findSubclasses(superClass,varargin)
+% matRad_findSubclasses: Helper function to find subclasses
+% This method can find subclasses to a class within package folders and
+% normal folders. It is not very fast, but Matlab & Octave compatible, so
+% it is advised to cache classes once scanned.
+%
+% call:
+% classList = matRad_findSubclasses(superClass);
+% classList =
+% matRad_findSubclasses(superClass,'package',{packageNames})
+% classList =
+% matRad_findSubclasses(superClass,'folders',{folderNames})
+% classList =
+% matRad_findSubclasses(superClass,'includeAbstract',true/false)
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+p = inputParser;
+p.addRequired('superClass',@(x) ischar(x) || isa(x,'meta.class'));
+p.addParameter('packages',{},@(x) iscell(x) && (iscellstr(x) || all(cellfun(@(y) isa(y,'meta.package'),x))));
+p.addParameter('folders',{},@(x) iscellstr(x) && all(isfolder(x)));
+p.addParameter('includeAbstract',false,@(x) isscalar(x) && islogical(x));
+p.addParameter('includeSubfolders',false,@(x) isscalar(x) && islogical(x));
+p.addParameter('usePath',false,@(x) islogical(x) && isscalar(x));
+
+p.parse(superClass,varargin{:});
+
+superClass = p.Results.superClass;
+packages = p.Results.packages;
+folders = p.Results.folders;
+includeAbstract = p.Results.includeAbstract;
+includeSubfolders = p.Results.includeSubfolders;
+usePath = p.Results.usePath;
+
+%Create MetaObject if char was given
+if ischar(superClass)
+ superClass = meta.class.fromName(superClass);
+end
+
+%Check if path is used
+if usePath
+ addPath = [path pathsep];
+else
+ addPath = '';
+end
+
+for folderIx = 1:length(folders)
+ %Generate subfolder path or only add current folder to class search
+ %path
+ if includeSubfolders
+ addPath = [addPath genpath(folders{folderIx}) pathsep];
+ else
+ addPath = [addPath folders{folderIx} pathsep];
+ end
+end
+folders = strsplit(addPath,pathsep);
+
+if all(cellfun(@(y) isa(y,'meta.package'),packages))
+ packages = cellfun(@(y) y.Name,packages);
+end
+
+matRad_cfg = MatRad_Config.instance();
+
+classList = {};
+
+%%Collect package classes
+for packageIx = 1:length(packages)
+ package = packages{packageIx};
+ if matRad_cfg.isMatlab
+ mp = meta.package.fromName(package);
+ classList = [classList; num2cell(mp.ClassList)];
+ elseif matRad_cfg.isOctave
+ p = strsplit(path,pathsep);
+ ix = find(cellfun(@(f) isfolder([f filesep '+' package]),p));
+ if ~isscalar(ix)
+ matRad_cfg.dispError('Could not uniquely identify package folder ''+%s'' (%d folders found with that name!)',package,numel(ix));
+ end
+ classList = [classList; matRad_getClassesFromFolder(p{ix},package)];
+ else
+ matRad_cfg.dispError('Environment %s unknown!',matRad_cfg.env);
+ end
+end
+
+%Collect classes from folders
+for folderIx = 1:length(folders)
+ folder = folders{folderIx};
+ classList = [classList; matRad_getClassesFromFolder(folder)];
+end
+
+%Throw out abstract classes if requested
+if ~includeAbstract
+ isNotAbstract = cellfun(@(mc) ~mc.Abstract,classList);
+ classList = classList(isNotAbstract);
+end
+
+%Now test if it is a subclass
+
+inherits = cellfun(@(mc) matRad_checkInheritance(mc,superClass),classList);
+classList = classList(inherits);
+
+%We want classes to be a row vector, but the meta.class lists column
+%vector
+if ~isrow(classList)
+ classList = classList';
+end
+end
+
+function metaClassList = matRad_getClassesFromFolder(folder,packageName)
+% matRad_getClassesFromFolder: collects classes from a folder. packageName
+% can be passed for Octave compatibility
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%Sanity check to avoid octave calling what when argument empty
+if isempty(folder)
+ metaClassList = {};
+ return;
+end
+
+%We accept the specific package name for Octave compatability
+if nargin < 2
+ packageNameWithDot= '';
+ fullFolder = folder;
+else
+ packageNameWithDot = [packageName '.'];
+ fullFolder = [folder filesep '+' packageName];
+end
+
+folderInfo = what(fullFolder);
+if ~isempty(folderInfo)
+ [~,potentialClasses] = cellfun(@fileparts,{folderInfo.m{:},folderInfo.p{:}},'UniformOutput',false); %Potential class files
+
+ %In octave the what function returns the class folders with an '@'
+ classFolders = folderInfo.classes;
+ classFolders = cellfun(@(f) erase(f,'@'),classFolders,'UniformOutput',false);
+
+ potentialClasses = [potentialClasses, classFolders];
+
+else
+ potentialClasses = {};
+end
+
+metaClassList = {};
+
+for potentialClassIx = 1:length(potentialClasses)
+ potentialClass = potentialClasses{potentialClassIx};
+ % get meta class with package name infront
+ mc = meta.class.fromName([packageNameWithDot potentialClass]);
+
+ % Check if we found a class
+ if isempty(mc)
+ continue;
+ end
+
+ metaClassList{end+1} = mc;
+end
+metaClassList = transpose(metaClassList);
+end
+
+function inherits = matRad_checkInheritance(metaClass,superClass)
+% matRad_checkInheritance: checks class inheritance of a superclass
+% recursively. Would be easier with superclass method, but this is not
+% available in Octave
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+
+if matRad_cfg.isMatlab
+ scs = superclasses(metaClass.Name);
+ inherits = any(strcmp(scs,superClass.Name));
+else
+ superClasses = metaClass.SuperclassList;
+
+ if isempty(superClasses)
+ inherits = false;
+ else
+ inherits = any(cellfun(@(x) strcmp(x.Name,superClass.Name),superClasses));
+ if inherits == false
+ for ix = 1:numel(superClasses)
+ inherits = matRad_checkInheritance(superClasses{ix},superClass);
+ end
+ end
+ end
+end
+
+end
+
+
diff --git a/matRad/util/matRad_fitBaseData.m b/matRad/util/matRad_fitBaseData.m
new file mode 100644
index 000000000..1fc204b2d
--- /dev/null
+++ b/matRad/util/matRad_fitBaseData.m
@@ -0,0 +1,238 @@
+function fitData = matRad_fitBaseData(doseCube, resolution, energy, mcData, initSigma0, onAxis)
+% fit analytical data to pencil beam stored in doseCube,
+% pencil beam in positive y direction, target per default in center of
+% plane
+%
+% call
+% fitData = matRad_fitBaseData(doseCube, resolution, energy, initSigma0)
+%
+% input
+% doseCube: dose cube as an M x N x O array
+% resolution: resolution of the cubes [mm/voxel]
+% energy: energy of ray
+% initSigma0: initial sigma of beam, measured at entrance into doseCube
+% onAxis: y and z coordinates of beam
+%
+% output
+% fitData: struct containing fit, structured in the same way as data
+% in machine.data
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+matRad_cfg = MatRad_Config.instance();
+
+% save cube dimesions
+cubeDim = size(doseCube);
+
+
+% set onAxis to default if not given
+if ~exist('onAxis','var')
+ onAxis = [cubeDim(2)/2 * resolution.y, cubeDim(3)/2 * resolution.z];
+end
+
+% extract IDD and discard values = 0
+IDD = reshape(sum(sum(doseCube,2),3),cubeDim(1),1);
+IDD = [IDD(1); IDD];
+IDDnotZero = find(IDD);
+IDD = IDD(IDDnotZero);
+
+% calculate radiological depths for IDD
+depthsIDD = [0, (resolution.x / 2 : resolution.x : resolution.x * cubeDim(1))];
+depthsIDD = depthsIDD(IDDnotZero);
+
+% interpolate IDD in steps of 0.05mm
+IDD = interp1(depthsIDD, IDD, 0:0.05:depthsIDD(end), 'spline');
+depthsIDD = 0:0.05:depthsIDD(end);
+
+% calculate lateral radii to ray axis
+axisGauss = zeros(cubeDim(2), cubeDim(3));
+for i = 1:cubeDim(2)
+ for j = 1:cubeDim(3)
+ axisGauss(i,j) = sqrt((resolution.y * i - onAxis(1))^2 + (resolution.z * j - onAxis(2))^2);
+ end
+end
+
+resSigma1 = zeros(numel(IDDnotZero) - 1, 1);
+resSigma2 = zeros(numel(IDDnotZero) - 1, 1);
+resWeight = zeros(numel(IDDnotZero) - 1, 1);
+
+preSigma2 = 20;
+
+for i = 1:size(resSigma1,1)
+% curve fitting gaussians
+
+ % save y-z dose slice in profile
+ profile = doseCube(i,:,:);
+ profile = reshape(profile, cubeDim(2), cubeDim(3));
+ profile = profile / sum(sum(profile)) / (resolution.x * resolution.y);
+
+ gauss1 = @(x , sigma1) 1 / (2 * pi * sigma1^2) * exp(- x.^2 / (2*sigma1^2));
+
+ gauss2 = @(x, w, sigma1, sigma2) (1 - w) / (2 * pi * sigma1^2) .* exp(- x.^2 / (2*sigma1^2)) + ...
+ w / (2 * pi * sigma2^2) .* exp(- x.^2 / (2*sigma2^2));
+
+ % fitting for either matlab or octave_core_file_limit
+ if ~matRad_cfg.isOctave
+
+ % first single gauss fit
+ funcs1.objective = @(p) sum((gauss1(axisGauss, p(1)) - profile).^2, 'all');
+
+ funcs1.gradient = @(p) 2 * sum((gauss1(axisGauss, p(1)) - profile) .* ...
+ exp(-axisGauss.^2 ./ (2 * p(1)^2)) .* (axisGauss.^2 - 2 .* p(1)^2) ./ (2 .* pi .* p(1)^5), 'all');
+
+
+ options1.lb = 0;
+ options1.ub = Inf;
+ options1.ipopt.hessian_approximation = 'limited-memory';
+ options1.ipopt.limited_memory_update_type = 'bfgs';
+ options1.ipopt.print_level = 1;
+ start1 = 8;
+
+ [fitResult1, ~] = ipopt (start1, funcs1, options1);
+ preSigma1 = fitResult1;
+
+ %define fit parameters
+ funcs2.objective = @(p) sum(sum((gauss2(axisGauss, p(1), p(2), p(3)) - profile).^2));
+
+ funcs2.gradient = @(p) [ 2 * sum(sum((gauss2(axisGauss, p(1), p(2), p(3)) - profile) .* ...
+ (-1 / (2 * pi * p(2)^2) .* exp(-axisGauss.^2 / (2 * p(2)^2)) + 1 / (2 * pi * p(3)^2) .* exp(-axisGauss.^2 / (2 * p(3)^2)))));
+ 2 * sum(sum((gauss2(axisGauss, p(1), p(2), p(3)) - profile) .* ...
+ (1 - p(1)) .* exp(-axisGauss.^2 / (2 * p(2)^2)) .* (axisGauss.^2 - 2 * p(2)^2) / (2 * pi * p(2)^5)));
+ 2 * sum(sum((gauss2(axisGauss, p(1), p(2), p(3)) - profile) .* ...
+ p(1) .* exp(-axisGauss.^2 / (2 * p(3)^2)) .* (axisGauss.^2 - 2 * p(3)^2) / (2 * pi * p(3)^5)))];
+
+ options2.lb = [ 0, preSigma1 - 1, preSigma1 + 1];
+ options2.ub = [ 0.2, preSigma1 + 1, preSigma2 + 10];
+ % options.ipopt.tol = 1e-30;
+
+ options2.ipopt.hessian_approximation = 'limited-memory';
+ options2.ipopt.limited_memory_update_type = 'bfgs';
+ options2.ipopt.print_level = 1;
+
+ %run fit and calculate actual sigma by squared substracting initial
+ %sigma / spotsize
+
+ start2 = [0.002, preSigma1, preSigma2];
+ [fitResult, ~] = ipopt (start2, funcs2, options2);
+
+ preSigma2 = fitResult(3);
+
+ else
+
+ % first single gauss fit
+ phi1{1} = @(p) sum(sum((gauss1(axisGauss, p(1)) - profile).^2));
+ phi1{2} = @(p) 2 * sum(sum((gauss1(axisGauss, p(1)) - profile) .* ...
+ exp(-axisGauss.^2 ./ (2 * p(1)^2)) .* (axisGauss.^2 - 2 .* p(1)^2) ./ (2 .* pi .* p(1)^5)));
+
+ start1 = 8;
+
+ [fitResult1, ~] = sqp(start1, phi1, [], [], 0, Inf);
+ preSigma1 = fitResult1;
+
+ %define fit parameters
+ phi2{1} = @(p) sum(sum((gauss2(axisGauss, p(1), p(2), p(3)) - profile).^2));
+ phi2{2} = @(p) [ 2 * sum(sum((gauss2(axisGauss, p(1), p(2), p(3)) - profile) .* ...
+ (-1 / (2 * pi * p(2)^2) .* exp(-axisGauss.^2 / (2 * p(2)^2)) + 1 / (2 * pi * p(3)^2) .* exp(-axisGauss.^2 / (2 * p(3)^2)))));
+ 2 * sum(sum((gauss2(axisGauss, p(1), p(2), p(3)) - profile) .* ...
+ (1 - p(1)) .* exp(-axisGauss.^2 / (2 * p(2)^2)) .* (axisGauss.^2 - 2 * p(2)^2) / (2 * pi * p(2)^5)));
+ 2 * sum(sum((gauss2(axisGauss, p(1), p(2), p(3)) - profile) .* ...
+ p(1) .* exp(-axisGauss.^2 / (2 * p(3)^2)) .* (axisGauss.^2 - 2 * p(3)^2) / (2 * pi * p(3)^5)))];
+
+ %run fit and calculate actual sigma by squared substracting initial
+ %sigma / spotsize
+
+ start2 = [0.002, preSigma1, preSigma2];
+ lb = [ 0, preSigma1 - 1, preSigma1 + 1];
+ ub = [ 0.2, preSigma1 + 1, preSigma2 + 10];
+ [fitResult, ~] = sqp (start2, phi2, [], [], lb, ub);
+
+ preSigma2 = fitResult(3);
+ end
+
+
+ if (i == 1)
+ if ~exist('initSigma0','var')
+ noInitSigma = true;
+ options.lb = 0;
+ initSigma0 = 0;
+ else
+ noInitSigma = false;
+ options.lb = initSigma0;
+ end
+ end
+
+
+ if (i == 1) && noInitSigma
+ initSigma0 = fitResult(2);
+ end
+
+
+ if(fitResult(2) > initSigma0)
+ actualSigma = sqrt(fitResult(2)^2 - initSigma0^2);
+ else
+ actualSigma = 0;
+ end
+
+ resSigma1(i) = actualSigma;
+ resSigma2(i) = sqrt(fitResult(3)^2 - initSigma0^2);
+ resWeight(i) = fitResult(1);
+end
+
+% interpolate sigma on depths of IDD
+resSigma1 = [0; resSigma1];
+resSigma2 = [resSigma2(1); resSigma2];
+resWeight = [resWeight(1); resWeight];
+
+depthsSigma = [0, (resolution.x / 2 : resolution.x : resolution.x * cubeDim(1))];
+depthsSigma = depthsSigma(IDDnotZero);
+resSigma1 = interp1(depthsSigma, resSigma1, depthsIDD);
+resSigma2 = interp1(depthsSigma, resSigma2, depthsIDD);
+resWeight = interp1(depthsSigma, resWeight, depthsIDD);
+%resSigma2 = smoothdata(resSigma2, 'gaussian', 200);
+
+% interpolate range at 80% dose after peak.
+[maxV, maxI] = max(IDD);
+[~, r80ind] = min(abs(IDD(maxI:end) - 0.8 * maxV));
+r80ind = r80ind - 1;
+r80 = interp1(IDD(maxI + r80ind - 1:maxI + r80ind + 1), ...
+ depthsIDD(maxI + r80ind - 1:maxI + r80ind + 1), 0.8 * maxV);
+
+% conversion factor for IDDs
+cf = 1 / 1.6021766208e-02 * resolution.y * resolution.z;
+
+% save data in machine
+fitData.energy = energy;
+fitData.range = r80;
+fitData.depths = depthsIDD';
+fitData.Z = IDD' * cf;
+fitData.peakPos = depthsIDD(maxI);
+fitData.weight = resWeight';
+fitData.offset = 0;
+
+SAD = (2218 + 1839) / 2;
+fitData.initFocus.dist = linspace(SAD - 420, SAD + 420, 11);
+
+corIso = (mcData.corNozzle * mcData.spotNozzle + mcData.divNozzle * mcData.z) / initSigma0;
+initSigmaZ = @(z) sqrt( initSigma0^2 - 2 * corIso * initSigma0 * mcData.divNozzle .* z + mcData.divNozzle^2 .* z.^2);
+
+
+fitData.initFocus.sigma = initSigmaZ(-(fitData.initFocus.dist - SAD));
+fitData.initFocus.SisFWHMAtIso = 2.3548 * initSigma0;
+
+fitData.sigma1 = resSigma1';
+fitData.sigma2 = resSigma2';
+
+
+end
diff --git a/matRad/util/matRad_generateBodyContour.m b/matRad/util/matRad_generateBodyContour.m
new file mode 100644
index 000000000..e0953751c
--- /dev/null
+++ b/matRad/util/matRad_generateBodyContour.m
@@ -0,0 +1,67 @@
+function cst = matRad_generateBodyContour(ct,cst,thresholdHU)
+% function to create a BODY contour for imported patient cases that do not have one
+%
+% call
+% cst = matRad_generateBodyContour(ct,cst,thresholdHU)
+%
+% input
+% ct: matrad ct structure
+% cst: matrad cst structure
+% thresholdHU: HU thresholding value (optional) default = -500 HU
+%
+%
+% output
+% cst: matRads cst struct with inserted BODY contour
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+% dicom import needs image processing toolbox -> check if available
+available = matRad_checkEnvDicomRequirements(matRad_cfg.env);
+
+if ~available
+ matRad_cfg.dispError('Image processing toolbox / packages not available!');
+end
+
+if nargin < 3
+ thresholdHU = -500;
+end
+% visualize Histogram
+% figure,
+% histogram(ct.cubeHU{1});
+% hold on
+% scatter(-500,100,'x')
+% hold off
+% xlabel('HU')
+% ylabel('#')
+
+% Thresholding on HU
+mask = zeros(ct.cubeDim);
+mask(ct.cubeHU{1}>thresholdHU) = 1;
+filledIm= imfill(mask);
+
+% Write to cst
+pos = size(cst,1);
+cst{pos+1,1} = pos;
+cst{pos+1,2} = 'BODY';
+cst{pos+1,3} = 'OAR';
+cst{pos+1,4}{1} = find(filledIm);
+cst{pos+1,5}.Priority = 99;
+cst{pos+1,5}.alphaX = 0.1;
+cst{pos+1,5}.betaX = 0.05;
+cst{pos+1,5}.Visible = 1;
+cst{pos+1,5}.visibleColor = [0.7,0.3,0.1];
+cst{pos+1,6} = [];
+
+end
\ No newline at end of file
diff --git a/matRad/util/matRad_generateSingleBixelStf.m b/matRad/util/matRad_generateSingleBixelStf.m
new file mode 100644
index 000000000..dcd74040e
--- /dev/null
+++ b/matRad/util/matRad_generateSingleBixelStf.m
@@ -0,0 +1,48 @@
+function stf = matRad_generateSingleBixelStf(ct,cst,pln)
+%
+% call
+% stf = matRad_generateSingleBixelStf(ct,cst,pln,visMode)
+%
+% input
+% ct: ct cube
+% cst: matRad cst struct
+% pln: matRad plan meta information struct
+% visMode: toggle on/off different visualizations by setting this value to 1,2,3 (optional)
+%
+% output
+% stf: matRad steering information struct
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+matRad_cfg.dispDeprecationWarning('The function %s is not intendet to be used, check out the Single Bixel Stf Generators in the steering folder',fileparts(mfilename));
+
+if any(strcmp(pln.radiationMode,{'protons','helium','carbon','oxygen'}))
+ stfGen = matRad_StfGeneratorParticleSingleBeamlet(pln);
+elseif strcmp(pln.radiationMode,'photons')
+ stfGen = matRad_StfGeneratorPhotonSingleBeamlet(pln);
+else
+ matRad_cfg.dispError('Unsupported Radiation Mode!');
+end
+
+stf = stfGen.generate(ct,cst);
+
+end
+
+
+
diff --git a/matRad/util/matRad_getAlphaBetaCurves.m b/matRad/util/matRad_getAlphaBetaCurves.m
new file mode 100644
index 000000000..79370983b
--- /dev/null
+++ b/matRad/util/matRad_getAlphaBetaCurves.m
@@ -0,0 +1,110 @@
+function [machine] = matRad_getAlphaBetaCurves(machine,varargin)
+% matRad alpha beta curve calculation tool
+%
+% call
+% machine = matRad_getAlphaBetaCurves(machine)
+% machine = matRad_getAlphaBetaCurves(machine,cst,modelName,overrideAB)
+% Example full call for protons
+% machine = matRad_getAlphaBetaCurves(machine,pln,cst,'MCN','override')
+% input
+% machine: matRad machine file to change
+% varargin (optional): cst: matRad cst struct (for custom alpha/beta,
+% otherwise default is alpha=0.1, beta=0.05;
+% modelName: specify RBE modelName
+% overrideAB: calculate new alpha beta even if available
+% and override
+%
+% output
+% machine: updated machine file with alpha/beta curves
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2021 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+overrideAB = false;
+if ~isempty(varargin)
+ for i = 1:nargin-1
+ if iscell(varargin{i})
+ cst = varargin{i};
+ elseif ischar(varargin{i})
+ if ~isempty(strfind(lower(varargin{i}),'override'))
+ if isfield(machine.data,'alphaX')
+ overrideAB = true;
+ end
+ else
+ modelName = varargin{i};
+ end
+ end
+ end
+end
+
+if ~any(cellfun(@(teststr) isempty(strfind(lower(machine.meta.radiationMode),lower(teststr))), {'protons','helium'})) || ...
+ (exist('modelName','var') && ~isempty(strfind(lower(modelName),'LEM')))
+ matRad_cfg.dispWarning('alphaBetaCurves cannot be calculated for LEM model, skipping...');
+elseif ~isfield(machine.data,'alphaX') || overrideAB
+ %% save original fields with suffix if available
+ if overrideAB
+ fieldnames = {'alphaX','betaX','alphaBetaRatio','alpha','beta'};
+ for k=1:numel(fieldnames)
+ [machine.data.([fieldnames{k} '_org'])] = deal(machine.data.(fieldnames{k}));
+ end
+ machine.data = rmfield(machine.data,fieldnames);
+ end
+
+ %% Set biological models
+ if ~exist('modelName','var')
+ switch machine.meta.radiationMode
+ case 'protons'
+ modelName = 'MCN'; % Use McNamara model as default for protons
+ case 'helium'
+ modelName = 'HEL';
+ end
+ end
+ pln.bioParam = matRad_bioModel(machine.meta.radiationMode,'RBExD',modelName);
+
+ %% get unique combintions of alpha/beta from cst or use default alpha/beta values
+ if ~exist('cst','var')
+ ab(1,1) = 0.1;
+ ab(1,2) = 0.05;
+ else
+ ab = cellfun(@(f)cst{f,5}.alphaX,num2cell(1:10)');
+ ab(:,2) = cellfun(@(f)cst{f,5}.betaX,num2cell(1:10)');
+ end
+ ab = unique(ab,'rows');
+
+ % save alpha/beta values for each energy in machine.data
+ [machine.data(:).alphaX] = deal(ab(:,1));
+ [machine.data(:).betaX] = deal(ab(:,2));
+ [machine.data(:).alphaBetaRatio] = deal(ab(:,1) ./ ab(:,2));
+
+ % calculate alpha/beta curves for each energy in machine.data
+ for j = 1:length(machine.data)
+ depths = machine.data(j).depths + machine.data(j).offset;
+ voxelsOnes = ones(numel(depths),1);
+
+ machine.data(j).alpha = zeros(numel(voxelsOnes),size(ab,1));
+ machine.data(j).beta = zeros(numel(voxelsOnes),size(ab,1));
+
+ for k = 1:length(machine.data(j).alphaX)
+ [machine.data(j).alpha(:,k),machine.data(j).beta(:,k)] = pln.bioParam.calcLQParameter(depths,machine.data(j),voxelsOnes,...
+ machine.data(j).alphaX(k)*voxelsOnes,machine.data(j).betaX(k)*voxelsOnes,machine.data(j).alphaBetaRatio(k)*voxelsOnes);
+ end
+ end
+
+else
+ matRad_cfg.dispWarning('Basedata already contains alpha/beta curves. Use "overrideAB"-flag to override.')
+end
+
+end
\ No newline at end of file
diff --git a/tools/matRad_getEnvironment.m b/matRad/util/matRad_getEnvironment.m
similarity index 93%
rename from tools/matRad_getEnvironment.m
rename to matRad/util/matRad_getEnvironment.m
index d15f994b4..db87f4727 100644
--- a/tools/matRad_getEnvironment.m
+++ b/matRad/util/matRad_getEnvironment.m
@@ -20,7 +20,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/util/matRad_identifyClassesByConstantProperties.m b/matRad/util/matRad_identifyClassesByConstantProperties.m
new file mode 100644
index 000000000..5457edb4e
--- /dev/null
+++ b/matRad/util/matRad_identifyClassesByConstantProperties.m
@@ -0,0 +1,118 @@
+function [classList] = matRad_identifyClassesByConstantProperties(metaClasses,primaryPropertyName,varargin)
+% matRad_identifyClassesByProperty: Helper function to identify classes by
+% property
+% This method identifies classes based on a primary property and optional
+% additional properties.
+%
+% call:
+% classList = matRad_identifyClassesByProperty(metaClasses,
+% primaryPropertyName) classList =
+% matRad_identifyClassesByProperty(metaClasses, primaryPropertyName,
+% 'defaults', {defaultClasses})
+% classList =
+% matRad_identifyClassesByProperty(metaClasses, primaryPropertyName,
+% 'additionalPropertyNames', {additionalProperties})
+%
+% inputs:
+% - metaClasses: A cell array of meta.class objects representing the
+% classes to be identified.
+% - primaryPropertyName: The name of the primary property used for
+% identification.
+%
+% optional Parameter Inputs:
+% - defaults: A cell array of default classes that should be listed
+% first.
+% - additionalPropertyNames: A cell array of additional property names
+% used for identification.
+%
+% outputs:
+% - classList: A structure array containing the identified classes.
+% - primaryPropertyName: The values of the primary property for each
+% class. - additionalPropertyNames: The values of the additional
+% properties for each class. - className: The names of the identified
+% classes. - handle: The constructor handles of the identified
+% classes.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance(); % Get the instance of the MatRad_Config class
+
+p = inputParser; % Create an input parser object
+
+% Define the required inputs for the input parser
+p.addRequired('metaClasses',@(x) iscell(x) && all(cellfun(@(x) isa(x,'meta.class'),x)));
+p.addRequired('primaryPropertyName',@(x) ischar(x) && isrow(x) && ~isempty(x));
+
+% Define the optional inputs for the input parser
+p.addParameter('defaults',{},@(x) iscell(x) && all(cellfun(@(x) ischar(x) && isrow(x),x)));
+p.addParameter('additionalPropertyNames',{},@(x) iscell(x) && all(cellfun(@(x) ischar(x) && isrow(x),x)));
+
+% Parse the input arguments
+p.parse(metaClasses,primaryPropertyName,varargin{:});
+defaults = p.Results.defaults;
+additionalPropertyNames = p.Results.additionalPropertyNames;
+
+% Get the values of the primary property for each class
+primaryPropertyValueList = getPropertyValues(metaClasses,primaryPropertyName,matRad_cfg.isMatlab);
+
+% Get the values of the additional properties for each class
+additionalPropertyValueList = cell(numel(additionalPropertyNames),numel(metaClasses));
+for i = 1:numel(additionalPropertyNames)
+ additionalPropertyName = additionalPropertyNames{i};
+ additionalPropertyValueList(i,:) = getPropertyValues(metaClasses,additionalPropertyName,matRad_cfg.isMatlab);
+end
+
+% Get the names of the identified classes and their constructor handles
+classNameList = cellfun(@(mc) mc.Name,metaClasses,'UniformOutput',false);
+constructorHandleList = cellfun(@(namestr) str2func(namestr),classNameList,'UniformOutput',false);
+
+% Create a default sort pattern
+sortPattern = 1:numel(primaryPropertyValueList);
+
+% Make sure the default engines are the first ones listed
+if ~isempty(defaults)
+ findDefaultIx = [];
+ for defaultClassIx = 1:length(defaults)
+ defaultClass = defaults{defaultClassIx};
+ foundIx = find(strcmp(defaultClass,primaryPropertyValueList));
+ findDefaultIx(end+1:end+numel(foundIx)) = foundIx;
+ end
+
+ if ~isempty(findDefaultIx)
+ sortPattern = [sortPattern(findDefaultIx), sortPattern];
+ sortPattern = unique(sortPattern,'stable');
+ end
+end
+
+% Sort the property values, class names, and constructor handles
+primaryPropertyValueList = primaryPropertyValueList(sortPattern);
+additionalPropertyValueList = additionalPropertyValueList(:,sortPattern);
+classNameList = classNameList(sortPattern);
+constructorHandleList = constructorHandleList(sortPattern);
+
+% Create a structure array containing the identified classes
+classList = cell2struct([primaryPropertyValueList; additionalPropertyValueList; classNameList; constructorHandleList],{primaryPropertyName,additionalPropertyNames{:},'className','handle'});
+
+end
+
+%Helper function to get the property values dependent on the used environment
+function valueList = getPropertyValues(metaClasses,propertyName,isMatlab)
+ if isMatlab
+ % Get the default values of the property for each class in MATLAB
+ valueList = cellfun(@(mc) mc.PropertyList(strcmp({mc.PropertyList.Name}, propertyName)).DefaultValue,metaClasses,'UniformOutput',false);
+ else
+ % Get the default values of the property for each class in Octave
+ valueList = cellfun(@(mc) mc.PropertyList{find(cellfun(@(p) strcmp(p.Name, propertyName),mc.PropertyList))}.DefaultValue,metaClasses,'UniformOutput',false);
+ end
+end
diff --git a/matRad/util/matRad_info.m b/matRad/util/matRad_info.m
new file mode 100644
index 000000000..dba7a07d5
--- /dev/null
+++ b/matRad/util/matRad_info.m
@@ -0,0 +1,43 @@
+function message = matRad_info()
+% matRad function to get information message
+%
+% call
+% message = matRad_info()
+%
+% input
+%
+% output
+% message: An Information message about matRad
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2020 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%Copyright
+currYear = datestr(now,'yyyy');
+message = ['Copyright (C) ' currYear ' The matRad developers @ DKFZ Group Radiotherapy Optimization (e0404)'];
+
+%Warranty clause and license
+message = [message newline newline ...
+ 'matRad is free software and NOT A MEDICAL PRODUCT!' ...
+ newline 'matRad comes WITHOUT ANY WARRANTY and does not guarantee FITNESS FOR A PARTICULAR PURPOSE.' ...
+ newline 'Use matRad ONLY for research and education at your own risk.' ...
+ newline 'For license conditions see LICESNE.md. Third-party software used in matRad is subject to their respective licenses.'];
+
+%Websites
+message = [message newline newline ...
+ 'Check www.matRad.org and github.com/e0404/matRad for more information.'];
+
+end
\ No newline at end of file
diff --git a/matRad_interp1.m b/matRad/util/matRad_interp1.m
similarity index 63%
rename from matRad_interp1.m
rename to matRad/util/matRad_interp1.m
index 209914e7e..89ac8bc67 100644
--- a/matRad_interp1.m
+++ b/matRad/util/matRad_interp1.m
@@ -1,5 +1,5 @@
function y = matRad_interp1(xi,yi,x,extrapolation)
-% interpolates 1-D data (table lookup) and utilizes griddedInterpolant
+% interpolates 1-D data (table lookup) and utilizes griddedInterpolant
% if availabe in the used MATLAB version
%
% call
@@ -7,17 +7,17 @@
% y = matRad_interp1(xi,yi,x,extrapolation)
%
% input
-% xi: sample points
+% xi: sample points
% yi: corresponding data to sample points
% x: query points for interpolation
-% extrapolation: (optional) strategy for extrapolation. Similar to
+% extrapolation: (optional) strategy for extrapolation. Similar to
% interp1. NaN is the default extrapolation value
-%
+%
% output
-% y: interpolated data
+% y: interpolated data
%
% Note that all input data has to be given as column vectors for a correct
-% interpolation. yi can be a matrix consisting out of several 1-D datasets
+% interpolation. yi can be a matrix consisting out of several 1-D datasets
% in each column, which will all be interpolated for the given query points.
%
% Reference
@@ -25,13 +25,13 @@
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
-% Copyright 2020 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
+% Copyright 2020 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -56,35 +56,44 @@
% manual interpolation for only one query point to save time
if numel(x) == 1
-
+
ix1 = find((x >= xi), 1, 'last');
ix2 = find((x <= xi), 1, 'first');
-
+
if ix1 == ix2
y = yi(ix1,:);
elseif ix2 == ix1 + 1
- y = yi(ix1,:) + ( yi(ix2,:)-yi(ix1,:) ) * ( x - xi(ix1) ) / ( xi(ix2) - xi(ix1) );
+ y = yi(ix1,:) + ( yi(ix2,:)-yi(ix1,:) ) * ( x - xi(ix1) ) / ( xi(ix2) - xi(ix1) );
else
if isscalar(extrapolation)
y = extrapolation;
elseif strcmp(extrapolation,'extrap')
%In this unlikely event fall back to classic
y = interp1(xi,yi,x,'linear',extrapolation);
+ elseif strcmp(extrapolation,'nearest')
+ if x < min(xi)
+ y = min(yi);
+ elseif x > max(xi)
+ y = max(yi);
+ else
+ y = yi(ix1,:) + ( yi(ix2,:)-yi(ix1,:) ) * ( x - xi(ix1) ) / ( xi(ix2) - xi(ix1) );
+ end
else
- error('Invalid extrapolation argument!');
- end
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid extrapolation argument ''%s''!',extrapolation);
+ end
end
-
+
elseif isGriddedInterpolantAvailable
-
+
if isscalar(extrapolation)
extrapmethod = 'none';
elseif strcmp(extrapolation,'extrap')
extrapmethod = 'linear';
- else
- error('Invalid extrapolation argument!');
+ else
+ extrapmethod = extrapolation;
end
-
+
if size(yi,2) > 1
% interpolation for multiple 1-D datasets
samplePoints = {xi, 1:size(yi,2)};
@@ -94,19 +103,35 @@
samplePoints = {xi};
queryPoints = {x};
end
-
+
F = griddedInterpolant(samplePoints,yi,'linear',extrapmethod);
y = F(queryPoints);
-
+
if isnumeric(extrapolation) && ~isnan(extrapolation)
y(isnan(y)) = extrapolation;
end
-
-
+
+
else
-
- % for older matlab versions use this code
- y = interp1(xi,yi,x,'linear',extrapolation);
+ % for older matlab versions or octave use this code
+ if isscalar(extrapolation) || strcmp(extrapolation,'extrap')
+ extrapmethod = extrapolation;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ if matRad_cfg.isOctave && strcmp(extrapolation,'nearest')
+ extrapmethod = NaN;
+ elseif matRad_cfg.isOctave && ~strcmp(extrapolation,'linear')
+ matRad_cfg.dispError('Invalid extrapolation argument ''%s''!',extrapolation);
+ else
+ extrapmethod = 'extrap';
+ end
+ end
+ y = interp1(xi,yi,x(:),'linear',extrapmethod);
+ %Manual nearest neighbor assignment for extrapolation
+ if strcmp(extrapolation,'nearest')
+ y(x(:) < min(xi),:) = repmat(min(yi),sum(x(:) max(xi),:) = repmat(max(yi),sum(x(:)>max(xi)),1);
+ end
end
diff --git a/matRad_interp3.m b/matRad/util/matRad_interp3.m
similarity index 67%
rename from matRad_interp3.m
rename to matRad/util/matRad_interp3.m
index a14c2e490..6b240b553 100644
--- a/matRad_interp3.m
+++ b/matRad/util/matRad_interp3.m
@@ -25,7 +25,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -47,8 +47,16 @@
switch env
case 'MATLAB'
y = interp3(xi,yi,zi,x,xq,yq,zq,mode,extrapVal);
- case 'OCTAVE'
- [xqMesh,yqMesh,zqMesh] = meshgrid(xq,yq,zq);
- y = interp3(xi,yi,zi,x,xqMesh,yqMesh,zqMesh,mode,extrapVal);
+ case 'OCTAVE'
+ %If we do a vector query with similar sizes only don't create a meshgrid
+ if isequal(size(xq),size(yq),size(zq))
+ y = interp3(xi,yi,zi,x,xq,yq,zq,mode,extrapVal);
+ else
+ %Here we require a meshgrid to force octave to return the correct size
+ %Maybe the same thing could be achieved with a reshape?
+ [xqMesh,yqMesh,zqMesh] = meshgrid(xq,yq,zq);
+ y = interp3(xi,yi,zi,x,xqMesh,yqMesh,zqMesh,mode,extrapVal);
+ end
+ %Old implementation
end
diff --git a/matRad/util/matRad_plotParticleBaseDataEntry.m b/matRad/util/matRad_plotParticleBaseDataEntry.m
new file mode 100644
index 000000000..47ce1cfed
--- /dev/null
+++ b/matRad/util/matRad_plotParticleBaseDataEntry.m
@@ -0,0 +1,90 @@
+function matRad_plotParticleBaseDataEntry(machine,index,hFigure)
+%MATRAD_PLOTPARTICLEBASEDATAENTRY Summary of this function goes here
+% Detailed explanation goes here
+
+if (ischar(machine) || isstring(machine)) && isfile(machine)
+ load(machine);
+end
+
+if nargin < 3
+ hFigure = figure;
+end
+
+subplot(2,3,1);
+plot(machine.data(index).depths,machine.data(index).Z);
+xlabel('depth [mm]');
+ylabel('Z [MeV cm^2 /(g * primary)]');
+title(sprintf('Depth Dose for E = %g MeV',machine.data(index).energy));
+
+if isfield(machine.data(index),'sigma')
+ subplot(2,3,2);
+ plot(machine.data(index).depths,machine.data(index).sigma);
+ xlabel('depth [mm]');
+ ylabel('\sigma [mm]');
+ title(sprintf('\\sigma for E = %g MeV',machine.data(index).energy));
+end
+
+if isfield(machine.data(index),'sigma')
+ subplot(2,3,2);
+ plot(machine.data(index).depths,machine.data(index).sigma);
+ xlabel('depth [mm]');
+ ylabel('\sigma [mm]');
+ title(sprintf('\\sigma for E = %g MeV',machine.data(index).energy));
+end
+
+if isfield(machine.data(index),'sigma1')
+ subplot(2,3,2);
+ plot(machine.data(index).depths,machine.data(index).sigma1);
+ xlabel('depth [mm]');
+ ylabel('\sigma [mm]');
+ title(sprintf('\\sigma_1 for E = %g MeV',machine.data(index).energy));
+end
+
+if isfield(machine.data(index),'sigma2')
+ subplot(2,3,3);
+ plot(machine.data(index).depths,machine.data(index).sigma2);
+ xlabel('depth [mm]');
+ ylabel('\sigma [mm]');
+ title(sprintf('\\sigma_2 for E = %g MeV',machine.data(index).energy));
+end
+
+if isfield(machine.data(index),'LET')
+ subplot(2,3,4);
+ plot(machine.data(index).depths,machine.data(index).LET);
+ xlabel('depth [mm]');
+ ylabel('LET [keV/\mu m]');
+ title(sprintf('LET for E = %g MeV',machine.data(index).energy));
+end
+
+if isfield(machine.data(index),'alpha')
+ subplot(2,3,5);
+ nT = numel(machine.data(index).alphaBetaRatio);
+ legendNames = cell(1,nT);
+ for i =1:nT
+ plot(machine.data(index).depths,machine.data(index).alpha); hold on;
+ legendNames{i} = sprintf('[\\alpha_\\gamma = %g, \\beta_\\gamma = %g]',machine.data(index).alphaX(i),machine.data(index).betaX(i));
+ end
+ legend(legendNames);
+
+ xlabel('depth [mm]');
+ ylabel('\alpha [Gy^{-1}]');
+ title(sprintf('\\alpha for E = %g MeV',machine.data(index).energy));
+end
+
+if isfield(machine.data(index),'beta')
+ subplot(2,3,6);
+ nT = numel(machine.data(index).alphaBetaRatio);
+ legendNames = cell(1,nT);
+ for i =1:nT
+ plot(machine.data(index).depths,machine.data(index).beta); hold on;
+ legendNames{i} = sprintf('[\\alpha_\\gamma = %g, \\beta_\\gamma = %g]',machine.data(index).alphaX(i),machine.data(index).betaX(i));
+ end
+ legend(legendNames);
+
+ xlabel('depth [mm]');
+ ylabel('\beta [Gy^{-2}]');
+ title(sprintf('\\beta for E = %g MeV',machine.data(index).energy));
+end
+
+end
+
diff --git a/tools/matRad_plotSliceWrapper.m b/matRad/util/matRad_plotSliceWrapper.m
similarity index 89%
rename from tools/matRad_plotSliceWrapper.m
rename to matRad/util/matRad_plotSliceWrapper.m
index 698e84dd4..bcc7d9436 100644
--- a/tools/matRad_plotSliceWrapper.m
+++ b/matRad/util/matRad_plotSliceWrapper.m
@@ -56,7 +56,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -99,12 +99,18 @@
cst = [];
end
+matRad_cfg = MatRad_Config.instance();
+
set(axesHandle,'YDir','Reverse');
% plot ct slice
hCt = matRad_plotCtSlice(axesHandle,ct.cubeHU,cubeIdx,plane,slice);
hold on;
% plot dose
+if ~isempty(doseWindow) && doseWindow(2) - doseWindow(1) <= 0
+ doseWindow = [0 2];
+end
+
[hDose,doseColorMap,doseWindow] = matRad_plotDoseSlice(axesHandle,dose,plane,slice,thresh,alpha,doseColorMap,doseWindow);
% plot iso dose lines
@@ -117,20 +123,21 @@
%plot VOI contours
if ~isempty(cst)
- hContour = matRad_plotVoiContourSlice(axesHandle,cst,ct,cubeIdx,voiSelection,plane,slice,contourColorMap,varargin{:});
+
+ [hContour,~] = matRad_plotVoiContourSlice(axesHandle,cst,ct,cubeIdx,voiSelection,plane,slice,contourColorMap,varargin{:});
if boolPlotLegend
visibleOnSlice = (~cellfun(@isempty,hContour));
ixLegend = find(voiSelection);
hContourTmp = cellfun(@(X) X(1),hContour(visibleOnSlice),'UniformOutput',false);
if ~isempty(voiSelection)
- hLegend = legend(axesHandle,[hContourTmp{:}],[cst(ixLegend(visibleOnSlice),2)],'AutoUpdate','off');
+ hLegend = legend(axesHandle,[hContourTmp{:}],[cst(ixLegend(visibleOnSlice),2)],'AutoUpdate','off','TextColor',matRad_cfg.gui.textColor);
else
- hLegend = legend(axesHandle,[hContourTmp{:}],[cst(visibleOnSlice,2)],'AutoUpdate','off');
+ hLegend = legend(axesHandle,[hContourTmp{:}],[cst(visibleOnSlice,2)],'AutoUpdate','off','TextColor',matRad_cfg.gui.textColor);
end
set(hLegend,'Box','Off');
- set(hLegend,'TextColor',[1 1 1]);
- set(hLegend,'FontSize',12);
+ set(hLegend,'TextColor',matRad_cfg.gui.textColor);
+ set(hLegend,'FontSize',matRad_cfg.gui.fontSize);
end
else
@@ -160,8 +167,9 @@
end
hCMap = matRad_plotColorbar(axesHandle,doseColorMap,doseWindow,'Location','EastOutside');
+set(hCMap,'Color',matRad_cfg.gui.textColor);
if ~isempty(colorBarLabel)
- set(get(hCMap,'YLabel'),'String', colorBarLabel,'FontSize',14);
+ set(get(hCMap,'YLabel'),'String', colorBarLabel,'FontSize',matRad_cfg.gui.fontSize);
end
end
diff --git a/matRad_progress.m b/matRad/util/matRad_progress.m
similarity index 94%
rename from matRad_progress.m
rename to matRad/util/matRad_progress.m
index 5b9924c9e..9cc357a3b 100644
--- a/matRad_progress.m
+++ b/matRad/util/matRad_progress.m
@@ -21,7 +21,7 @@ function matRad_progress(currentIndex, totalNumberOfEvaluations)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/util/matRad_readCsvData.m b/matRad/util/matRad_readCsvData.m
new file mode 100644
index 000000000..30c2b5908
--- /dev/null
+++ b/matRad/util/matRad_readCsvData.m
@@ -0,0 +1,50 @@
+function dataOut = matRad_readCsvData(csvFile,cubeDim)
+% matRad read TOPAS csv data
+%
+% call
+% dataOut = matRad_readCsvData(csvFile,cubeDim)
+%
+% input
+% csvFile: TOPAS csv scoring file
+% cubeDim: size of cube
+%
+% output
+% dataOut: cube of size cubeDim containing scored values
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Read in csv file as table
+dataTable = readtable(csvFile,'ReadVariableNames',false);
+
+% this is the number of ReportQuantities contained in that file
+numOfReportQuantities = size(dataTable,2)-3;
+
+% Save all scored quantities as cell array and reshape to cubeDim
+dataOut = cell(1,numOfReportQuantities);
+
+%Get the indices and do i,j swap
+ixi = dataTable.Var2+1;
+ixj = dataTable.Var1+1;
+ixk = dataTable.Var3+1;
+ix = sub2ind(cubeDim,ixi,ixj,ixk);
+
+for i = 1:numOfReportQuantities
+ dataOut{i} = zeros(cubeDim);
+ dataOut{i}(ix) = dataTable.(['Var' num2str(i+3)]);
+end
+
+end
\ No newline at end of file
diff --git a/matRad/util/matRad_recursiveFieldAssignment.m b/matRad/util/matRad_recursiveFieldAssignment.m
new file mode 100644
index 000000000..eefed3ab6
--- /dev/null
+++ b/matRad/util/matRad_recursiveFieldAssignment.m
@@ -0,0 +1,102 @@
+function assigned = matRad_recursiveFieldAssignment(assignTo,reference,overwrite,fieldChangedWarningMessage,fieldname)
+% matRad recursive field assignment tool
+% This function recursively assigns fields from one structure to another. If both 'assignTo' and 'reference' are structures,
+% it will recurse into their fields. If a field in 'assignTo' is a structure and its corresponding field in 'reference' is not,
+% or vice versa, a warning message is displayed. The function also handles the case where 'assignTo' or 'reference' are not structures,
+% directly assigning the values. Custom warning messages can be specified for overwriting fields.
+%
+% call
+% assigned = matRad_recursiveFieldAssignment(assignTo,reference,fieldChangedWarningMessage,fieldname)
+%
+% input
+% assignTo: The initial structure to which the fields are to be assigned.
+% reference: The structure containing the fields and values to be assigned to 'assignTo'.
+% overwrite: Boolean flag that determines if the field value should be overwritten ( by defaults ) or preserved
+% fieldChangedWarningMessage: Optional. A message to display if a field is overwritten. If not provided, no message is displayed.
+% fieldname: Optional. The name of the current field being processed. Used for generating specific warning messages.
+%
+% output
+% assigned: The structure 'assignTo' after assigning the fields from 'reference'.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+if nargin < 4 || isempty(fieldChangedWarningMessage)
+ fieldChangedWarningMessage = '';
+else
+ if ~isempty(fieldChangedWarningMessage) && fieldChangedWarningMessage(end) ~= ':'
+ fieldChangedWarningMessage = [fieldChangedWarningMessage ':'];
+ end
+end
+
+if nargin < 5
+ fieldname = '';
+end
+
+% If the reference is a struct, we need to recurse into it
+if isstruct(assignTo) && isstruct(reference)
+ %First make sure the output has all the fields of the assignTo structure
+ assigned = assignTo;
+
+ %Now iterate over all fields provided in the reference structure
+ fields = fieldnames(reference);
+ for i = 1:numel(fields)
+ field = fields{i};
+ if ~isfield(assignTo,field)
+ assigned.(field) = reference.(field);
+ else
+ assigned.(field) = matRad_recursiveFieldAssignment(assignTo.(field),reference.(field),overwrite,fieldChangedWarningMessage,field);
+ end
+ end
+% If the reference is not a struct, we can assign it directly.
+% However, we need to check if the assignTo is a struct and if we need to warn the user of overwriting a struct with a non-struct
+elseif isstruct(assignTo) && ~isstruct(reference)
+ if overwrite
+ if ~isempty(fieldname)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning([fieldChangedWarningMessage 'Field ''%s'' is a struct but will be overwritten by a ''%s!'''],fieldname,class(reference));
+ end
+ assigned = reference;
+ else
+ assigned = assignTo;
+ end
+% If the assignTo is not a struct, we can assign it directly.
+% However, we need to check if the reference is a struct and if we need to warn the user of overwriting a non-struct with a struct
+elseif ~isstruct(assignTo) && isstruct(reference)
+
+ if overwrite
+ if ~isempty(fieldname)
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispWarning([fieldChangedWarningMessage 'Field ''%s'' is not a struct but will be overwritten by a struct!'],fieldname);
+ end
+ assigned = reference;
+ else
+ assigned = assignTo;
+ end
+else
+ if overwrite
+ if ~isempty(fieldname) && ~isempty(fieldChangedWarningMessage)
+ matRad_cfg = MatRad_Config.instance();
+ if ~isequal(class(assignTo),class(reference))
+ matRad_cfg.dispWarning([fieldChangedWarningMessage 'Field ''%s'' is supposed to be a %s but will be overwritten by a ''%s!'''],fieldname,class(assignTo),class(reference));
+ end
+ end
+ assigned = reference;
+ else
+ assigned = assignTo;
+ end
+
+end
+
+end
\ No newline at end of file
diff --git a/matRad/util/matRad_resampleCTtoGrid.m b/matRad/util/matRad_resampleCTtoGrid.m
new file mode 100644
index 000000000..b9ff932bc
--- /dev/null
+++ b/matRad/util/matRad_resampleCTtoGrid.m
@@ -0,0 +1,70 @@
+function [ctR] = matRad_resampleCTtoGrid(ct,dij)
+% function to resample the ct grid for example for faster MC computation
+%
+% call
+% [ctR] = matRad_resampleGrid(ct)
+%
+% input
+% ct: Path to folder where TOPAS files are in (as string)
+% cst: matRad segmentation struct
+%
+% output
+% ctR: resampled CT
+% cst: updated ct struct (due to calcDoseInit)
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2022 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class
+
+% Check if CT has already been resampled
+if ~isfield(ct,'resampled')
+ % Allpcate resampled cubes
+ cubeHUresampled = cell(1,ct.numOfCtScen);
+ cubeResampled = cell(1,ct.numOfCtScen);
+
+ % Perform resampling to dose grid
+ for s = 1:ct.numOfCtScen
+ cubeHUresampled{s} = matRad_interp3(dij.ctGrid.x, dij.ctGrid.y', dij.ctGrid.z,ct.cubeHU{s}, ...
+ dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'linear');
+ cubeResampled{s} = matRad_interp3(dij.ctGrid.x, dij.ctGrid.y', dij.ctGrid.z,ct.cube{s}, ...
+ dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'linear');
+ end
+
+ % Allocate temporary resampled CT
+ ctR = ct;
+ ctR.cube = cell(1);
+ ctR.cubeHU = cell(1);
+
+ % Set CT resolution to doseGrid resolution
+ ctR.resolution = dij.doseGrid.resolution;
+ ctR.cubeDim = dij.doseGrid.dimensions;
+ ctR.x = dij.doseGrid.x;
+ ctR.y = dij.doseGrid.y;
+ ctR.z = dij.doseGrid.z;
+
+ % Write resampled cubes
+ ctR.cubeHU = cubeHUresampled;
+ ctR.cube = cubeResampled;
+
+ % Set flag for complete resampling
+ ctR.resampled = 1;
+ ctR.ctGrid = dij.doseGrid;
+
+ % Save original grid
+ ctR.originalGrid = dij.ctGrid;
+else
+ ctR = ct;
+ matRad_cfg.dispInfo('CT already resampled.');
+end
+end
diff --git a/matRad_version.m b/matRad/util/matRad_version.m
similarity index 74%
rename from matRad_version.m
rename to matRad/util/matRad_version.m
index 91769f8b5..65fe2880f 100644
--- a/matRad_version.m
+++ b/matRad/util/matRad_version.m
@@ -1,12 +1,14 @@
-function [versionString,matRadVer] = matRad_version()
+function [versionString,matRadVer] = matRad_version(matRadRoot)
% matRad function to get the current matRad version
% (and git information when used from within a repository
%
% call
% [versionString,matRadVer] = matRad_version()
+% [versionString,matRadVer] = matRad_version(matRadRoot)
%
% input
-%
+% matRadRoot: Optional Root Directory. This is for call in matRad
+% initialization when MatRad_Config is not yet available
% output
% versionString: Readable string build from version information
% matRadVer: struct with version information
@@ -20,7 +22,7 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -38,8 +40,14 @@
%Retreive branch / commit information from current git repo if applicable
try
%read HEAD file to point to current ref / commit
- repoGitDir = [fileparts(mfilename('fullpath')) filesep '.git'];
- headText = fileread([repoGitDir filesep 'HEAD']);
+ if nargin == 1
+ repoDir = matRadRoot;
+ else
+ matRad_cfg = MatRad_Config.instance();
+ repoDir = matRad_cfg.matRadRoot;
+ end
+ repoGitDir = fullfile(repoDir,'.git');
+ headText = fileread(fullfile(repoGitDir,'HEAD'));
%Test if detached head (HEAD contains 40 hex SHA1 commit ID)
i = regexp(headText,'[a-f0-9]{40}', 'once');
@@ -50,7 +58,8 @@
headParse = textscan(headText,'%s');
refHead = headParse{1}{2};
refParse = strsplit(refHead,'/');
- matRadVer.branch = refParse{end};
+ refType = refParse{2};
+ matRadVer.branch = strjoin(refParse(3:end),'/');
%Read ID from ref path
refID = fileread([repoGitDir filesep strjoin(refParse,filesep)]);
@@ -69,7 +78,6 @@
end
end
%}
-
end
catch
@@ -82,11 +90,11 @@
%Git path first
gitString = '';
if ~isempty(matRadVer.branch) && ~isempty(matRadVer.commitID) && ~tagged
- gitString = sprintf(' (%s-%s)',matRadVer.branch,matRadVer.commitID(1:8));
+ gitString = sprintf('(%s-%s)',matRadVer.branch,matRadVer.commitID(1:8));
end
%Full string
-versionString = sprintf('v%d.%d.%d "%s"%s',matRadVer.major,matRadVer.minor,matRadVer.patch,matRadVer.name,gitString);
+versionString = sprintf('"%s" v%d.%d.%d%s',matRadVer.name,matRadVer.major,matRadVer.minor,matRadVer.patch,gitString);
%This checks if no explicit assigment is done in which case the version is printed.
if nargout == 0
diff --git a/matRad_visApertureInfo.m b/matRad/util/matRad_visApertureInfo.m
similarity index 98%
rename from matRad_visApertureInfo.m
rename to matRad/util/matRad_visApertureInfo.m
index 93981eb01..2647d728a 100644
--- a/matRad_visApertureInfo.m
+++ b/matRad/util/matRad_visApertureInfo.m
@@ -21,7 +21,7 @@ function matRad_visApertureInfo(apertureInfo,mode)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/tools/matRad_visPhotonFieldShapes.m b/matRad/util/matRad_visPhotonFieldShapes.m
similarity index 93%
rename from tools/matRad_visPhotonFieldShapes.m
rename to matRad/util/matRad_visPhotonFieldShapes.m
index 230bc22c0..669f09bca 100644
--- a/tools/matRad_visPhotonFieldShapes.m
+++ b/matRad/util/matRad_visPhotonFieldShapes.m
@@ -22,7 +22,7 @@ function matRad_visPhotonFieldShapes(pln)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -52,7 +52,7 @@ function matRad_visPhotonFieldShapes(pln)
xlabel(fieldShapeAxisHandle, '[mm]')
ylabel(fieldShapeAxisHandle, '[mm]')
- title(fieldShapeAxisHandle, ['field shape i: ' num2str(i) ' @ gantry = ' num2str(pln.propStf.collimation.Fields(i).GantryAngle) '� and couch = ' num2str(pln.propStf.collimation.Fields(i).CouchAngle) '�'])
+ title(fieldShapeAxisHandle, ['field shape i: ' num2str(i) ' @ gantry = ' num2str(pln.propStf.collimation.Fields(i).GantryAngle) '� and couch = ' num2str(pln.propStf.collimation.Fields(i).CouchAngle) '�'])
axis(fieldShapeAxisHandle, 'equal', 'tight')
drawnow
diff --git a/tools/matRad_visSpotWeights.m b/matRad/util/matRad_visSpotWeights.m
similarity index 98%
rename from tools/matRad_visSpotWeights.m
rename to matRad/util/matRad_visSpotWeights.m
index 62689f06e..0a8b12e83 100644
--- a/tools/matRad_visSpotWeights.m
+++ b/matRad/util/matRad_visSpotWeights.m
@@ -21,7 +21,7 @@ function matRad_visSpotWeights(stf,weights)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
diff --git a/matRad/util/matRad_weightedQuantile.m b/matRad/util/matRad_weightedQuantile.m
new file mode 100644
index 000000000..5dcb7ad6d
--- /dev/null
+++ b/matRad/util/matRad_weightedQuantile.m
@@ -0,0 +1,67 @@
+function wQ = matRad_weightedQuantile(values, percentiles, weight, isSorted, extraPolMethod)
+% matRad uncertainty analysis report generaator function
+%
+% call
+% matRad_weightedQuantile(values, percentiles, weight, isSorted, extraPol)
+%
+% input
+% values: random variable vector
+% percentiles: percentiles to be calculated
+% weight: (optional) weight vector (same length as values)
+% isSorted: (optional) bool: are the values sorted alreay?
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ % check input arguments
+ if ~exist('sortedB', 'var') || isempty(isSorted)
+ isSorted = false;
+ end
+ if ~exist('weight', 'var') || isempty(weight)
+ weight = ones(size(values));
+ end
+ if ~(percentiles(:) >= 0 & percentiles(:) <= 1)
+ error('Quantiles must not be outside [0, 1]');
+ end
+ if ~exist('extraPolMethod', 'var') || isempty(extraPolMethod)
+ extraPolMethod = NaN;
+ end
+
+
+
+ % sort values
+ if ~isSorted
+ [values, sortIx] = sort(values);
+ weight = weight(sortIx);
+ end
+
+ wQtemp = cumsum(weight) - 0.5 * weight;
+ wQtemp = wQtemp ./ sum(weight);
+
+ wQ = NaN * ones(size(values,1), 2);
+
+ [x,ia] = unique(wQtemp);
+
+ V = values(ia);
+
+ if ~iscolumn(x)
+ x = x';
+ end
+ if ~iscolumn(V)
+ V = V';
+ end
+
+ wQ = matRad_interp1(x,V,percentiles,extraPolMethod);
+
+end % eof
diff --git a/matRad/util/octaveCompat/matRad_getPropsCompat.m b/matRad/util/octaveCompat/matRad_getPropsCompat.m
new file mode 100644
index 000000000..7e18e6279
--- /dev/null
+++ b/matRad/util/octaveCompat/matRad_getPropsCompat.m
@@ -0,0 +1,34 @@
+function p = matRad_getPropsCompat(obj)
+% matRad function mimicking Matlab's properties for compatibility with
+% Octave 6 in classdef files (avoids a parse error in the file)
+%
+% call
+% p = matRad_getPropsCompat(obj)
+%
+% input
+% obj object (classdef) to get properties from
+%
+% output
+% p properties of the object
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+try
+ p = properties(obj);
+catch ME
+ p = [];
+end
+end
\ No newline at end of file
diff --git a/matRad/util/octaveCompat/matRad_ispropCompat.m b/matRad/util/octaveCompat/matRad_ispropCompat.m
new file mode 100644
index 000000000..4d52dccc8
--- /dev/null
+++ b/matRad/util/octaveCompat/matRad_ispropCompat.m
@@ -0,0 +1,39 @@
+function result = matRad_ispropCompat(obj,prop)
+% matRad function mimicking Matlab's properties for compatibility with
+% Octave 6 in classdef files (avoids a parse error in the file)
+%
+% call
+% result = matRad_ispropCompat(obj)
+%
+% input
+% obj object (classdef) to check for property
+% prop property to check for
+%
+% output
+% result true if property exists, false otherwise
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2024 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = MatRad_Config.instance();
+
+if matRad_cfg.isOctave && str2double(matRad_cfg.envVersion(1)) <= 6 && ~ishghandle(obj)
+ result = any(strcmp(matRad_getPropsCompat(obj),prop));
+else
+ result = isprop(obj,prop);
+end
+
+end
\ No newline at end of file
diff --git a/matRadGUI.fig b/matRadGUI.fig
deleted file mode 100644
index 72daf0d19..000000000
Binary files a/matRadGUI.fig and /dev/null differ
diff --git a/matRadGUI.m b/matRadGUI.m
index 9061d4516..cb25e7f82 100644
--- a/matRadGUI.m
+++ b/matRadGUI.m
@@ -1,406 +1,35 @@
-function varargout = matRadGUI(varargin)
-% matRad GUI
+function hGUI = matRadGUI(varargin)
+% matRad compatability function to call the matRad_MainGUI
+% The function checks input parameters and handles the GUI as a
+% singleton, so following calls will not create new windows
%
% call
-% MATRADGUI, by itself, creates a new MATRADGUI or raises the existing
-% singleton*.
+% hGUI = matRadGUI
+% matRadGUI
%
-% H = MATRADGUI returns the handle to a new MATRADGUI or the handle to
-% the existing singleton*.
-%
-% MATRADGUI('CALLBACK',hObject,eventData,handles,...) calls the local
-% function named CALLBACK in MATRADGUI.M with the given input arguments.
-%
-% MATRADGUI('Property','Value',...) creates a new MATRADGUI or raises the
-% existing singleton*. Starting from the left, property value pairs are
-% applied to the GUI before matRadGUI_OpeningFcn gets called. An
-% unrecognized property name or invalid value makes property application
-% stop. All inputs are passed to matRadGUI_OpeningFcn via varargin.
-%
-% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
-% instance to run (singleton)".
-%
-% See also: GUIDE, GUIDATA, GUIHANDLES
+% References
+% -
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-matRad_cfg = MatRad_Config.instance();
-
-if matRad_cfg.disableGUI
- matRad_cfg.dispInfo('matRad GUI disabled in matRad_cfg!\n');
- return;
-end
-
-if ~isdeployed
- matRadRootDir = fileparts(mfilename('fullpath'));
- addpath(genpath(matRadRootDir));
-end
-
-[env, versionString] = matRad_getEnvironment();
-
-% abort for octave
-switch env
- case 'MATLAB'
-
- case 'OCTAVE'
- matRad_cfg.dispInfo(['matRad GUI not available for ' env ' ' versionString ' \n']);
- return;
- otherwise
- matRad_cfg.dispInfo('not yet tested');
- end
-
-% Begin initialization code - DO NOT EDIT
-% set platform specific look and feel
-if ispc
- lf = 'com.sun.java.swing.plaf.windows.WindowsLookAndFeel';
-elseif isunix
- lf = 'com.jgoodies.looks.plastic.Plastic3DLookAndFeel';
-elseif ismac
- lf = 'com.apple.laf.AquaLookAndFeel';
-end
-javax.swing.UIManager.setLookAndFeel(lf);
-
-gui_Singleton = 1;
-gui_State = struct('gui_Name', mfilename, ...
- 'gui_Singleton', gui_Singleton, ...
- 'gui_OpeningFcn', @matRadGUI_OpeningFcn, ...
- 'gui_OutputFcn', @matRadGUI_OutputFcn, ...
- 'gui_LayoutFcn', [] , ...
- 'gui_Callback', []);
-if nargin && ischar(varargin{1})
- gui_State.gui_Callback = str2func(varargin{1});
-end
-
-if nargout
- [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
-else
- gui_mainfcn(gui_State, varargin{:});
-end
-
-% End initialization code - DO NOT EDIT
-
-function handles = resetGUI(hObject, handles, varargin)
-% enable opengl software rendering to visualize linewidths properly
-if ispc
- opengl software
-elseif ismac
- % opengl is not supported
-end
-
-% Choose default command line output for matRadGUI
-handles.output = hObject;
-%show matrad logo
-axes(handles.axesLogo)
-[im, ~, alpha] = imread('matrad_logo.png');
-f = image(im);
-axis equal off
-set(f, 'AlphaData', alpha);
-% show dkfz logo
-axes(handles.axesDKFZ)
-[im, ~, alpha] = imread('DKFZ_Logo.png');
-f = image(im);
-axis equal off;
-set(f, 'AlphaData', alpha);
-
-% turn off the datacursormode (since it does not allow to set scrolling
-% callbacks
-handles.dcm_obj = datacursormode(handles.figure1);
-set(handles.dcm_obj,'DisplayStyle','window');
-if strcmpi(get(handles.dcm_obj,'Enable'),'on')
- set(handles.dcm_obj,'Enable','off');
-end
-%Add the callback for the datacursor display
-set(handles.dcm_obj,'UpdateFcn',@dataCursorUpdateFunction);
-
-% set callback for scroll wheel function
-set(gcf,'WindowScrollWheelFcn',@matRadScrollWheelFcn);
-
-% change color of toobar but only the first time GUI is started
-if handles.initialGuiStart
- hToolbar = findall(hObject,'tag','uitoolbar1');
- jToolbar = get(get(hToolbar,'JavaContainer'),'ComponentPeer');
- jToolbar.setBorderPainted(false);
- color = java.awt.Color.gray;
- % Remove the toolbar border, to blend into figure contents
- jToolbar.setBackground(color);
- % Remove the separator line between toolbar and contents
- warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame');
- jFrame = get(handle(hObject),'JavaFrame');
- jFrame.showTopSeparator(false);
- jtbc = jToolbar.getComponents;
- for idx=1:length(jtbc)
- jtbc(idx).setOpaque(false);
- jtbc(idx).setBackground(color);
- for childIdx = 1 : length(jtbc(idx).getComponents)
- jtbc(idx).getComponent(childIdx-1).setBackground(color);
- end
- end
-end
-
-
-set(handles.legendTable,'String',{'no data loaded'});
-% clear VOIPlotFlag
-if isfield(handles,'VOIPlotFlag')
- handles = rmfield(handles,'VOIPlotFlag');
-end
-
-%seach for availabes machines
-handles.Modalities = {'photons','protons','carbon'};
-for i = 1:length(handles.Modalities)
- pattern = [handles.Modalities{1,i} '_*'];
- if isdeployed
- baseroot = [ctfroot filesep 'matRad'];
- else
- baseroot = fileparts(mfilename('fullpath'));
- end
- Files = dir([baseroot filesep 'basedata' filesep pattern]);
-
- for j = 1:length(Files)
- if ~isempty(Files)
- MachineName = Files(j).name(numel(handles.Modalities{1,i})+2:end-4);
- if isfield(handles,'Machines')
- if sum(strcmp(handles.Machines,MachineName)) == 0
- handles.Machines{size(handles.Machines,2)+1} = MachineName;
- end
- else
- handles.Machines = cell(1);
- handles.Machines{1} = MachineName;
- end
- end
- end
-end
-set(handles.popUpMachine,'String',handles.Machines);
-
-
-vChar = get(handles.editGantryAngle,'String');
-if strcmp(vChar(1,1),'0') && length(vChar)==6
- set(handles.editGantryAngle,'String','0');
-end
-vChar = get(handles.editCouchAngle,'String');
-if strcmp(vChar(1,1),'0') && length(vChar)==3
- set(handles.editCouchAngle,'String','0')
-end
-%%
-% handles.State=0 no data available
-% handles.State=1 ct cst and pln available; ready for dose calculation
-% handles.State=2 ct cst and pln available and dij matric(s) are calculated;
-% ready for optimization
-% handles.State=3 plan is optimized
-
-
-% if plan is changed go back to state 1
-% if table VOI Type or Priorities changed go to state 1
-% if objective parameters changed go back to state 2
-handles.CutOffLevel = 0.01; % relative cut off level for dose vis
-handles.IsoDose.NewIsoDoseFlag = false;
-handles.TableChanged = false;
-handles.State = 0;
-handles.doseOpacity = 0.6;
-handles.IsoDose.Levels = 0;
-handles.dispWindow = cell(3,2); % first dimension refers to the selected
-
-% do not calculate / suggest isoCenter new by default
-set(handles.checkIsoCenter, 'Value', 0);
-set(handles.editIsoCenter,'Enable','on')
-
-% suppose no ct cube in HU is available (because no ct could be available)
-handles.cubeHUavailable = false;
-% initial startup finished
-handles.initialGuiStart = false;
-guidata(hObject, handles);
-% eof resetGUI
-
-
-% --- Initializes the slice slider for the current ct & isocenter (or sets
-% it to default)
-function initViewSliceSlider(handles)
-% handles structure with handles and user data (see GUIDATA)
-
-if handles.State > 0
- ct = evalin('base', 'ct');
-
- %Helpers for managing the resolution and Matlab xy permutation
- planePermute = [2 1 3];
- ctRes = [ct.resolution.x ct.resolution.y ct.resolution.z];
- planePermIx = planePermute(handles.plane);
- planeDim = ct.cubeDim(handles.plane);
-
- %Try to select the slice from defined isocenter
- try
- if evalin('base','exist(''pln'',''var'')')
- currPln = evalin('base','pln');
-
- if sum(currPln.propStf.isoCenter(:)) ~= 0
- %currSlice = round((currPln.propStf.isoCenter(1,planePermIx)+ctRes(planePermix)/2)/ctRes(planePermIx))-1;
- currSlice = round(currPln.propStf.isoCenter(1,planePermIx) / ctRes(planePermIx));
- else
- currSlice = 0;
- end
- end
- catch
- currSlice = 0; %Set to zero for next if
- end
-
- %If no isocenter or wrong value, choose central slice
- if currSlice < 1 || currSlice > planeDim
- currSlice = ceil(planeDim/2);
- end
-
- set(handles.sliderSlice,'Min',1,'Max',planeDim,...
- 'Value',currSlice,...
- 'SliderStep',[1/(planeDim-1) 1/(planeDim-1)]);
-else
- % reset slider when nothing is loaded
- set(handles.sliderSlice,'Min',0,'Max',1,'Value',0,'SliderStep',[1 1]);
-end
-
-
-
-function handles = reloadGUI(hObject, handles, ct, cst)
-AllVarNames = handles.AllVarNames;
-
-if nargin >=3 && ismember('ct',AllVarNames)
- % compute HU values
- if ~isfield(ct, 'cubeHU')
- ct = matRad_electronDensitiesToHU(ct);
- assignin('base','ct',ct);
- end
- if ~isfield(ct, 'cubeHU')
- handles.cubeHUavailable = false;
- else
- handles.cubeHUavailable = true;
- end
-end
-
-%set plan if available - if not create one
-try
- if ismember('pln',AllVarNames) && handles.State > 0
- % check if you are working with a valid pln
- pln = evalin('base','pln');
- if ~isfield(pln,'propStf')
- handles = showWarning(handles,'GUI OpeningFunc: Overwriting outdated pln format with default GUI pln');
- evalin('base','clear pln');
- getPlnFromGUI(handles);
- end
- setPln(handles);
- elseif handles.State > 0
- getPlnFromGUI(handles);
- setPln(handles);
- end
-
-catch
- handles.State = 0;
- handles = showError(handles,'GUI OpeningFunc: Could not set or get pln');
-end
-
-% check for dij structure
-if ismember('dij',AllVarNames)
- handles.State = 2;
-end
-
-% check for optimized results
-if ismember('resultGUI',AllVarNames)
- handles.State = 3;
-end
-
-% set some default values
-if handles.State == 2 || handles.State == 3
- set(handles.popupDisplayOption,'String','physicalDose');
- handles.SelectedDisplayOption ='physicalDose';
- handles.SelectedDisplayOptionIdx=1;
-else
- handles.resultGUI = [];
- set(handles.popupDisplayOption,'String','no option available');
- handles.SelectedDisplayOption='';
- handles.SelectedDisplayOptionIdx=1;
-end
-
-% precompute iso dose lines
-if handles.State == 3
- handles = updateIsoDoseLineCache(handles);
-end
-
-%per default the first beam is selected for the profile plot
-handles.selectedBeam = 1;
-handles.plane = get(handles.popupPlane,'Value');
-handles.DijCalcWarning = false;
-
-% set slice slider
-initViewSliceSlider(handles);
-
-% define context menu for structures
-if handles.State > 0
- for i = 1:size(cst,1)
- if cst{i,5}.Visible
- handles.VOIPlotFlag(i) = true;
- else
- handles.VOIPlotFlag(i) = false;
- end
- end
-end
-
-%Initialize colormaps and windows
-handles.doseColorMap = 'jet';
-handles.ctColorMap = 'bone';
-handles.cMapSize = 64;
-handles.cBarChanged = true;
-
-%Set up the colormap selection box
-availableColormaps = matRad_getColormap();
-set(handles.popupmenu_chooseColormap,'String',availableColormaps);
-
-currentCtMapIndex = find(strcmp(availableColormaps,handles.ctColorMap));
-currentDoseMapIndex = find(strcmp(availableColormaps,handles.doseColorMap));
-
-if handles.State >= 1
- set(handles.popupmenu_chooseColormap,'Value',currentCtMapIndex);
-end
-
-if handles.State >= 3
- set(handles.popupmenu_chooseColormap,'Value',currentDoseMapIndex);
-end
-
-% Update handles structure
-handles.profileOffset = 0;
-UpdateState(handles)
-
-axes(handles.axesFig)
-handles.rememberCurrAxes = false;
-UpdatePlot(handles)
-handles.rememberCurrAxes = true;
-guidata(hObject, handles);
-% eof reloadGUI
+persistent hMatRadGUI;
-% --- Executes just before matRadGUI is made visible.
-function matRadGUI_OpeningFcn(hObject, ~, handles, varargin)
-%#ok<*DEFNU>
-%#ok<*AGROW>
-% This function has no output args, see OutputFcn.
-% hObject handle to figure
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-% varargin command line arguments to matRadGUI (see VARARGIN)
-
-% variable to check whether GUI is opened or just refreshed / new data
-% loaded, since resetGUI needs to distinguish at one point
-
-handles.initialGuiStart = true;
p = inputParser;
-addParameter(p,'devMode',false,@validateModeValue);
-addParameter(p,'eduMode',false,@validateModeValue);
+addParameter(p,'devMode',false,@(x) validateModeValue(x));
+addParameter(p,'eduMode',false,@(x) validateModeValue(x));
p.KeepUnmatched = true; %No error with incorrect parameters
parse(p,varargin{:});
@@ -414,3867 +43,59 @@ function matRadGUI_OpeningFcn(hObject, ~, handles, varargin)
parsedInput.eduMode = str2double(parsedInput.eduMode);
end
+matRad_cfg = matRad_rc;
+
%If devMode is true, error dialogs will include the full stack trace of the error
%If false, only the basic error message is shown (works for errors that
%handle the MException object)
-handles.devMode = logical(parsedInput.devMode);
-if handles.devMode
+matRad_cfg.devMode = logical(parsedInput.devMode);
+if matRad_cfg.devMode
disp('matRadGUI starting in developer mode!');
end
-%Enables simple educational mode which removes certain functionality from
+%Enables simple educational mode which removes certain functionality from
%the GUI
-handles.eduMode = logical(parsedInput.eduMode);
-if handles.eduMode
+matRad_cfg.eduMode = logical(parsedInput.eduMode);
+if matRad_cfg.eduMode
disp('matRadGUI starting in educational mode!');
end
-
-set(handles.radiobtnPlan,'value',0);
-
-
-
-if handles.eduMode
- %Visisbility in Educational Mode
- eduHideHandles = {handles.radiobutton3Dconf,...
- handles.btnRunDAO,...
- handles.pushbutton_importFromBinary,...
- handles.btnLoadDicom,...
- handles.btn_export,...
- handles.importDoseButton};
- eduDisableHandles = {handles.editCouchAngle,handles.popUpMachine};
- cellfun(@(h) set(h,'Visible','Off'),eduHideHandles);
- cellfun(@(h) set(h,'Enable','Off'),eduDisableHandles);
-end
-
-
-%Alter matRad Version string positioning
-vString = matRad_version();
-vPos = get(handles.text15,'Position');
-urlPos = get(handles.text31,'Position');
-btnPos = get(handles.btnAbout,'Position');
-
-%vPos([1 3]) = urlPos([1 3]);
-vPos([1 3]) = [0 1];
-vPos(4) = vPos(4)*1.25;
-btnPos(2) = 0.05;
-urlPos(2) = btnPos(2)+btnPos(4)+0.05;
-vPos(2) = urlPos(2) + urlPos(4) + 0.05;
-vPos(4) = 0.98 - vPos(2);
-
-set(handles.btnAbout,'Position',btnPos);
-set(handles.text31,'String','www.matRad.org','Position',urlPos,'Enable','inactive','ButtonDownFcn', @(~,~) web('www.matrad.org','-browser'));
-set(handles.text15,'String',vString,'Position',vPos);
-
-handles = resetGUI(hObject, handles);
-
-%% parse variables from base workspace
-AllVarNames = evalin('base','who');
-handles.AllVarNames = AllVarNames;
-try
- if ismember('ct',AllVarNames) && ismember('cst',AllVarNames)
- ct = evalin('base','ct');
- cst = evalin('base','cst');
- %cst = setCstTable(handles,cst);
- cst = generateCstTable(handles,cst);
- handles.State = 1;
- cst = matRad_computeVoiContoursWrapper(cst,ct);
- assignin('base','cst',cst);
-
- elseif ismember('ct',AllVarNames) && ~ismember('cst',AllVarNames)
- handles = showError(handles,'GUI OpeningFunc: could not find cst file');
- elseif ~ismember('ct',AllVarNames) && ismember('cst',AllVarNames)
- handles = showError(handles,'GUI OpeningFunc: could not find ct file');
- end
-catch
- handles = showError(handles,'GUI OpeningFunc: Could not load ct and cst file');
-end
-
-if ismember('ct',AllVarNames) && ismember('cst',AllVarNames)
- handles = reloadGUI(hObject, handles, ct, cst);
-else
- handles = reloadGUI(hObject, handles);
-end
-
-guidata(hObject, handles);
-
-%Validates the attributes for the command line Modes
-function validateModeValue(x)
-%When passed from OS terminal (or inline in Command Window) everything is a string
-if isdeployed || ischar(x) || isstring(x)
- x=str2double(x);
-end
-validateattributes(x,{'logical','numeric'},{'scalar','binary'});
-
-
-
-function Callback_StructVisibilty(source,~)
-
-handles = guidata(findobj('Name','matRadGUI'));
-
-contextUiChildren = get(get(handles.figure1,'UIContextMenu'),'Children');
-
-Idx = find(strcmp(get(contextUiChildren,'Label'),get(source,'Label')));
-if strcmp(get(source,'Checked'),'on')
- set(contextUiChildren(Idx),'Checked','off');
-else
- set(contextUiChildren(Idx),'Checked','on');
-end
-%guidata(findobj('Name','matRadGUI'), handles);
-UpdatePlot(handles);
-
-% --- Outputs from this function are returned to the command line.
-function varargout = matRadGUI_OutputFcn(~, ~, handles)
-% varargout cell array for returning output args (see VARARGOUT);
-% hObject handle to figure
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Get default command line output from handles structure
-varargout{1} = handles.output;
-
-% set focus on error dialog
-if isfield(handles,'ErrorDlg')
- figure(handles.ErrorDlg)
-end
-
-% --- Executes on button press in btnLoadMat.
-function btnLoadMat_Callback(hObject, ~, handles)
-% hObject handle to btnLoadMat (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-[FileName, FilePath] = uigetfile('*.mat');
-if FileName == 0 % user pressed cancel --> do nothing.
+if matRad_cfg.disableGUI
+ matRad_cfg.dispInfo('matRad GUI disabled in matRad_cfg!\n');
return;
end
-handles = resetGUI(hObject, handles);
-
-try
-
- % delete existing workspace - parse variables from base workspace
- AllVarNames = evalin('base','who');
- RefVarNames = {'ct','cst','pln','stf','dij','resultGUI'};
-
- for i = 1:length(RefVarNames)
- if sum(ismember(AllVarNames,RefVarNames{i}))>0
- evalin('base',['clear ', RefVarNames{i}]);
- end
- end
-
- % read new data
- load([FilePath FileName]);
- set(handles.legendTable,'String',{'no data loaded'});
- set(handles.popupDisplayOption,'String','no option available');
-
-catch ME
- handles = showError(handles,'LoadMatFileFnc: Could not load *.mat file',ME);
-
- guidata(hObject,handles);
- UpdateState(handles);
- UpdatePlot(handles);
- return
-end
-
-try
- generateCstTable(handles,cst);
- handles.TableChanged = false;
- set(handles.popupTypeOfPlot,'Value',1);
- cst = matRad_computeVoiContoursWrapper(cst,ct);
-
- assignin('base','ct',ct);
- assignin('base','cst',cst);
- handles.State = 1;
-catch ME
- handles = showError(handles,'LoadMatFileFnc: Could not load *.mat file',ME);
-end
-
-% check if a optimized plan was loaded
-if exist('stf','var')
- assignin('base','stf',stf);
-end
-if exist('pln','var')
- assignin('base','pln',pln);
-end
-if exist('dij','var')
- assignin('base','dij',dij);
-end
-% if exist('stf','var') && exist('dij','var')
-% handles.State = 2;
-% end
-
-if exist('resultGUI','var')
- assignin('base','resultGUI',resultGUI);
- % handles.State = 3;
- % handles.SelectedDisplayOption ='physicalDose';
-end
-
-% recheck current workspace variables
-AllVarNames = evalin('base','who');
-handles.AllVarNames = AllVarNames;
-
-if ismember('ct',AllVarNames) && ismember('cst',AllVarNames)
- handles = reloadGUI(hObject, handles, ct, cst);
-else
- handles = reloadGUI(hObject, handles);
-end
-
-guidata(hObject,handles);
+handleValid = true;
-% --- Executes on button press in btnLoadDicom.
-function btnLoadDicom_Callback(hObject, ~, handles)
-% hObject handle to btnLoadDicom (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
try
- % delete existing workspace - parse variables from base workspace
- set(handles.popupDisplayOption,'String','no option available');
- AllVarNames = evalin('base','who');
- RefVarNames = {'ct','cst','pln','stf','dij','resultGUI'};
- for i = 1:length(RefVarNames)
- if sum(ismember(AllVarNames,RefVarNames{i}))>0
- evalin('base',['clear ', RefVarNames{i}]);
- end
- end
- handles.State = 0;
- matRad_importDicomGUI;
-
+ handleValid = ishandle(hMatRadGUI.guiHandle);
catch
- handles = showError(handles,'DicomImport: Could not import data');
-end
-UpdateState(handles);
-guidata(hObject,handles);
-
-
-% --- Executes on button press in btn_export.
-function btn_export_Callback(hObject, eventdata, handles)
-% hObject handle to btn_export (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-try
- matRad_exportGUI;
-catch
- handles = showError(handles,'Could not export data');
-end
-UpdateState(handles);
-guidata(hObject,handles);
-
-function editBixelWidth_Callback(hObject, ~, handles)
-% hObject handle to editBixelWidth (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of editBixelWidth as text
-% str2double(get(hObject,'String')) returns contents of editBixelWidth as a double
-getPlnFromGUI(handles);
-if handles.State > 0
- handles.State = 1;
- UpdateState(handles);
- guidata(hObject,handles);
-end
-
-function editGantryAngle_Callback(hObject, ~, handles)
-% hObject handle to editGantryAngle (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of editGantryAngle as text
-% str2double(get(hObject,'String')) returns contents of editGantryAngle as a double
-getPlnFromGUI(handles);
-if handles.State > 0
- handles.State = 1;
- UpdateState(handles);
- UpdatePlot(handles);
- guidata(hObject,handles);
-end
-
-function editCouchAngle_Callback(hObject, ~, handles)
-% hObject handle to editCouchAngle (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of editCouchAngle as text
-% str2double(get(hObject,'String')) returns contents of editCouchAngle as a double
-getPlnFromGUI(handles);
-if handles.State > 0
- handles.State = 1;
- UpdateState(handles);
- guidata(hObject,handles);
-end
-
-% --- Executes on selection change in popupRadMode.
-function popupRadMode_Callback(hObject, eventdata, handles)
-% hObject handle to popupRadMode (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-checkRadiationComposition(handles);
-contents = cellstr(get(hObject,'String'));
-RadIdentifier = contents{get(hObject,'Value')};
-contentPopUp = get(handles.popMenuBioOpt,'String');
-switch RadIdentifier
- case 'photons'
-
- set(handles.popMenuBioOpt,'Enable','off');
- ix = find(strcmp(contentPopUp,'none'));
- set(handles.popMenuBioOpt,'Value',ix);
- set(handles.btnSetTissue,'Enable','off');
-
- set(handles.btnRunSequencing,'Enable','on');
- set(handles.btnRunDAO,'Enable','on');
- set(handles.radiobutton3Dconf,'Enable','on');
- set(handles.txtSequencing,'Enable','on');
- set(handles.editSequencingLevel,'Enable','on');
-
- case 'protons'
-
- set(handles.popMenuBioOpt,'Enable','on');
- ix = find(strcmp(contentPopUp,'const_RBExD'));
- set(handles.popMenuBioOpt,'Value',ix);
- set(handles.btnSetTissue,'Enable','on');
-
- set(handles.btnSetTissue,'Enable','off');
- set(handles.btnRunSequencing,'Enable','off');
- set(handles.btnRunDAO,'Enable','off');
- set(handles.radiobutton3Dconf,'Enable','off');
- set(handles.txtSequencing,'Enable','off');
- set(handles.editSequencingLevel,'Enable','off');
-
- case 'carbon'
-
- set(handles.popMenuBioOpt,'Enable','on');
- ix = find(strcmp(contentPopUp,'LEMIV_RBExD'));
- set(handles.popMenuBioOpt,'Value',ix);
- set(handles.btnSetTissue,'Enable','on');
-
- set(handles.btnRunSequencing,'Enable','off');
- set(handles.btnRunDAO,'Enable','off');
- set(handles.radiobutton3Dconf,'Enable','off');
- set(handles.txtSequencing,'Enable','off');
- set(handles.editSequencingLevel,'Enable','off');
-end
-
-if handles.State > 0
- pln = evalin('base','pln');
- if handles.State > 0 && ~strcmp(contents(get(hObject,'Value')),pln.radiationMode)
-
- % new radiation modality is photons -> just keep physicalDose
- if strcmp(contents(get(hObject,'Value')),'photons')
- try
- AllVarNames = evalin('base','who');
- if ismember('resultGUI',AllVarNames)
- resultGUI = evalin('base','resultGUI');
- if isfield(resultGUI,'alpha'); resultGUI = rmfield(resultGUI,'alpha'); end
- if isfield(resultGUI,'beta'); resultGUI = rmfield(resultGUI,'beta'); end
- if isfield(resultGUI,'RBExDose'); resultGUI = rmfield(resultGUI,'RBExDose');end
- if isfield(resultGUI,'RBE'); resultGUI = rmfield(resultGUI,'RBE'); end
- assignin('base','resultGUI',resultGUI);
- handles = updateIsoDoseLineCache(handles);
- end
- catch
- end
- elseif strcmp(contents(get(hObject,'Value')),'protons')
- try
- AllVarNames = evalin('base','who');
- if ismember('resultGUI',AllVarNames)
- resultGUI = evalin('base','resultGUI');
- if isfield(resultGUI,'alpha'); resultGUI = rmfield(resultGUI,'alpha');end
- if isfield(resultGUI,'beta'); resultGUI = rmfield(resultGUI,'beta'); end
- if isfield(resultGUI,'RBE'); resultGUI = rmfield(resultGUI,'RBE'); end
- assignin('base','resultGUI',resultGUI);
- handles = updateIsoDoseLineCache(handles);
- end
- catch
- end
- end
-
- guidata(hObject,handles);
- UpdatePlot(handles);
-
- getPlnFromGUI(handles);
- handles.State = 1;
- UpdateState(handles);
-
- end
-
-guidata(hObject,handles);
-
-end
-
-
-% --- Executes on button press in btnCalcDose.
-function btnCalcDose_Callback(hObject, ~, handles)
-% hObject handle to btnCalcDose (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% http://stackoverflow.com/questions/24703962/trigger-celleditcallback-before-button-callback
-% http://www.mathworks.com/matlabcentral/newsreader/view_thread/332613
-% wait some time until the CallEditCallback is finished
-% Callback triggers the cst saving mechanism from GUI
-try
- % indicate that matRad is busy
- % change mouse pointer to hour glass
- Figures = gcf;%findobj('type','figure');
- set(Figures, 'pointer', 'watch');
- drawnow;
- % disable all active objects
- InterfaceObj = findobj(Figures,'Enable','on');
- set(InterfaceObj,'Enable','off');
-
- % read plan from gui and save it to workspace
- % gets also IsoCenter from GUI if checkbox is not checked
- getPlnFromGUI(handles);
-
- % get default iso center as center of gravity of all targets if not
- % already defined
- pln = evalin('base','pln');
-
- if length(pln.propStf.gantryAngles) ~= length(pln.propStf.couchAngles)
- handles = showWarning(handles,'number of gantryAngles != number of couchAngles');
- end
- %%
- if ~checkRadiationComposition(handles)
- fileName = [pln.radiationMode '_' pln.machine];
- handles = showError(handles,errordlg(['Could not find the following machine file: ' fileName ]));
- guidata(hObject,handles);
- return;
- end
-
- %% check if isocenter is already set
- if ~isfield(pln.propStf,'isoCenter')
- handles = showWarning(handles,'no iso center set - using center of gravity based on structures defined as TARGET');
- pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(evalin('base','cst'),evalin('base','ct'));
- assignin('base','pln',pln);
- elseif ~get(handles.checkIsoCenter,'Value')
- if ~strcmp(get(handles.editIsoCenter,'String'),'multiple isoCenter')
- pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1)*str2num(get(handles.editIsoCenter,'String'));
- end
- end
-
-catch ME
- handles = showError(handles,'CalcDoseCallback: Error in preprocessing!',ME);
- % change state from busy to normal
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
- guidata(hObject,handles);
- return;
-end
-
-% generate steering file
-try
- currPln = evalin('base','pln');
- % if we run 3d conf opt -> hijack runDao to trigger computation of
- % connected bixels
- if strcmp(pln.radiationMode,'photons') && get(handles.radiobutton3Dconf,'Value')
- currpln.propOpt.runDAO = true;
- end
- stf = matRad_generateStf(evalin('base','ct'),...
- evalin('base','cst'),...
- currPln);
- assignin('base','stf',stf);
-catch ME
- handles = showError(handles,'CalcDoseCallback: Error in steering file generation!',ME);
- % change state from busy to normal
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
- guidata(hObject,handles);
- return;
-end
-
-% carry out dose calculation
-try
- if strcmp(pln.radiationMode,'photons')
- dij = matRad_calcPhotonDose(evalin('base','ct'),stf,pln,evalin('base','cst'));
- elseif strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon')
- dij = matRad_calcParticleDose(evalin('base','ct'),stf,pln,evalin('base','cst'));
- end
-
- % assign results to base worksapce
- assignin('base','dij',dij);
- handles.State = 2;
- handles.TableChanged = false;
- UpdateState(handles);
- UpdatePlot(handles);
- guidata(hObject,handles);
-catch ME
- handles = showError(handles,'CalcDoseCallback: Error in dose calculation!',ME);
- % change state from busy to normal
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
- guidata(hObject,handles);
- return;
-end
-
-% change state from busy to normal
-set(Figures, 'pointer', 'arrow');
-set(InterfaceObj,'Enable','on');
-
-guidata(hObject,handles);
-
-
-
-%% plots ct and distributions
-function UpdatePlot(handles)
-
-%profile on;
-axes(handles.axesFig);
-
-% this is necessary to prevent multiple callbacks of update plot drawing on
-% top of each other in matlab <2014
-drawnow;
-
-defaultFontSize = 8;
-currAxes = axis(handles.axesFig);
-AxesHandlesCT_Dose = gobjects(0);
-AxesHandlesVOI = cell(0);
-AxesHandlesIsoDose = gobjects(0);
-
-if handles.State == 0
- cla reset
- return
-elseif handles.State > 0
- ct = evalin('base','ct');
- cst = evalin('base','cst');
- pln = evalin('base','pln');
-end
-
-%% state 3 indicates that a optimization has been performed
- AllVarNames = evalin('base','who');
-if ismember('resultGUI',AllVarNames)
- Result = evalin('base','resultGUI');
-end
-
-if exist('Result','var')
- if ~isempty(Result) && ~isempty(ct.cubeHU) && ~isfield(handles,'DispInfo')
-
- DispInfo = fieldnames(Result);
-
- for i = 1:size(DispInfo,1)
-
- % delete weight vectors in Result struct for plotting
- if isstruct(Result.(DispInfo{i,1})) || isvector(Result.(DispInfo{i,1}))
- Result = rmfield(Result,DispInfo{i,1});
- DispInfo{i,2}=false;
- else
- %second dimension indicates if it should be plotted
- DispInfo{i,2} = true;
- % determine units
- if strfind(DispInfo{i,1},'physicalDose')
- DispInfo{i,3} = '[Gy]';
- elseif strfind(DispInfo{i,1},'alpha')
- DispInfo{i,3} = '[Gy^{-1}]';
- elseif strfind(DispInfo{i,1},'beta')
- DispInfo{i,3} = '[Gy^{-2}]';
- elseif strfind(DispInfo{i,1},'RBExD')
- DispInfo{i,3} = '[Gy(RBE)]';
- elseif strfind(DispInfo{i,1},'LET')
- DispInfo{i,3} = '[keV/um]';
- else
- DispInfo{i,3} = '[a.u.]';
- end
- DispInfo{i,4} = []; % optional for the future: color range for plotting
- DispInfo{i,5} = []; % optional for the future: min max values
- end
- end
-
- set(handles.popupDisplayOption,'String',fieldnames(Result));
- if sum(strcmp(handles.SelectedDisplayOption,fieldnames(Result))) == 0
- handles.SelectedDisplayOption = DispInfo{find([DispInfo{:,2}],1,'first'),1};
- end
- set(handles.popupDisplayOption,'Value',find(strcmp(handles.SelectedDisplayOption,fieldnames(Result))));
-
- end
-end
-
-%% set and get required variables
-plane = get(handles.popupPlane,'Value');
-slice = round(get(handles.sliderSlice,'Value'));
-hold(handles.axesFig,'on');
-if get(handles.popupTypeOfPlot,'Value')==1
- set(handles.axesFig,'YDir','Reverse');
-end
-
-%% Remove colorbar?
-plotColorbarSelection = get(handles.popupmenu_chooseColorData,'Value');
-
-if get(handles.popupTypeOfPlot,'Value')==2 || plotColorbarSelection == 1
- if isfield(handles,'cBarHandel')
- delete(handles.cBarHandel);
- end
- %The following seems to be necessary as MATLAB messes up some stuff
- %with the handle storage
- ch = findall(gcf,'tag','Colorbar');
- if ~isempty(ch)
- delete(ch);
- end
-end
-
-selectIx = get(handles.popupmenu_chooseColorData,'Value');
-
-cla(handles.axesFig);
-%% plot ct - if a ct cube is available and type of plot is set to 1 and not 2; 1 indicate cube plotting and 2 profile plotting
-if ~isempty(ct) && get(handles.popupTypeOfPlot,'Value')==1
-
- if selectIx == 3
- ctIx = 2;
- else
- ctIx = selectIx;
- end
-
- if handles.cubeHUavailable
- plotCtCube = ct.cubeHU;
- ctLabel = 'Hounsfield Units';
- else
- plotCtCube = ct.cube;
- ctLabel = 'Electron Density / WEQ';
- end
-
- ctMap = matRad_getColormap(handles.ctColorMap,handles.cMapSize);
-
- if isempty(handles.dispWindow{ctIx,2})
- handles.dispWindow{ctIx,2} = [min(reshape([ct.cubeHU{:}],[],1)) max(reshape([ct.cubeHU{:}],[],1))];
- end
-
- if get(handles.radiobtnCT,'Value')
- [AxesHandlesCT_Dose(end+1),~,handles.dispWindow{ctIx,1}] = matRad_plotCtSlice(handles.axesFig,plotCtCube,1,plane,slice,ctMap,handles.dispWindow{ctIx,1});
-
- % plot colorbar? If 1 the user asked for the CT
- if plotColorbarSelection == 2 && handles.cBarChanged
- %Plot the colorbar
- handles.cBarHandel = matRad_plotColorbar(handles.axesFig,ctMap,handles.dispWindow{ctIx,1},'fontsize',defaultFontSize);
- %adjust lables
- set(get(handles.cBarHandel,'ylabel'),'String', ctLabel,'fontsize',defaultFontSize);
- % do not interprete as tex syntax
- set(get(handles.cBarHandel,'ylabel'),'interpreter','none');
- end
- end
-end
-
-%% plot dose cube
-if handles.State >= 1 && get(handles.popupTypeOfPlot,'Value')== 1 && exist('Result','var')
- doseMap = matRad_getColormap(handles.doseColorMap,handles.cMapSize);
- doseIx = 3;
- % if the selected display option doesn't exist then simply display
- % the first cube of the Result struct
- if ~isfield(Result,handles.SelectedDisplayOption)
- CubeNames = fieldnames(Result);
- handles.SelectedDisplayOption = CubeNames{1,1};
- end
-
- dose = Result.(handles.SelectedDisplayOption);
-
- % dose colorwash
- if ~isempty(dose) && ~isvector(dose)
-
- if isempty(handles.dispWindow{doseIx,2})
- handles.dispWindow{doseIx,2} = [min(dose(:)) max(dose(:))]; % set min and max dose values
- end
-
- if get(handles.radiobtnDose,'Value')
- [doseHandle,~,handles.dispWindow{doseIx,1}] = matRad_plotDoseSlice(handles.axesFig,dose,plane,slice,handles.CutOffLevel,handles.doseOpacity,doseMap,handles.dispWindow{doseIx,1});
- AxesHandlesCT_Dose(end+1) = doseHandle;
- end
-
- % plot colorbar?
- if plotColorbarSelection > 2 && handles.cBarChanged
- %Plot the colorbar
- handles.cBarHandel = matRad_plotColorbar(handles.axesFig,doseMap,handles.dispWindow{selectIx,1},'fontsize',defaultFontSize);
- %adjust lables
- Idx = find(strcmp(handles.SelectedDisplayOption,DispInfo(:,1)));
- set(get(handles.cBarHandel,'ylabel'),'String', [DispInfo{Idx,1} ' ' DispInfo{Idx,3} ],'fontsize',defaultFontSize);
- % do not interprete as tex syntax
- set(get(handles.cBarHandel,'ylabel'),'interpreter','none');
- end
- end
-
-
- %% plot iso dose lines
- if get(handles.radiobtnIsoDoseLines,'Value')
- plotLabels = get(handles.radiobtnIsoDoseLinesLabels,'Value') == 1;
-
- %Sanity Check for Contours, which actually should have been
- %computed before calling UpdatePlot
- if ~isfield(handles.IsoDose,'Contours')
- try
- handles.IsoDose.Contours = matRad_computeIsoDoseContours(dose,handles.IsoDose.Levels);
- catch
- %If the computation didn't work, we set the field to
- %empty, which will force matRad_plotIsoDoseLines to use
- %matlabs contour function instead of repeating the
- %failing computation every time
- handles.IsoDose.Contours = [];
- warning('Could not compute isodose lines! Will try slower contour function!');
- end
- end
- AxesHandlesIsoDose = matRad_plotIsoDoseLines(handles.axesFig,dose,handles.IsoDose.Contours,handles.IsoDose.Levels,plotLabels,plane,slice,doseMap,handles.dispWindow{doseIx,1},'LineWidth',1.5);
- end
-end
-
-selectIx = get(handles.popupmenu_chooseColorData,'Value');
-set(handles.txtMinVal,'String',num2str(handles.dispWindow{selectIx,2}(1,1)));
-set(handles.txtMaxVal,'String',num2str(handles.dispWindow{selectIx,2}(1,2)));
-
-%% plot VOIs
-if get(handles.radiobtnContour,'Value') && get(handles.popupTypeOfPlot,'Value')==1 && handles.State>0
- AxesHandlesVOI = [AxesHandlesVOI matRad_plotVoiContourSlice(handles.axesFig,cst,ct,1,handles.VOIPlotFlag,plane,slice,[],'LineWidth',2)];
-end
-
-%% Set axis labels and plot iso center
-matRad_plotAxisLabels(handles.axesFig,ct,plane,slice,defaultFontSize);
-
-if get(handles.radioBtnIsoCenter,'Value') == 1 && get(handles.popupTypeOfPlot,'Value') == 1 && ~isempty(pln)
- hIsoCenterCross = matRad_plotIsoCenterMarker(handles.axesFig,pln,ct,plane,slice);
-end
-
-if get(handles.radiobtnPlan,'value') == 1 && ~isempty(pln)
- matRad_plotProjectedGantryAngles(handles.axesFig,pln,ct,plane);
-end
-
-% the following line ensures the plotting order (optional)
-% set(gca,'Children',[AxesHandlesCT_Dose hIsoCenterCross AxesHandlesIsoDose AxesHandlesVOI ]);
-
-%set axis ratio
-ratios = [1/ct.resolution.x 1/ct.resolution.y 1/ct.resolution.z];
-set(handles.axesFig,'DataAspectRatioMode','manual');
-if plane == 1
- res = [ratios(3) ratios(1)]./max([ratios(3) ratios(1)]);
- set(handles.axesFig,'DataAspectRatio',[res 1])
-elseif plane == 2 % sagittal plane
- res = [ratios(3) ratios(2)]./max([ratios(3) ratios(2)]);
- set(handles.axesFig,'DataAspectRatio',[res 1])
-elseif plane == 3 % Axial plane
- res = [ratios(1) ratios(2)]./max([ratios(1) ratios(2)]);
- set(handles.axesFig,'DataAspectRatio',[res 1])
-end
-
-
-%% profile plot
-if get(handles.popupTypeOfPlot,'Value') == 2 && exist('Result','var')
- % set SAD
- fileName = [pln.radiationMode '_' pln.machine];
- try
- load(['basedata' filesep fileName]);
- SAD = machine.meta.SAD;
- catch
- error(['Could not find the following machine file: ' fileName ]);
- end
-
- % clear view and initialize some values
- cla(handles.axesFig,'reset')
- set(gca,'YDir','normal');
- ylabel('{\color{black}dose [Gy]}')
- cColor={'black','green','magenta','cyan','yellow','red','blue'};
-
- % Rotate the system into the beam.
- % passive rotation & row vector multiplication & inverted rotation requires triple matrix transpose
- rotMat_system_T = transpose(matRad_getRotationMatrix(pln.propStf.gantryAngles(handles.selectedBeam),pln.propStf.couchAngles(handles.selectedBeam)));
-
- if strcmp(handles.ProfileType,'longitudinal')
- sourcePointBEV = [handles.profileOffset -SAD 0];
- targetPointBEV = [handles.profileOffset SAD 0];
- elseif strcmp(handles.ProfileType,'lateral')
- sourcePointBEV = [-SAD handles.profileOffset 0];
- targetPointBEV = [ SAD handles.profileOffset 0];
- end
-
- rotSourcePointBEV = sourcePointBEV * rotMat_system_T;
- rotTargetPointBEV = targetPointBEV * rotMat_system_T;
-
- % perform raytracing on the central axis of the selected beam, use unit
- % electron density for plotting against the geometrical depth
- [~,l,rho,~,ix] = matRad_siddonRayTracer(pln.propStf.isoCenter(handles.selectedBeam,:),ct.resolution,rotSourcePointBEV,rotTargetPointBEV,{0*ct.cubeHU{1}+1});
- d = [0 l .* rho{1}];
- % Calculate accumulated d sum.
- vX = cumsum(d(1:end-1));
-
- % this step is necessary if visualization is set to profile plot
- % and another optimization is carried out - set focus on GUI
- figHandles = get(0,'Children');
- idxHandle = [];
- if ~isempty(figHandles)
- v=version;
- if str2double(v(1:3))>= 8.5
- idxHandle = strcmp({figHandles(:).Name},'matRadGUI');
- else
- idxHandle = strcmp(get(figHandles,'Name'),'matRadGUI');
- end
- end
- figure(figHandles(idxHandle));
-
- % plot physical dose
- Content = get(handles.popupDisplayOption,'String');
- SelectedCube = Content{get(handles.popupDisplayOption,'Value')};
- if sum(strcmp(SelectedCube,{'physicalDose','effect','RBExDose','alpha','beta','RBE'})) > 0
- Suffix = '';
- else
- Idx = find(SelectedCube == '_');
- Suffix = SelectedCube(Idx:end);
- end
-
- mPhysDose = Result.(['physicalDose' Suffix]);
- PlotHandles{1} = plot(handles.axesFig,vX,mPhysDose(ix),'color',cColor{1,1},'LineWidth',3); hold on;
- PlotHandles{1,2} ='physicalDose';
- ylabel(handles.axesFig,'dose in [Gy]');
- set(handles.axesFig,'FontSize',defaultFontSize);
-
- % plot counter
- Cnt=2;
-
- if isfield(Result,['RBE' Suffix])
-
- %disbale specific plots
- %DispInfo{6,2}=0;
- %DispInfo{5,2}=0;
- %DispInfo{2,2}=0;
-
- % generate two lines for ylabel
- StringYLabel1 = '\fontsize{8}{\color{red}RBE x dose [Gy(RBE)] \color{black}dose [Gy] ';
- StringYLabel2 = '';
- for i=1:1:size(DispInfo,1)
- if DispInfo{i,2} && sum(strcmp(DispInfo{i,1},{['effect' Suffix],['alpha' Suffix],['beta' Suffix]})) > 0
- %physicalDose is already plotted and RBExD vs RBE is plotted later with plotyy
- if ~strcmp(DispInfo{i,1},['RBExDose' Suffix]) &&...
- ~strcmp(DispInfo{i,1},['RBE' Suffix]) && ...
- ~strcmp(DispInfo{i,1},['physicalDose' Suffix])
-
- mCube = Result.([DispInfo{i,1}]);
- PlotHandles{Cnt,1} = plot(handles.axesFig,vX,mCube(ix),'color',cColor{1,Cnt},'LineWidth',3);hold on;
- PlotHandles{Cnt,2} = DispInfo{i,1};
- StringYLabel2 = [StringYLabel2 ' \color{' cColor{1,Cnt} '}' DispInfo{i,1} ' [' DispInfo{i,3} ']'];
- Cnt = Cnt+1;
- end
- end
- end
- StringYLabel2 = [StringYLabel2 '}'];
- % always plot RBExD against RBE
- mRBExDose = Result.(['RBExDose' Suffix]);
- vBED = mRBExDose(ix);
- mRBE = Result.(['RBE' Suffix]);
- vRBE = mRBE(ix);
-
- % plot biological dose against RBE
- [ax, PlotHandles{Cnt,1}, PlotHandles{Cnt+1,1}]=plotyy(handles.axesFig,vX,vBED,vX,vRBE,'plot');hold on;
- PlotHandles{Cnt,2}='RBExDose';
- PlotHandles{Cnt+1,2}='RBE';
-
- % set plotyy properties
- set(get(ax(2),'Ylabel'),'String','RBE [a.u.]','FontSize',8);
- ylabel({StringYLabel1;StringYLabel2})
- set(PlotHandles{Cnt,1},'Linewidth',4,'color','r');
- set(PlotHandles{Cnt+1,1},'Linewidth',3,'color','b');
- set(ax(1),'ycolor','r')
- set(ax(2),'ycolor','b')
- set(ax,'FontSize',8);
- Cnt=Cnt+2;
- end
-
- % asses target coordinates
- tmpPrior = intmax;
- tmpSize = 0;
- for i=1:size(cst,1)
- if strcmp(cst{i,3},'TARGET') && tmpPrior >= cst{i,5}.Priority && tmpSize 0
- AllVarNames = evalin('base','who');
- if ismember('resultGUI',AllVarNames)
- Result = evalin('base','resultGUI');
- end
-
- ct = evalin('base','ct');
- cst = evalin('base','cst');
- pln = evalin('base','pln');
-
- if ismember('stf',AllVarNames)
- stf = evalin('base','stf');
-
- %validate stf with current pln settings
- validStf = true;
- gantryAngles = [stf.gantryAngle];
- validStf = isequal(gantryAngles,pln.propStf.gantryAngles) & validStf;
- couchAngles = [stf.couchAngle];
- validStf = isequal(couchAngles,pln.propStf.couchAngles) & validStf;
- isoCenter = vertcat(stf.isoCenter);
- validStf = isequal(isoCenter,pln.propStf.isoCenter) & validStf;
-
- if ~validStf
- matRad_cfg = MatRad_Config.instance();
- matRad_cfg.dispWarning('stf and pln are not consistent, using pln for geometry display!');
- stf = [];
- end
-
- else
- stf = [];
- end
-
-
+if nargout > 0
+ hGUI = hMatRadGUI;
end
-oldView = get(axesFig3D,'View');
-
-cla(axesFig3D);
-%delete(allchild(axesFig3D));
-
-%test = allchild(axesFig3D);
-
-plane = get(handles.popupPlane,'Value');
-slice = round(get(handles.sliderSlice,'Value'));
-defaultFontSize = 8;
-%Check if we need to precompute the surface data
-if size(cst,2) < 8
- cst = matRad_computeAllVoiSurfaces(ct,cst);
- assignin('base','cst',cst);
end
-set(fig3D,'Color',0.5*[1 1 1]);
-set(axesFig3D,'Color',1*[0 0 0]);
-%% Plot 3D structures
-hold(axesFig3D,'on');
-if get(handles.radiobtnContour,'Value') && handles.State>0
- voiPatches = matRad_plotVois3D(axesFig3D,ct,cst,handles.VOIPlotFlag,colorcube);
-end
-
-%% plot the CT slice
-if get(handles.radiobtnCT,'Value')
- window = handles.dispWindow{2,1}; %(2 for ct)
- ctMap = matRad_getColormap(handles.ctColorMap,handles.cMapSize);
- ctHandle = matRad_plotCtSlice3D(axesFig3D,ct,1,plane,slice,ctMap,window);
-end
+%Validates the attributes for the command line Modes
+function validateModeValue(x)
+%When passed from OS terminal (or inline in Command Window) everything is a string
+if isdeployed || ischar(x) || isstring(x)
+ x=str2double(x);
-%% plot the dose slice
-if handles.State >= 1 && exist('Result','var')
- doseMap = matRad_getColormap(handles.doseColorMap,handles.cMapSize);
- doseIx = 3;
- % if the selected display option doesn't exist then simply display
- % the first cube of the Result struct
- if ~isfield(Result,handles.SelectedDisplayOption)
- CubeNames = fieldnames(Result);
- handles.SelectedDisplayOption = CubeNames{1,1};
- end
-
- dose = Result.(handles.SelectedDisplayOption);
-
- % dose colorwash
- if ~isempty(dose) && ~isvector(dose)
-
- if isempty(handles.dispWindow{doseIx,2})
- handles.dispWindow{doseIx,2} = [min(dose(:)) max(dose(:))]; % set min and max dose values
- end
-
- if get(handles.radiobtnDose,'Value')
- [doseHandle,~,handles.dispWindow{doseIx,1}] = matRad_plotDoseSlice3D(axesFig3D,ct,dose,plane,slice,handles.CutOffLevel,handles.doseOpacity,doseMap,handles.dispWindow{doseIx,1});
- end
- if get(handles.radiobtnIsoDoseLines,'Value')
- matRad_plotIsoDoseLines3D(axesFig3D,ct,dose,handles.IsoDose.Contours,handles.IsoDose.Levels,plane,slice,doseMap,handles.dispWindow{doseIx,1},'LineWidth',1.5);
- end
- end
end
-
-if get(handles.radiobtnPlan,'Value')
- matRad_plotPlan3D(axesFig3D,pln,stf);
+validateattributes(x,{'logical','numeric'},{'scalar','binary'});
end
-
-%hLight = light('Parent',axesFig3D);
-%camlight(hLight,'left');
-%lighting('gouraud');
-
-xlabel(axesFig3D,'x [voxels]','FontSize',defaultFontSize)
-ylabel(axesFig3D,'y [voxels]','FontSize',defaultFontSize)
-zlabel(axesFig3D,'z [voxels]','FontSize',defaultFontSize)
-title(axesFig3D,'matRad 3D view');
-
-% set axis ratio
-ratios = [1 1 1]; %[1/ct.resolution.x 1/ct.resolution.y 1/ct.resolution.z];
-ratios = ratios([2 1 3]);
-set(axesFig3D,'DataAspectRatioMode','manual');
-set(axesFig3D,'DataAspectRatio',ratios./max(ratios));
-
-set(axesFig3D,'Ydir','reverse');
-
-set(axesFig3D,'view',oldView);
-
-
-% --- Executes on selection change in popupPlane.
-function popupPlane_Callback(hObject, ~, handles)
-% hObject handle to popupPlane (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns popupPlane contents as cell array
-% contents{get(hObject,'Value')} returns selected item from popupPlane
-
-% set slice slider
-handles.plane = get(hObject,'value');
-initViewSliceSlider(handles);
-
-handles.rememberCurrAxes = false;
-UpdatePlot(handles);
-handles.rememberCurrAxes = true;
-guidata(hObject,handles);
-
-% --- Executes on slider movement.
-function sliderSlice_Callback(~, ~, handles)
-% hObject handle to sliderSlice (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'Value') returns position of slider
-% get(hObject,'Min') and get(hObject,'Max') to determine range of slider
-UpdatePlot(handles)
-
-% --- Executes on button press in radiobtnContour.
-function radiobtnContour_Callback(~, ~, handles)
-% hObject handle to radiobtnContour (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of radiobtnContour
-UpdatePlot(handles)
-
-% --- Executes on button press in radiobtnDose.
-function radiobtnDose_Callback(~, ~, handles)
-% hObject handle to radiobtnDose (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of radiobtnDose
-UpdatePlot(handles)
-
-% --- Executes on button press in radiobtnIsoDoseLines.
-function radiobtnIsoDoseLines_Callback(~, ~, handles)
-% hObject handle to radiobtnIsoDoseLines (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of radiobtnIsoDoseLines
-UpdatePlot(handles)
-
-% --- Executes on button press in btnOptimize.
-function btnOptimize_Callback(hObject, eventdata, handles)
-% hObject handle to btnOptimize (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-try
- % indicate that matRad is busy
- % change mouse pointer to hour glass
- Figures = gcf;%findobj('type','figure');
- set(Figures, 'pointer', 'watch');
- drawnow;
- % disable all active objects
- InterfaceObj = findobj(Figures,'Enable','on');
- set(InterfaceObj,'Enable','off');
- % wait until the table is updated
- btnTableSave_Callback([],[],handles); %We don't need it?
-
-
- % if a critical change to the cst has been made which affects the dij matrix
- if handles.DijCalcWarning == true
-
- choice = questdlg('Overlap priorites of OAR constraints have been edited, a new OAR VOI was added or a critical row constraint was deleted. A new Dij calculation might be necessary.', ...
- 'Title','Cancel','Calculate Dij then Optimize','Optimze directly','Optimze directly');
-
- switch choice
- case 'Cancel'
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
- guidata(hObject,handles);
- return;
- case 'Calculate dij again and optimize'
- handles.DijCalcWarning = false;
- btnCalcDose_Callback(hObject, eventdata, handles)
- case 'Optimze directly'
- handles.DijCalcWarning = false;
- end
- end
-
- pln = evalin('base','pln');
- ct = evalin('base','ct');
-
- % optimize
- if get(handles.radiobutton3Dconf,'Value') && strcmp(handles.Modalities{get(handles.popupRadMode,'Value')},'photons')
- % conformal plan if photons and 3d conformal
- if ~matRad_checkForConnectedBixelRows(evalin('base','stf'))
- error('disconnetced dose influence data in BEV - run dose calculation again with consistent settings');
- end
- [resultGUIcurrentRun,usedOptimizer] = matRad_fluenceOptimization(matRad_collapseDij(evalin('base','dij')),evalin('base','cst'),pln);
- resultGUIcurrentRun.w = resultGUIcurrentRun.w * ones(evalin('base','dij.totalNumOfBixels'),1);
- resultGUIcurrentRun.wUnsequenced = resultGUIcurrentRun.w;
- else
- if pln.propOpt.runDAO
- if ~matRad_checkForConnectedBixelRows(evalin('base','stf'))
- error('disconnetced dose influence data in BEV - run dose calculation again with consistent settings');
- end
- end
-
- [resultGUIcurrentRun,usedOptimizer] = matRad_fluenceOptimization(evalin('base','dij'),evalin('base','cst'),pln);
- end
-
- %if resultGUI already exists then overwrite the "standard" fields
- AllVarNames = evalin('base','who');
- if ismember('resultGUI',AllVarNames)
- resultGUI = evalin('base','resultGUI');
- sNames = fieldnames(resultGUIcurrentRun);
- oldNames = fieldnames(resultGUI);
- if(length(oldNames) > length(sNames))
- for j = 1:length(oldNames)
- if strfind(oldNames{j}, 'beam')
- resultGUI = rmfield(resultGUI, oldNames{j});
- end
- end
- end
- for j = 1:length(sNames)
- resultGUI.(sNames{j}) = resultGUIcurrentRun.(sNames{j});
- end
- else
- resultGUI = resultGUIcurrentRun;
- end
- assignin('base','resultGUI',resultGUI);
-
- % set some values
- if handles.plane == 1
- set(handles.sliderSlice,'Value',ceil(pln.propStf.isoCenter(1,2)/ct.resolution.x));
- elseif handles.plane == 2
- set(handles.sliderSlice,'Value',ceil(pln.propStf.isoCenter(1,1)/ct.resolution.y));
- elseif handles.plane == 3
- set(handles.sliderSlice,'Value',ceil(pln.propStf.isoCenter(1,3)/ct.resolution.z));
- end
-
- handles.State = 3;
- handles.SelectedDisplayOptionIdx = 1;
- if strcmp(pln.radiationMode,'carbon') || (strcmp(pln.radiationMode,'protons') && strcmp(pln.propOpt.bioOptimization,'const_RBExD'))
- handles.SelectedDisplayOption = 'RBExDose';
- else
- handles.SelectedDisplayOption = 'physicalDose';
- end
- handles.selectedBeam = 1;
- % check IPOPT status and return message for GUI user if no DAO or
- % particles
- if ~pln.propOpt.runDAO || ~strcmp(pln.radiationMode,'photons')
- CheckOptimizerStatus(usedOptimizer,'Fluence')
- end
-
-catch ME
- handles = showError(handles,'OptimizeCallback: Could not optimize!',ME);
- % change state from busy to normal
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
- guidata(hObject,handles);
- return;
-end
-
-% perform sequencing and DAO
-try
-
- %% sequencing
- if strcmp(pln.radiationMode,'photons') && (pln.propOpt.runSequencing || pln.propOpt.runDAO)
- % resultGUI = matRad_xiaLeafSequencing(resultGUI,evalin('base','stf'),evalin('base','dij')...
- % ,get(handles.editSequencingLevel,'Value'));
- % resultGUI = matRad_engelLeafSequencing(resultGUI,evalin('base','stf'),evalin('base','dij')...
- % ,str2double(get(handles.editSequencingLevel,'String')));
- resultGUI = matRad_siochiLeafSequencing(resultGUI,evalin('base','stf'),evalin('base','dij')...
- ,str2double(get(handles.editSequencingLevel,'String')));
-
- assignin('base','resultGUI',resultGUI);
- end
-
-catch ME
- handles = showError(handles,'OptimizeCallback: Could not perform sequencing',ME);
- % change state from busy to normal
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
- guidata(hObject,handles);
- return;
-end
-
-try
- %% DAO
- if strcmp(pln.radiationMode,'photons') && pln.propOpt.runDAO
- handles = showWarning(handles,['Observe: You are running direct aperture optimization' filesep 'This is experimental code that has not been thoroughly debugged - especially in combination with constrained optimization.']);
- [resultGUI,usedOptimizer] = matRad_directApertureOptimization(evalin('base','dij'),evalin('base','cst'),...
- resultGUI.apertureInfo,resultGUI,pln);
- assignin('base','resultGUI',resultGUI);
- % check IPOPT status and return message for GUI user
- CheckOptimizerStatus(usedOptimizer,'DAO');
- end
-
- if strcmp(pln.radiationMode,'photons') && (pln.propOpt.runSequencing || pln.propOpt.runDAO)
- matRad_visApertureInfo(resultGUI.apertureInfo);
- end
-
-catch ME
- handles = showError(handles,'OptimizeCallback: Could not perform direct aperture optimization',ME);
- % change state from busy to normal
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
- guidata(hObject,handles);
- return;
-end
-
-% change state from busy to normal
-set(Figures, 'pointer', 'arrow');
-set(InterfaceObj,'Enable','on');
-handles.dispWindow{3,1} = []; % reset dose ranges
-handles.dispWindow{3,2} = []; % reset min max dose values
-handles.rememberCurrAxes = false;
-handles.IsoDose.Levels = 0; % ensure to use default iso dose line spacing
-handles.cBarChanged = true;
-
-guidata(hObject,handles);
-handles = updateIsoDoseLineCache(handles);
-UpdateState(handles);
-UpdatePlot(handles);
-handles.rememberCurrAxes = true;
-guidata(hObject,handles);
-
-
-% the function CheckValidityPln checks if the provided plan is valid so
-% that it can be used further on for optimization
-function FlagValid = CheckValidityPln(cst)
-
-FlagValid = true;
-%check if mean constraint is always used in combination
-for i = 1:size(cst,1)
- if ~isempty(cst{i,6})
- if ~isempty(strfind([cst{i,6}.type],'mean')) && isempty(strfind([cst{i,6}.type],'square'))
- FlagValid = false;
- warndlg('mean constraint needs to be defined in addition to a second constraint (e.g. squared deviation)');
- break
- end
- end
-end
-
-
-% --- Executes on selection change in popupTypeOfPlot
-function popupTypeOfPlot_Callback(hObject, ~, handles)
-
- % intensity plot
-if get(hObject,'Value') == 1
-
- set(handles.sliderBeamSelection,'Enable','off')
- set(handles.sliderOffset,'Enable','off')
- set(handles.popupDisplayOption,'Enable','on')
- set(handles.btnProfileType,'Enable','off');
- set(handles.popupPlane,'Enable','on');
- set(handles.radiobtnCT,'Enable','on');
- set(handles.radiobtnContour,'Enable','on');
- set(handles.radiobtnDose,'Enable','on');
- set(handles.radiobtnIsoDoseLines,'Enable','on');
- set(handles.radiobtnIsoDoseLinesLabels,'Enable','on');
- set(handles.sliderSlice,'Enable','on');
-
-% profile plot
-elseif get(hObject,'Value') == 2
-
- if handles.State > 0
- if length(parseStringAsNum(get(handles.editGantryAngle,'String'),true)) > 1
-
- set(handles.sliderBeamSelection,'Enable','on');
- handles.selectedBeam = 1;
- pln = evalin('base','pln');
- set(handles.sliderBeamSelection,'Min',handles.selectedBeam,'Max',pln.propStf.numOfBeams,...
- 'Value',handles.selectedBeam,...
- 'SliderStep',[1/(pln.propStf.numOfBeams-1) 1/(pln.propStf.numOfBeams-1)],...
- 'Enable','on');
-
- else
- handles.selectedBeam = 1;
- end
-
- handles.profileOffset = get(handles.sliderOffset,'Value');
-
- vMinMax = [-100 100];
- vRange = sum(abs(vMinMax));
-
- ct = evalin('base','ct');
- if strcmp(get(handles.btnProfileType,'String'),'lateral')
- SliderStep = vRange/ct.resolution.x;
- else
- SliderStep = vRange/ct.resolution.y;
- end
-
- set(handles.sliderOffset,'Min',vMinMax(1),'Max',vMinMax(2),...
- 'Value',handles.profileOffset,...
- 'SliderStep',[1/SliderStep 1/SliderStep],...
- 'Enable','on');
- end
-
-
- set(handles.popupDisplayOption,'Enable','on');
- set(handles.btnProfileType,'Enable','on');
- set(handles.popupPlane,'Enable','off');
- set(handles.radiobtnCT,'Enable','off');
- set(handles.radiobtnContour,'Enable','off');
- set(handles.radiobtnDose,'Enable','off');
- set(handles.radiobtnIsoDoseLines,'Enable','off');
- set(handles.sliderSlice,'Enable','off');
- set(handles.radiobtnIsoDoseLinesLabels,'Enable','off');
-
-
- set(handles.btnProfileType,'Enable','on')
-
- if strcmp(get(handles.btnProfileType,'String'),'lateral')
- handles.ProfileType = 'longitudinal';
- else
- handles.ProfileType = 'lateral';
- end
-
-end
-
-handles.cBarChanged = true;
-
-handles.rememberCurrAxes = false;
-cla(handles.axesFig,'reset');
-UpdatePlot(handles);
-handles.rememberCurrAxes = true;
-guidata(hObject, handles);
-
-% --- Executes on selection change in popupDisplayOption.
-function popupDisplayOption_Callback(hObject, ~, handles)
-content = get(hObject,'String');
-handles.SelectedDisplayOption = content{get(hObject,'Value'),1};
-handles.SelectedDisplayOptionIdx = get(hObject,'Value');
-%handles.dispWindow{3,1} = []; handles.dispWindow{3,2} = [];
-
-if ~isfield(handles,'colormapLocked') || ~handles.colormapLocked
- handles.dispWindow{3,1} = []; handles.dispWindow{3,2} = [];
-end
-
-handles = updateIsoDoseLineCache(handles);
-handles.cBarChanged = true;
-guidata(hObject, handles);
-UpdatePlot(handles);
-guidata(hObject, handles);
-
-% --- Executes on slider movement.
-function sliderBeamSelection_Callback(hObject, ~, handles)
-% hObject handle to sliderBeamSelection (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'Value') returns position of slider
-% get(hObject,'Min') and get(hObject,'Max') to determine range of slider
-
-
-handles.selectedBeam = round(get(hObject,'Value'));
-set(hObject, 'Value', handles.selectedBeam);
-handles.rememberCurrAxes = false;
-UpdatePlot(handles);
-handles.rememberCurrAxes = true;
-guidata(hObject,handles);
-
-% --- Executes on button press in btnProfileType.
-function btnProfileType_Callback(hObject, ~, handles)
-% hObject handle to btnProfileType (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-if strcmp(get(hObject,'Enable') ,'on')
- if strcmp(handles.ProfileType,'lateral')
- handles.ProfileType = 'longitudinal';
- set(hObject,'String','lateral');
- else
- handles.ProfileType = 'lateral';
- set(hObject,'String','longitudinal');
- end
-
- handles.rememberCurrAxes = false;
- UpdatePlot(handles);
- handles.rememberCurrAxes = true;
-
- guidata(hObject, handles);
-
-end
-
-% enables/ disables buttons according to the current state
-function UpdateState(handles)
-
-if handles.State > 0
- pln = evalin('base','pln');
-
- if strcmp(pln.radiationMode,'carbon')
- set(handles.popMenuBioOpt,'Enable','on');
- set(handles.btnSetTissue,'Enable','on');
- elseif strcmp(pln.radiationMode,'protons')
- set(handles.popMenuBioOpt,'Enable','on');
- set(handles.btnSetTissue,'Enable','off');
- else
- set(handles.popMenuBioOpt,'Enable','off');
- set(handles.btnSetTissue,'Enable','off');
- end
-
- cMapControls = allchild(handles.uipanel_colormapOptions);
- for runHandles = cMapControls
- set(runHandles,'Enable','on');
- end
-end
-
-if handles.cubeHUavailable
- cMapOptionsSelectList = {'None','CT (HU)','Result (i.e. dose)'};
- set(handles.popupmenu_windowPreset,'Visible','on');
- set(handles.text_windowPreset,'String','Window Preset');
-else
- cMapOptionsSelectList = {'None','CT (ED)','Result (i.e. dose)'};
- set(handles.popupmenu_windowPreset,'Visible','off');
- set(handles.text_windowPreset,'String','No available Window Presets');
-end
-handles.cBarChanged = true;
-
- switch handles.State
-
- case 0
-
- set(handles.txtInfo,'String','no data loaded');
- set(handles.btnCalcDose,'Enable','off');
- set(handles.btnOptimize ,'Enable','off');
- set(handles.pushbutton_recalc,'Enable','off');
- set(handles.btnSaveToGUI,'Enable','off');
- set(handles.btnDVH,'Enable','off');
- set(handles.importDoseButton,'Enable','off');
- set(handles.btn_export,'Enable','off');
- set(handles.btn3Dview,'Enable','off');
-
- cMapControls = allchild(handles.uipanel_colormapOptions);
- for runHandles = cMapControls
- set(runHandles,'Enable','off');
- end
-
- set(handles.popupmenu_chooseColorData,'String',cMapOptionsSelectList{1})
- set(handles.popupmenu_chooseColorData,'Value',1);
-
- case 1
-
- set(handles.txtInfo,'String','ready for dose calculation');
- set(handles.btnCalcDose,'Enable','on');
- set(handles.btnOptimize ,'Enable','off');
- set(handles.pushbutton_recalc,'Enable','off');
- set(handles.btnSaveToGUI,'Enable','off');
- set(handles.btnDVH,'Enable','off');
- set(handles.importDoseButton,'Enable','off');
- set(handles.btn_export,'Enable','on');
- set(handles.btn3Dview,'Enable','on');
-
- set(handles.popupmenu_chooseColorData,'String',cMapOptionsSelectList(1:2))
- set(handles.popupmenu_chooseColorData,'Value',2);
- AllVarNames = evalin('base','who');
- if ~isempty(AllVarNames)
- if ismember('resultGUI',AllVarNames)
- set(handles.pushbutton_recalc,'Enable','on');
- set(handles.btnSaveToGUI,'Enable','on');
- set(handles.btnDVH,'Enable','on');
- set(handles.popupmenu_chooseColorData,'String',cMapOptionsSelectList(1:3))
- set(handles.popupmenu_chooseColorData,'Value',3);
- end
- end
-
- case 2
-
- set(handles.txtInfo,'String','ready for optimization');
- set(handles.btnCalcDose,'Enable','on');
- set(handles.btnOptimize ,'Enable','on');
- set(handles.pushbutton_recalc,'Enable','off');
- set(handles.btnSaveToGUI,'Enable','off');
- set(handles.btnDVH,'Enable','off');
- set(handles.importDoseButton,'Enable','off');
- set(handles.btn_export,'Enable','on');
- set(handles.btn3Dview,'Enable','on');
- set(handles.popupmenu_chooseColorData,'String',cMapOptionsSelectList(1:2))
- set(handles.popupmenu_chooseColorData,'Value',2);
- AllVarNames = evalin('base','who');
-
- if ~isempty(AllVarNames)
- if ismember('resultGUI',AllVarNames)
- set(handles.pushbutton_recalc,'Enable','on');
- set(handles.btnSaveToGUI,'Enable','on');
- set(handles.btnDVH,'Enable','on');
- set(handles.popupmenu_chooseColorData,'String',cMapOptionsSelectList(1:3))
- set(handles.popupmenu_chooseColorData,'Value',3);
- end
- end
-
- case 3
- set(handles.txtInfo,'String','plan is optimized');
- set(handles.btnCalcDose,'Enable','on');
- set(handles.btnOptimize ,'Enable','on');
- set(handles.pushbutton_recalc,'Enable','on');
- set(handles.btnSaveToGUI,'Enable','on');
- set(handles.btnDVH,'Enable','on');
- set(handles.btn_export,'Enable','on');
- set(handles.btn3Dview,'Enable','on');
- % resultGUI struct needs to be available to import dose
- % otherwise inconsistent states can be achieved
- set(handles.importDoseButton,'Enable','on');
- set(handles.popupmenu_chooseColorData,'String',cMapOptionsSelectList(1:3))
- set(handles.popupmenu_chooseColorData,'Value',3);
- end
-
-guidata(handles.figure1,handles);
-
-% fill GUI elements with plan information
-function setPln(handles)
-
-matRad_cfg = MatRad_Config.instance();
-
-pln = evalin('base','pln');
-% sanity check of isoCenter
-if size(pln.propStf.isoCenter,1) ~= pln.propStf.numOfBeams && size(pln.propStf.isoCenter,1) == 1
- pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * pln.propStf.isoCenter(1,:);
-elseif size(pln.propStf.isoCenter,1) ~= pln.propStf.numOfBeams && size(pln.propStf.isoCenter,1) ~= 1
- matRad_cfg.dispError('Isocenter in plan file are incosistent.');
-end
-
-%Sanity check for the bixelWidth field
-bixelWidth = pln.propStf.bixelWidth;
-
-if isnumeric(bixelWidth) && isscalar(bixelWidth)
- bixelWidth = num2str(pln.propStf.bixelWidth);
-elseif ~isnumeric(bixelWidth) && ~strcmp(bixelWidth,'field')
- matRad_cfg.dispError('Invalid bixel width! Must be a scalar number or ''field'' for field-based dose calculation with shapes stored in stf!');
-end
-
-set(handles.editBixelWidth,'String',bixelWidth);
-set(handles.editFraction,'String',num2str(pln.numOfFractions));
-
-if isfield(pln.propStf,'isoCenter')
- if size(unique(pln.propStf.isoCenter,'rows'),1) == 1
- set(handles.editIsoCenter,'String',regexprep(num2str((round(pln.propStf.isoCenter(1,:)*10))./10), '\s+', ' '));
- set(handles.editIsoCenter,'Enable','on');
- set(handles.checkIsoCenter,'Enable','on');
- else
- set(handles.editIsoCenter,'String','multiple isoCenter');
- set(handles.editIsoCenter,'Enable','off');
- set(handles.checkIsoCenter,'Value',0);
- set(handles.checkIsoCenter,'Enable','off');
- end
-end
-set(handles.editGantryAngle,'String',num2str((pln.propStf.gantryAngles)));
-set(handles.editCouchAngle,'String',num2str((pln.propStf.couchAngles)));
-set(handles.popupRadMode,'Value',find(strcmp(get(handles.popupRadMode,'String'),pln.radiationMode)));
-set(handles.popUpMachine,'Value',find(strcmp(get(handles.popUpMachine,'String'),pln.machine)));
-
-if ~strcmp(pln.propOpt.bioOptimization,'none')
- set(handles.popMenuBioOpt,'Enable','on');
- contentPopUp = get(handles.popMenuBioOpt,'String');
- ix = find(strcmp(pln.propOpt.bioOptimization,contentPopUp));
- set(handles.popMenuBioOpt,'Value',ix);
- set(handles.btnSetTissue,'Enable','on');
-else
- set(handles.popMenuBioOpt,'Enable','off');
- set(handles.btnSetTissue,'Enable','off');
-end
-%% enable sequencing and DAO button if radiation mode is set to photons
-if strcmp(pln.radiationMode,'photons') && pln.propOpt.runSequencing
- set(handles.btnRunSequencing,'Enable','on');
- set(handles.btnRunSequencing,'Value',1);
-elseif strcmp(pln.radiationMode,'photons') && ~pln.propOpt.runSequencing
- set(handles.btnRunSequencing,'Enable','on');
- set(handles.btnRunSequencing,'Value',0);
-else
- set(handles.btnRunSequencing,'Enable','off');
-end
-%% enable DAO button if radiation mode is set to photons
-if strcmp(pln.radiationMode,'photons') && pln.propOpt.runDAO
- set(handles.btnRunDAO,'Enable','on');
- set(handles.btnRunDAO,'Value',1);
-elseif strcmp(pln.radiationMode,'photons') && ~pln.propOpt.runDAO
- set(handles.btnRunDAO,'Enable','on');
- set(handles.btnRunDAO,'Value',0);
-else
- set(handles.btnRunDAO,'Enable','off');
-end
-%% enable stratification level input if radiation mode is set to photons
-if strcmp(pln.radiationMode,'photons')
- set(handles.txtSequencing,'Enable','on');
- set(handles.radiobutton3Dconf,'Enable','on');
- set(handles.editSequencingLevel,'Enable','on');
-else
- set(handles.txtSequencing,'Enable','off');
- set(handles.radiobutton3Dconf,'Enable','off');
- set(handles.editSequencingLevel,'Enable','off');
-end
-
-% --- Executes on button press in btnTableSave.
-function btnTableSave_Callback(~, ~, handles)
-% hObject handle to btnTableSave (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-if get(handles.checkIsoCenter,'Value')
- pln = evalin('base','pln');
- pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(evalin('base','cst'),evalin('base','ct'));
- set(handles.editIsoCenter,'String',regexprep(num2str((round(pln.propStf.isoCenter(1,:) * 10))./10), '\s+', ' '));
- assignin('base','pln',pln);
-end
-getPlnFromGUI(handles);
-
-% --- Executes on selection change in listBoxCmd.
-function listBoxCmd_Callback(hObject, ~, ~)
-numLines = size(get(hObject,'String'),1);
-set(hObject, 'ListboxTop', numLines);
-
-% --- Executes on slider movement.
-function sliderOffset_Callback(hObject, ~, handles)
-handles.profileOffset = get(hObject,'Value');
-UpdatePlot(handles);
-
-
-%% HELPER FUNCTIONS
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% check validity of input for cst
-function flagValidity = CheckValidity(Val)
-
-flagValidity = true;
-
-if ischar(Val)
- Val = str2num(Val);
-end
-
-if length(Val) > 2
- warndlg('invalid input!');
-end
-
-if isempty(Val)
- warndlg('Input not a number!');
- flagValidity = false;
-end
-
-if any(Val < 0)
- warndlg('Input not a positive number!');
- flagValidity = false;
-end
-
-% return IPOPT status as message box
-function CheckOptimizerStatus(usedOptimizer,OptCase)
-
-[statusmsg,statusflag] = usedOptimizer.GetStatus();
-
-if statusflag == 0 || statusflag == 1
- status = 'none';
-else
- status = 'warn';
-end
-
-msgbox(['Optimizer finished with status ' num2str(statusflag) ' (' statusmsg ')'],'Optimizer',status,'modal');
-
-% get pln file form GUI
-function getPlnFromGUI(handles)
-
-% evalin pln (if existant) in order to decide whether isoCenter should be calculated
-% automatically
-if evalin('base','exist(''pln'',''var'')')
- pln = evalin('base','pln');
-end
-
-% Special parsing of bixelWidth (since it can also be "field") for imported
-% shapes
-bixelWidth = get(handles.editBixelWidth,'String'); % [mm] / also corresponds to lateral spot spacing for particles
-if strcmp(bixelWidth,'field')
- pln.propStf.bixelWidth = bixelWidth;
-else
- pln.propStf.bixelWidth = parseStringAsNum(bixelWidth,false);
- if isnan(pln.propStf.bixelWidth)
- warndlg('Invalid bixel width! Use standard bixel width of 5mm!');
- pln.propStf.bixelWidth = 5;
- set(handles.editBixelWidth,'String','5');
- end
-end
-
-pln.propStf.gantryAngles = parseStringAsNum(get(handles.editGantryAngle,'String'),true); % [???]
-
-if handles.eduMode
- set(handles.editCouchAngle,'String',num2str(zeros(size(pln.propStf.gantryAngles))));
-end
-
-pln.propStf.couchAngles = parseStringAsNum(get(handles.editCouchAngle,'String'),true); % [???]
-pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
-try
- ct = evalin('base','ct');
- pln.numOfVoxels = prod(ct.cubeDim);
- pln.voxelDimensions = ct.cubeDim;
-catch
-end
-pln.numOfFractions = parseStringAsNum(get(handles.editFraction,'String'),false);
-contents = get(handles.popupRadMode,'String');
-pln.radiationMode = contents{get(handles.popupRadMode,'Value')}; % either photons / protons / carbon
-contents = get(handles.popUpMachine,'String');
-pln.machine = contents{get(handles.popUpMachine,'Value')};
-
-if (~strcmp(pln.radiationMode,'photons'))
- contentBioOpt = get(handles.popMenuBioOpt,'String');
- pln.propOpt.bioOptimization = contentBioOpt{get(handles.popMenuBioOpt,'Value'),:};
-else
- pln.propOpt.bioOptimization = 'none';
-end
-
-pln.propOpt.runSequencing = logical(get(handles.btnRunSequencing,'Value'));
-pln.propOpt.runDAO = logical(get(handles.btnRunDAO,'Value'));
-
-try
- cst = evalin('base','cst');
- if (sum(strcmp('TARGET',cst(:,3))) > 0 && get(handles.checkIsoCenter,'Value')) || ...
- (sum(strcmp('TARGET',cst(:,3))) > 0 && ~isfield(pln.propStf,'isoCenter'))
- pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct);
- set(handles.checkIsoCenter,'Value',1);
- else
- if ~strcmp(get(handles.editIsoCenter,'String'),'multiple isoCenter')
- pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * str2num(get(handles.editIsoCenter,'String'));
- end
- end
-catch
- warning('couldnt set isocenter in getPln function')
-end
-
-handles.pln = pln;
-assignin('base','pln',pln);
-
-% parsing a string as number array
-function number = parseStringAsNum(stringIn,isVector)
-if isnumeric(stringIn)
- number = stringIn;
-else
- number = str2num(stringIn);
- if isempty(number) || length(number) > 1 && ~isVector
- warndlg(['could not parse all parameters (pln, optimization parameter)']);
- number = NaN;
- elseif isVector && iscolumn(number)
- number = number';
- end
-end
-
-
-% show error
-function handles = showError(handles,Message,ME)
-
-if nargin == 3
- %Add exception message
- if isfield(handles,'devMode') && handles.devMode
- meType = 'extended';
- else
- meType = 'basic';
- end
- Message = {Message,ME.getReport(meType,'hyperlinks','off')};
-end
-
-if isfield(handles,'ErrorDlg')
- if ishandle(handles.ErrorDlg)
- close(handles.ErrorDlg);
- end
-end
-handles.ErrorDlg = errordlg(Message);
-
-% show warning
-function handles = showWarning(handles,Message,ME)
-
-if nargin == 3
- %Add exception message
- if isfield(handles,'devMode') && handles.devMode
- meType = 'extended';
- else
- meType = 'basic';
- end
- Message = {Message,ME.getReport(meType,'hyperlinks','off')};
-end
-
-if isfield(handles,'WarnDlg')
- if ishandle(handles.WarnDlg)
- close(handles.WarnDlg);
- end
-end
-handles.WarnDlg = warndlg(Message);
-
-% check for valid machine data input file
-function flag = checkRadiationComposition(handles)
-flag = true;
-contents = cellstr(get(handles.popUpMachine,'String'));
-Machine = contents{get(handles.popUpMachine,'Value')};
-contents = cellstr(get(handles.popupRadMode,'String'));
-radMod = contents{get(handles.popupRadMode,'Value')};
-
-if isdeployed
- baseroot = [ctfroot filesep 'matRad'];
-else
- baseroot = fileparts(mfilename('fullpath'));
-end
-FoundFile = dir([baseroot filesep 'basedata' filesep radMod '_' Machine '.mat']);
-
-
-if isempty(FoundFile)
- warndlg(['No base data available for machine: ' Machine]);
- flag = false;
-end
-
-function matRadScrollWheelFcn(src,event)
-
-% get handles
-handles = guidata(src);
-
-% compute new slice
-currSlice = round(get(handles.sliderSlice,'Value'));
-newSlice = currSlice - event.VerticalScrollCount;
-
-% project to allowed set
-newSlice = min(newSlice,get(handles.sliderSlice,'Max'));
-newSlice = max(newSlice,get(handles.sliderSlice,'Min'));
-
-% update slider
-set(handles.sliderSlice,'Value',newSlice);
-
-% update handles object
-guidata(src,handles);
-
-% update plot
-UpdatePlot(handles);
-
-
-
-
-%% CALLBACKS
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% button: show DVH
-function btnDVH_Callback(~, ~, handles)
-
-resultGUI = evalin('base','resultGUI');
-Content = get(handles.popupDisplayOption,'String');
-SelectedCube = Content{get(handles.popupDisplayOption,'Value')};
-
-pln = evalin('base','pln');
-resultGUI_SelectedCube.physicalDose = resultGUI.(SelectedCube);
-
-if ~strcmp(pln.propOpt.bioOptimization,'none')
-
- %check if one of the default fields is selected
- if sum(strcmp(SelectedCube,{'physicalDose','effect','RBE,','RBExDose','alpha','beta'})) > 0
- resultGUI_SelectedCube.physicalDose = resultGUI.physicalDose;
- resultGUI_SelectedCube.RBExDose = resultGUI.RBExDose;
- else
- Idx = find(SelectedCube == '_');
- SelectedSuffix = SelectedCube(Idx(1):end);
- resultGUI_SelectedCube.physicalDose = resultGUI.(['physicalDose' SelectedSuffix]);
- resultGUI_SelectedCube.RBExDose = resultGUI.(['RBExDose' SelectedSuffix]);
- end
-end
-
-%adapt visibilty
-cst = evalin('base','cst');
-for i = 1:size(cst,1)
- cst{i,5}.Visible = handles.VOIPlotFlag(i);
-end
-
-matRad_indicatorWrapper(cst,pln,resultGUI_SelectedCube);
-
-assignin('base','cst',cst);
-
-% radio button: plot isolines labels
-function radiobtnIsoDoseLinesLabels_Callback(~, ~, handles)
-UpdatePlot(handles);
-
-% button: refresh
-function btnRefresh_Callback(hObject, ~, handles)
-
-handles = resetGUI(hObject, handles);
-
-%% parse variables from base workspace
-AllVarNames = evalin('base','who');
-handles.AllVarNames = AllVarNames;
-try
- if ismember('ct',AllVarNames) && ismember('cst',AllVarNames)
- ct = evalin('base','ct');
- cst = evalin('base','cst');
- %cst = setCstTable(handles,cst);
- cst = generateCstTable(handles,cst);
- handles.State = 1;
- cst = matRad_computeVoiContoursWrapper(cst,ct);
- assignin('base','cst',cst);
- handles = reloadGUI(hObject, handles, ct, cst);
- elseif ismember('ct',AllVarNames) && ~ismember('cst',AllVarNames)
- handles = showError(handles,'GUI OpeningFunc: could not find cst file');
- ct = evalin('base','ct');
- handles = reloadGUI(hObject,handles,ct);
- elseif ~ismember('ct',AllVarNames) && ismember('cst',AllVarNames)
- handles = showError(handles,'GUI OpeningFunc: could not find ct file');
- handles = reloadGUI(hObject, handles);
- else
- handles = reloadGUI(hObject, handles);
- end
-catch
- handles = showError(handles,'GUI OpeningFunc: Could not load ct and cst file');
- handles = reloadGUI(hObject, handles);
-end
-
-guidata(hObject, handles);
-
-
-% text box: # fractions
-function editFraction_Callback(hObject, ~, handles)
-getPlnFromGUI(handles);
-guidata(hObject,handles);
-
-% text box: stratification levels
-function editSequencingLevel_Callback(~, ~, ~)
-
-% text box: isoCenter in [mm]
-function editIsoCenter_Callback(hObject, ~, handles)
-
-pln = evalin('base','pln');
-tmpIsoCenter = str2num(get(hObject,'String'));
-
-if length(tmpIsoCenter) == 3
- if sum(any(unique(pln.propStf.isoCenter,'rows')~=tmpIsoCenter))
- pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1)*tmpIsoCenter;
- handles.State = 1;
- UpdateState(handles);
- end
-else
- handles = showError(handles,'EditIsoCenterCallback: Could not set iso center');
-end
-
-assignin('base','pln',pln);
-guidata(hObject,handles);
-
-% check box: iso center auto
-function checkIsoCenter_Callback(hObject, ~, handles)
-
-W = evalin('base','whos');
-doesPlnExist = ismember('pln',{W(:).name});
-
-if get(hObject,'Value') && doesPlnExist
- pln = evalin('base','pln');
- if ~isfield(pln.propStf,'isoCenter')
- pln.propStf.isoCenter = NaN;
- end
- tmpIsoCenter = matRad_getIsoCenter(evalin('base','cst'),evalin('base','ct'));
- if ~isequal(tmpIsoCenter,pln.propStf.isoCenter)
- pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1)*tmpIsoCenter;
- handles.State = 1;
- UpdateState(handles);
- end
- set(handles.editIsoCenter,'String',regexprep(num2str((round(tmpIsoCenter*10))./10), '\s+', ' '));
- set(handles.editIsoCenter,'Enable','off')
- assignin('base','pln',pln);
-else
- set(handles.editIsoCenter,'Enable','on')
-end
-
-% radio button: run sequencing
-function btnRunSequencing_Callback(~, ~, handles)
-getPlnFromGUI(handles);
-
-% radio button: run direct aperture optimization
-function btnRunDAO_Callback(~, ~, handles)
-getPlnFromGUI(handles);
-
-% button: set iso dose levels
-function btnSetIsoDoseLevels_Callback(hObject, ~, handles)
-prompt = {['Enter iso dose levels in [Gy]. Enter space-separated numbers, e.g. 1.5 2 3 4.98. Enter 0 to use default values']};
-if isequal(handles.IsoDose.Levels,0) || ~isvector(handles.IsoDose.Levels) || any(~isnumeric(handles.IsoDose.Levels)) || any(isnan(handles.IsoDose.Levels))
- defaultLine = {'1 2 3 '};
-else
- if isrow(handles.IsoDose.Levels)
- defaultLine = cellstr(num2str(handles.IsoDose.Levels,'%.2g '));
- else
- defaultLine = cellstr(num2str(handles.IsoDose.Levels','%.2g '));
- end
-end
-
-try
- Input = inputdlg(prompt,'Set iso dose levels ', [1 70],defaultLine);
- if ~isempty(Input)
- handles.IsoDose.Levels = (sort(str2num(Input{1})));
- if length(handles.IsoDose.Levels) == 1 && (handles.IsoDose.Levels(1) ~= 0)
- handles.IsoDose.Levels = [handles.IsoDose.Levels handles.IsoDose.Levels];
- end
- handles.IsoDose.NewIsoDoseFlag = true;
- end
-catch
- warning('Couldnt parse iso dose levels - using default values');
- handles.IsoDose.Levels = 0;
-end
-handles = updateIsoDoseLineCache(handles);
-handles.IsoDose.NewIsoDoseFlag = false;
-UpdatePlot(handles);
-guidata(hObject,handles);
-
-
-% popup menu: machine
-function popUpMachine_Callback(hObject, ~, handles)
-contents = cellstr(get(hObject,'String'));
-checkRadiationComposition(handles);
-if handles.State > 0
- pln = evalin('base','pln');
- if handles.State > 0 && ~strcmp(contents(get(hObject,'Value')),pln.machine)
- handles.State = 1;
- UpdateState(handles);
- guidata(hObject,handles);
- end
- getPlnFromGUI(handles);
-end
-
-% toolbar load button
-function toolbarLoad_ClickedCallback(hObject, eventdata, handles)
-btnLoadMat_Callback(hObject, eventdata, handles);
-
-% toolbar save button
-function toolbarSave_ClickedCallback(hObject, eventdata, handles)
-
-btnTableSave_Callback(hObject, eventdata, handles);
-
-try
-
- if handles.State > 0
- ct = evalin('base','ct');
- cst = evalin('base','cst');
- pln = evalin('base','pln');
- end
-
- if handles.State > 1
- stf = evalin('base','stf');
- dij = evalin('base','dij');
- end
-
- if handles.State > 2
- resultGUI = evalin('base','resultGUI');
- end
-
- switch handles.State
- case 1
- uisave({'cst','ct','pln'});
- case 2
- uisave({'cst','ct','pln','stf','dij'});
- case 3
- uisave({'cst','ct','pln','stf','dij','resultGUI'});
- end
-
-catch
- handles = showWarning(handles,'Could not save files');
-end
-guidata(hObject,handles);
-
-% button: about
-function btnAbout_Callback(hObject, eventdata, handles)
-
-[~,matRadVer] = matRad_version;
-
-msg{1} = ['matRad ''' matRadVer.name '''']; %Name
-if handles.eduMode
- msg{1} = [msg{1} ' Educational'];
-end
-msg{end+1} = sprintf('v%d.%d.%d',matRadVer.major,matRadVer.minor,matRadVer.patch); %Version Number
-if isdeployed
- msg{end+1} = 'Standalone Version';
-elseif ~isempty(matRadVer.branch) && ~isempty(matRadVer.commitID)
- msg{end+1} = sprintf('Git: Branch %s, commit %s',matRadVer.branch,matRadVer.commitID(1:8));
-end
-
-[env,envver] = matRad_getEnvironment();
-msg{end+1} = sprintf('Environment: %s v%s %s',env,envver,version('-release'));
-
-msg{end+1} = 'Web: www.matrad.org';
-msg{end+1} = 'E-Mail: contact@matrad.org';
-
-msgbox(msg,'About matRad');
-
-% button: close
-function figure1_CloseRequestFcn(hObject, ~, ~)
-set(0,'DefaultUicontrolBackgroundColor',[0.5 0.5 0.5]);
-selection = questdlg('Do you really want to close matRad?',...
- 'Close matRad',...
- 'Yes','No','Yes');
-
-%BackgroundColor',[0.5 0.5 0.5]
- switch selection
- case 'Yes'
- delete(hObject);
- case 'No'
- return
- end
-
-% --- Executes on button press in pushbutton_recalc.
-function pushbutton_recalc_Callback(hObject, ~, handles)
-
-% recalculation only makes sense if ...
-if evalin('base','exist(''pln'',''var'')') && ...
- evalin('base','exist(''stf'',''var'')') && ...
- evalin('base','exist(''ct'',''var'')') && ...
- evalin('base','exist(''cst'',''var'')') && ...
- evalin('base','exist(''resultGUI'',''var'')')
-
-try
-
- % indicate that matRad is busy
- % change mouse pointer to hour glass
- Figures = gcf;%findobj('type','figure');
- set(Figures, 'pointer', 'watch');
- drawnow;
- % disable all active objects
- InterfaceObj = findobj(Figures,'Enable','on');
- set(InterfaceObj,'Enable','off');
-
- % get all data from workspace
- pln = evalin('base','pln');
- stf = evalin('base','stf');
- ct = evalin('base','ct');
- cst = evalin('base','cst');
- resultGUI = evalin('base','resultGUI');
-
- % get weights of the selected cube
- Content = get(handles.popupDisplayOption,'String');
- SelectedCube = Content{get(handles.popupDisplayOption,'Value')};
- Suffix = strsplit(SelectedCube,'_');
- if length(Suffix)>1
- Suffix = ['_' Suffix{2}];
- else
- Suffix = '';
- end
-
- wField = ['w' Suffix];
-
- if ~isfield(resultGUI,wField)
- warndlg(['No exact match found for weight vector ''' wField ''' with selected dose insance. Trying common weight vector ''w'' instead!']);
- wField = 'w';
- end
-
- %Second sanity check to exclude case with no 'w' present
- if ~isfield(resultGUI,wField)
- errordlg('No weight vector found for forward dose recalculation!');
- return;
- end
-
- if sum([stf.totalNumOfBixels]) ~= length(resultGUI.(wField))
- errordlg('Selected weight vector does not correspond to current steering file (wrong number of entries/bixels!)!');
- return;
- end
-
- % change isocenter if that was changed and do _not_ recreate steering
- % information
- for i = 1:numel(pln.propStf.gantryAngles)
- stf(i).isoCenter = pln.propStf.isoCenter(i,:);
- end
-
- % recalculate influence matrix
- if strcmp(pln.radiationMode,'photons')
- dij = matRad_calcPhotonDose(ct,stf,pln,cst);
- elseif strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon')
- dij = matRad_calcParticleDose(ct,stf,pln,cst);
- end
-
- % recalculate cubes in resultGUI
- resultGUIreCalc = matRad_calcCubes(resultGUI.(wField),dij);
-
- % delete old variables to avoid confusion
- if isfield(resultGUI,'effect')
- resultGUI = rmfield(resultGUI,'effect');
- resultGUI = rmfield(resultGUI,'RBExDose');
- resultGUI = rmfield(resultGUI,'RBE');
- resultGUI = rmfield(resultGUI,'alpha');
- resultGUI = rmfield(resultGUI,'beta');
- end
-
- % overwrite the "standard" fields
- sNames = fieldnames(resultGUIreCalc);
- for j = 1:length(sNames)
- resultGUI.(sNames{j}) = resultGUIreCalc.(sNames{j});
- end
-
- % assign results to base worksapce
- assignin('base','dij',dij);
- assignin('base','resultGUI',resultGUI);
-
- handles.State = 3;
-
- % show physicalDose of newly computed state
- handles.SelectedDisplayOption = 'physicalDose';
- set(handles.popupDisplayOption,'Value',find(strcmp('physicalDose',Content)));
-
- % change state from busy to normal
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
-
- handles.cBarChanged = true;
-
- handles = updateIsoDoseLineCache(handles);
-
- UpdateState(handles);
-
- handles.rememberCurrAxes = false;
- UpdatePlot(handles);
- handles.rememberCurrAxes = true;
-
- guidata(hObject,handles);
-
-catch ME
- handles = showError(handles,'CalcDoseCallback: Error in dose recalculation!',ME);
-
- % change state from busy to normal
- set(Figures, 'pointer', 'arrow');
- set(InterfaceObj,'Enable','on');
-
- guidata(hObject,handles);
- return;
-
-end
-
-end
-
-
-% --- Executes on button press in btnSetTissue.
-function btnSetTissue_Callback(hObject, ~, handles)
-
-%check if patient is loaded
-if handles.State == 0
- return
-end
-
-%parse variables from base-workspace
-cst = evalin('base','cst');
-pln = evalin('base','pln');
-
-fileName = [pln.radiationMode '_' pln.machine];
-load(['basedata' filesep fileName]);
-
-% check for available cell types characterized by alphaX and betaX
-for i = 1:size(machine.data(1).alphaX,2)
- CellType{i} = [num2str(machine.data(1).alphaX(i)) ' ' num2str(machine.data(1).betaX(i))];
-end
-
-%fill table data array
-for i = 1:size(cst,1)
- data{i,1} = cst{i,2};
- data{i,2} = [num2str(cst{i,5}.alphaX) ' ' num2str(cst{i,5}.betaX)];
- data{i,3} = (cst{i,5}.alphaX / cst{i,5}.betaX );
-end
-
-Width = 400;
-Height = 200 + 20*size(data,1);
-ScreenSize = get(0,'ScreenSize');
-% show "set tissue parameter" window
-figHandles = get(0,'Children');
-if ~isempty(figHandles)
- IdxHandle = strcmp(get(figHandles,'Name'),'Set Tissue Parameters');
-else
- IdxHandle = [];
-end
-
-%check if window is already exists
-if any(IdxHandle)
- IdxTable = find(strcmp({figHandles(IdxHandle).Children.Type},'uitable'));
- set(figHandles(IdxHandle).Children(IdxTable), 'Data', []);
- figTissue = figHandles(IdxHandle);
- %set focus
- figure(figTissue);
-else
- figTissue = figure('Name','Set Tissue Parameters','Color',[.5 .5 .5],'NumberTitle','off','Position',...
- [ceil(ScreenSize(3)/2) ceil(ScreenSize(4)/2) Width Height]);
-end
-
-% define the tissue parameter table
-cNames = {'VOI','alphaX betaX','alpha beta ratio'};
-columnformat = {'char',CellType,'numeric'};
-
-tissueTable = uitable('Parent', figTissue,'Data', data,'ColumnEditable',[false true false],...
- 'ColumnName',cNames, 'ColumnFormat',columnformat,'Position',[50 150 10 10]);
-set(tissueTable,'CellEditCallback',@tissueTable_CellEditCallback);
-% set width and height
-currTablePos = get(tissueTable,'Position');
-currTableExt = get(tissueTable,'Extent');
-currTablePos(3) = currTableExt(3);
-currTablePos(4) = currTableExt(4);
-set(tissueTable,'Position',currTablePos);
-
-% define two buttons with callbacks
-uicontrol('Parent', figTissue,'Style', 'pushbutton', 'String', 'Save&Close',...
- 'Position', [Width-(0.25*Width) 0.1 * Height 70 30],...
- 'Callback', @(hpb,eventdata)SaveTissueParameters(hpb,eventdata,handles,tissueTable));
-
-uicontrol('Parent', figTissue,'Style', 'pushbutton', 'String', 'Cancel&Close',...
- 'Position', [Width-(0.5*Width) 0.1 * Height 80 30],...
- 'Callback', 'close');
-
-guidata(hObject,handles);
-UpdateState(handles);
-
-
-function SaveTissueParameters(~, ~, handles,tissueTable)
-cst = evalin('base','cst');
-% get handle to uiTable
-
-% retrieve data from uitable
-data = get(tissueTable,'data');
-
-for i = 1:size(cst,1)
- for j = 1:size(data,1)
- if strcmp(cst{i,2},data{j,1})
- alphaXBetaX = str2num(data{j,2});
- cst{i,5}.alphaX = alphaXBetaX(1);
- cst{i,5}.betaX = alphaXBetaX(2);
- end
- end
-end
-assignin('base','cst',cst);
-close(get(tissueTable,'Parent'));
-handles.State = 2;
-UpdateState(handles);
-
-
-function tissueTable_CellEditCallback(hObject, eventdata, ~)
-if eventdata.Indices(2) == 2
- alphaXBetaX = str2num(eventdata.NewData);
- data = get(hObject,'Data');
- data{eventdata.Indices(1),3} = alphaXBetaX(1)/alphaXBetaX(2);
- set(hObject,'Data',data);
-end
-
-% --- Executes on button press in btnSaveToGUI.
-function btnSaveToGUI_Callback(hObject, ~, handles)
-
-Width = 400;
-Height = 200;
-ScreenSize = get(0,'ScreenSize');
-
-% show "Provide result name" window
-figHandles = get(0,'Children');
-if ~isempty(figHandles)
- IdxHandle = strcmp(get(figHandles,'Name'),'Provide result name');
-else
- IdxHandle = [];
-end
-
-%check if window is already exists
-if any(IdxHandle)
- figDialog = figHandles(IdxHandle);
- %set focus
- figure(figDialog);
-else
- figDialog = dialog('Position',[ceil(ScreenSize(3)/2) ceil(ScreenSize(4)/2) Width Height],'Name','Provide result name','Color',[0.5 0.5 0.5]);
-
- uicontrol('Parent',figDialog,...
- 'Style','text',...
- 'Position',[20 Height - (0.35*Height) 350 60],...
- 'String','Please provide a decriptive name for your optimization result:','FontSize',10,'BackgroundColor',[0.5 0.5 0.5]);
-
- uicontrol('Parent',figDialog,...
- 'Style','edit',...
- 'Position',[30 60 350 60],...
- 'String','Please enter name here...','FontSize',10,'BackgroundColor',[0.55 0.55 0.55]);
-
- uicontrol('Parent', figDialog,'Style', 'pushbutton', 'String', 'Save','FontSize',10,...
- 'Position', [0.42*Width 0.1 * Height 70 30],...
- 'Callback', @(hpb,eventdata)SaveResultToGUI(hpb,eventdata,guidata(hpb)));
-end
-
-uiwait(figDialog);
-guidata(hObject, handles);
-UpdateState(handles)
-UpdatePlot(handles)
-
-
-function SaveResultToGUI(~, ~, ~)
-AllFigHandles = get(0,'Children');
-ixHandle = strcmp(get(AllFigHandles,'Name'),'Provide result name');
-uiEdit = get(AllFigHandles(ixHandle),'Children');
-
-if strcmp(get(uiEdit(2),'String'),'Please enter name here...')
-
- formatOut = 'mmddyyHHMM';
- Suffix = ['_' datestr(now,formatOut)];
-else
- % delete special characters
- Suffix = get(uiEdit(2),'String');
- logIx = isstrprop(Suffix,'alphanum');
- Suffix = ['_' Suffix(logIx)];
-end
-
-pln = evalin('base','pln');
-resultGUI = evalin('base','resultGUI');
-
-if isfield(resultGUI,'physicalDose')
- resultGUI.(['physicalDose' Suffix]) = resultGUI.physicalDose;
-end
-if isfield(resultGUI,'w')
- resultGUI.(['w' Suffix]) = resultGUI.w;
-end
-
-
-if ~strcmp(pln.propOpt.bioOptimization,'none')
-
- if isfield(resultGUI,'RBExDose')
- resultGUI.(['RBExDose' Suffix]) = resultGUI.RBExDose;
- end
-
- if strcmp(pln.radiationMode,'carbon') == 1
- if isfield(resultGUI,'effect')
- resultGUI.(['effect' Suffix])= resultGUI.effect;
- end
-
- if isfield(resultGUI,'RBE')
- resultGUI.(['RBE' Suffix]) = resultGUI.RBE;
- end
- if isfield(resultGUI,'alpha')
- resultGUI.(['alpha' Suffix]) = resultGUI.alpha;
- end
- if isfield(resultGUI,'beta')
- resultGUI.(['beta' Suffix]) = resultGUI.beta;
- end
- end
-end
-
-close(AllFigHandles(ixHandle));
-assignin('base','resultGUI',resultGUI);
-
-% precompute contours of VOIs
-function cst = precomputeContours(ct,cst)
-mask = zeros(ct.cubeDim); % create zero cube with same dimeonsions like dose cube
-for s = 1:size(cst,1)
- cst{s,7} = cell(max(ct.cubeDim(:)),3);
- mask(:) = 0;
- mask(cst{s,4}{1}) = 1;
- for slice = 1:ct.cubeDim(1)
- if sum(sum(mask(slice,:,:))) > 0
- cst{s,7}{slice,1} = contourc(squeeze(mask(slice,:,:)),.5*[1 1]);
- end
- end
- for slice = 1:ct.cubeDim(2)
- if sum(sum(mask(:,slice,:))) > 0
- cst{s,7}{slice,2} = contourc(squeeze(mask(:,slice,:)),.5*[1 1]);
- end
- end
- for slice = 1:ct.cubeDim(3)
- if sum(sum(mask(:,:,slice))) > 0
- cst{s,7}{slice,3} = contourc(squeeze(mask(:,:,slice)),.5*[1 1]);
- end
- end
-end
-
-%Update IsodoseLines
-function handles = updateIsoDoseLineCache(handles)
-resultGUI = evalin('base','resultGUI');
-% select first cube if selected option does not exist
-if ~isfield(resultGUI,handles.SelectedDisplayOption)
- CubeNames = fieldnames(resultGUI);
- dose = resultGUI.(CubeNames{1,1});
-else
- dose = resultGUI.(handles.SelectedDisplayOption);
-end
-
-%if function is called for the first time then set display parameters
-if isempty(handles.dispWindow{3,2})
- handles.dispWindow{3,1} = [min(dose(:)) max(dose(:))]; % set default dose range
- handles.dispWindow{3,2} = [min(dose(:)) max(dose(:))]; % set min max values
-end
-
-minMaxRange = handles.dispWindow{3,1};
-% if upper colorrange is defined then use it otherwise 120% iso dose
- upperMargin = 1;
-if abs((max(dose(:)) - handles.dispWindow{3,1}(1,2))) < 0.01 * max(dose(:))
- upperMargin = 1.2;
-end
-
-if (length(handles.IsoDose.Levels) == 1 && handles.IsoDose.Levels(1,1) == 0) || ~handles.IsoDose.NewIsoDoseFlag
- vLevels = [0.1:0.1:0.9 0.95:0.05:upperMargin];
- referenceDose = (minMaxRange(1,2))/(upperMargin);
- handles.IsoDose.Levels = minMaxRange(1,1) + (referenceDose-minMaxRange(1,1)) * vLevels;
-end
-handles.IsoDose.Contours = matRad_computeIsoDoseContours(dose,handles.IsoDose.Levels);
-
-
-
-%% CREATE FUNCTIONS
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% popup menu: machine
-function popUpMachine_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-% text box: max value
-function txtMaxVal_CreateFcn(hObject, ~, ~)
-
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-% text box: edit iso center
-function editIsoCenter_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-% text box: stratification levels
-function editSequencingLevel_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function slicerPrecision_CreateFcn(hObject, ~, ~)
-if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor',[.9 .9 .9]);
-end
-
-function editBixelWidth_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function editGantryAngle_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function editCouchAngle_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function popupRadMode_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function editFraction_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function popupPlane_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function sliderSlice_CreateFcn(hObject, ~, ~)
-if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor',[.9 .9 .9]);
-end
-
-function popupTypeOfPlot_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function popupDisplayOption_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function sliderBeamSelection_CreateFcn(hObject, ~, ~)
-if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor',[.9 .9 .9]);
-end
-
-function listBoxCmd_CreateFcn(hObject, ~, ~)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-function sliderOffset_CreateFcn(hObject, ~, ~)
-if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor',[.9 .9 .9]);
-end
-
-% --- Executes on selection change in legendTable.
-function legendTable_Callback(hObject, eventdata, handles)
-% hObject handle to legendTable (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns legendTable contents as cell array
-% contents{get(hObject,'Value')} returns selected item from legendTable
-cst = evalin('base','cst');
-
-idx = get(hObject,'Value');
-clr = dec2hex(round(cst{idx,5}.visibleColor(:)*255),2)';
-clr = ['#';clr(:)]';
-
-%Get the string entries
-tmpString = get(handles.legendTable,'String');
-
-if handles.VOIPlotFlag(idx)
- handles.VOIPlotFlag(idx) = false;
- cst{idx,5}.Visible = false;
- tmpString{idx} = [''];
-elseif ~handles.VOIPlotFlag(idx)
- handles.VOIPlotFlag(idx) = true;
- cst{idx,5}.Visible = true;
- tmpString{idx} = [''];
-end
-set(handles.legendTable,'String',tmpString);
-
-% update cst in workspace accordingly
-assignin('base','cst',cst)
-
-guidata(hObject, handles);
-UpdatePlot(handles)
-
-% --- Executes during object creation, after setting all properties.
-function legendTable_CreateFcn(hObject, eventdata, handles)
-% hObject handle to legendTable (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: listbox controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on button press in importDoseButton.
-function importDoseButton_Callback(hObject,eventdata, handles)
-% hObject handle to importDoseButton (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-extensions{1} = '*.nrrd';
-[filenames,filepath,~] = uigetfile(extensions,'MultiSelect','on');
-
-if ~iscell(filenames)
- tmp = filenames;
- filenames = cell(1);
- filenames{1} = tmp;
-end
-
-ct = evalin('base','ct');
-resultGUI = evalin('base','resultGUI');
-
-for filename = filenames
- [~,name,~] = fileparts(filename{1});
- [cube,~] = matRad_readCube(fullfile(filepath,filename{1}));
- if ~isequal(ct.cubeDim, size(cube))
- errordlg('Dimensions of the imported cube do not match with ct','Import failed!','modal');
- continue;
- end
-
- fieldname = ['import_' matlab.lang.makeValidName(name, 'ReplacementStyle','delete')];
- resultGUI.(fieldname) = cube;
-end
-
-assignin('base','resultGUI',resultGUI);
-btnRefresh_Callback(hObject, eventdata, handles)
-
-% --- Executes on button press in pushbutton_importFromBinary.
-function pushbutton_importFromBinary_Callback(hObject, eventdata, handles)
-% hObject handle to pushbutton_importFromBinary (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-try
- % delete existing workspace - parse variables from base workspace
- set(handles.popupDisplayOption,'String','no option available');
- AllVarNames = evalin('base','who');
- RefVarNames = {'ct','cst','pln','stf','dij','resultGUI'};
- for i = 1:length(RefVarNames)
- if sum(ismember(AllVarNames,RefVarNames{i}))>0
- evalin('base',['clear ', RefVarNames{i}]);
- end
- end
- handles.State = 0;
-
- %call the gui
- uiwait(matRad_importGUI);
-
- %Check if we have the variables in the workspace
- if evalin('base','exist(''cst'',''var'')') == 1 && evalin('base','exist(''ct'',''var'')') == 1
- cst = evalin('base','cst');
- ct = evalin('base','ct');
- %setCstTable(handles,cst);
- generateCstTable(handles,cst);
- handles.TableChanged = false;
- set(handles.popupTypeOfPlot,'Value',1);
-
- % compute HU values
- if ~isfield(ct, 'cubeHU')
- ct = matRad_electronDensitiesToHU(ct);
- assignin('base','ct',ct);
- end
- if ~isfield(ct, 'cubeHU')
- handles.cubeHUavailable = false;
- else
- handles.cubeHUavailable = true;
- end
-
- % precompute contours
- cst = precomputeContours(ct,cst);
-
- assignin('base','ct',ct);
- assignin('base','cst',cst);
-
- if evalin('base','exist(''pln'',''var'')')
- assignin('base','pln',pln);
- setPln(handles);
- else
- getPlnFromGUI(handles);
- setPln(handles);
- end
- handles.State = 1;
- end
-
- % set slice slider
- handles.plane = get(handles.popupPlane,'value');
- if handles.State >0
- set(handles.sliderSlice,'Min',1,'Max',ct.cubeDim(handles.plane),...
- 'Value',round(ct.cubeDim(handles.plane)/2),...
- 'SliderStep',[1/(ct.cubeDim(handles.plane)-1) 1/(ct.cubeDim(handles.plane)-1)]);
- end
-
- if handles.State > 0
- % define context menu for structures
- for i = 1:size(cst,1)
- if cst{i,5}.Visible
- handles.VOIPlotFlag(i) = true;
- else
- handles.VOIPlotFlag(i) = false;
- end
- end
- end
-
- handles.dispWindow = cell(3,2);
- handles.cBarChanged = true;
-
- UpdateState(handles);
- handles.rememberCurrAxes = false;
- UpdatePlot(handles);
- handles.rememberCurrAxes = true;
-catch
- handles = showError(handles,'Binary Patient Import: Could not import data');
- UpdateState(handles);
-end
-
-guidata(hObject,handles);
-
-% --- Executes on button press in radioBtnIsoCenter.
-function radioBtnIsoCenter_Callback(hObject, eventdata, handles)
-% hObject handle to radioBtnIsoCenter (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-UpdatePlot(handles)
-% Hint: get(hObject,'Value') returns toggle state of radioBtnIsoCenter
-
-% --------------------------------------------------------------------
-function uipushtool_screenshot_ClickedCallback(hObject, eventdata, handles)
-% hObject handle to uipushtool_screenshot (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-
-tmpFig = figure('position',[100 100 700 600],'Visible','off','name','Current View');
-cBarHandle = findobj(handles.figure1,'Type','colorbar');
-if ~isempty(cBarHandle)
- new_handle = copyobj([handles.axesFig cBarHandle],tmpFig);
-else
- new_handle = copyobj(handles.axesFig,tmpFig);
-end
-
-oldPos = get(handles.axesFig,'Position');
-set(new_handle(1),'units','normalized', 'Position',oldPos);
-
-if ~isfield(handles,'lastStoragePath') || exist(handles.lastStoragePath,'dir') ~= 7
- lastStoragePath = [];
-else
- lastStoragePath = handles.lastStoragePath;
-end
-
-[filename, pathname] = uiputfile({'*.jpg;*.tif;*.png;*.gif','All Image Files'; '*.fig','MATLAB figure file'},'Save current view',[lastStoragePath 'screenshot.png']);
-
-
-
-if ~isequal(filename,0) && ~isequal(pathname,0)
- handles.lastStoragePath = pathname;
- set(gcf, 'pointer', 'watch');
- saveas(tmpFig,fullfile(pathname,filename));
- set(gcf, 'pointer', 'arrow');
- close(tmpFig);
- uiwait(msgbox('Current view has been succesfully saved!'));
-else
- uiwait(msgbox('Aborted saving, showing figure instead!'));
- set(tmpFig,'Visible','on');
-end
-
-guidata(hObject,handles);
-
-
-%% Callbacks & Functions for color setting
-function UpdateColormapOptions(handles)
-
-if isfield(handles,'colormapLocked') && handles.colormapLocked
- return;
-end
-
-selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
-
-cMapSelectionIndex = get(handles.popupmenu_chooseColormap,'Value');
-cMapStrings = get(handles.popupmenu_chooseColormap,'String');
-
-if selectionIndex > 1
- set(handles.uitoggletool8,'State','on');
-else
- set(handles.uitoggletool8,'State','off');
-end
-
-try
- if selectionIndex == 2
- ct = evalin('base','ct');
- currentMap = handles.ctColorMap;
- window = handles.dispWindow{selectionIndex,1};
- if handles.cubeHUavailable
- minMax = [min(ct.cubeHU{1}(:)) max(ct.cubeHU{1}(:))];
- else
- minMax = [min(ct.cube{1}(:)) max(ct.cube{1}(:))];
- end
- % adjust value for custom window to current
- handles.windowPresets(1).width = max(window) - min(window);
- handles.windowPresets(1).center = mean(window);
- % update full window information
- handles.windowPresets(2).width = minMax(2) - minMax(1);
- handles.windowPresets(2).center = mean(minMax);
- elseif selectionIndex == 3
- result = evalin('base','resultGUI');
- dose = result.(handles.SelectedDisplayOption);
- currentMap = handles.doseColorMap;
- minMax = [min(dose(:)) max(dose(:))];
- window = handles.dispWindow{selectionIndex,1};
- else
- window = [0 1];
- minMax = window;
- currentMap = 'bone';
- end
-catch
- window = [0 1];
- minMax = window;
- currentMap = 'bone';
-end
-
-valueRange = minMax(2) - minMax(1);
-
-windowWidth = window(2) - window(1);
-windowCenter = mean(window);
-
-%This are some arbritrary settings to configure the sliders
-sliderCenterMinMax = [minMax(1)-valueRange/2 minMax(2)+valueRange/2];
-sliderWidthMinMax = [0 valueRange*2];
-
-%if we have selected a value outside this range, we adapt the slider
-%windows
-if windowCenter < sliderCenterMinMax(1)
- sliderCenterMinMax(1) = windowCenter;
-end
-if windowCenter > sliderCenterMinMax(2)
- sliderCenterMinMax(2) = windowCenter;
-end
-if windowWidth < sliderWidthMinMax(1)
- sliderWidthMinMax(1) = windowWidth;
-end
-if windowCenter > sliderCenterMinMax(2)
- sliderWidthMinMax(2) = windowWidth;
-end
-
-
-set(handles.edit_windowCenter,'String',num2str(windowCenter,3));
-set(handles.edit_windowWidth,'String',num2str(windowWidth,3));
-set(handles.edit_windowRange,'String',num2str(window,4));
-set(handles.slider_windowCenter,'Min',sliderCenterMinMax(1),'Max',sliderCenterMinMax(2),'Value',windowCenter);
-set(handles.slider_windowWidth,'Min',sliderWidthMinMax(1),'Max',sliderWidthMinMax(2),'Value',windowWidth);
-
-cMapPopupIndex = find(strcmp(currentMap,cMapStrings));
-set(handles.popupmenu_chooseColormap,'Value',cMapPopupIndex);
-
-guidata(gcf,handles);
-
-% --- Executes on selection change in popupmenu_chooseColorData.
-function popupmenu_chooseColorData_Callback(hObject, eventdata, handles)
-% hObject handle to popupmenu_chooseColorData (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_chooseColorData contents as cell array
-% contents{get(hObject,'Value')} returns selected item from popupmenu_chooseColorData
-
-%index = get(hObject,'Value') - 1;
-
-handles.cBarChanged = true;
-
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-
-% --- Executes during object creation, after setting all properties.
-function popupmenu_chooseColorData_CreateFcn(hObject, eventdata, handles)
-% hObject handle to popupmenu_chooseColorData (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: popupmenu controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --- Executes on slider movement.
-function slider_windowCenter_Callback(hObject, eventdata, handles)
-% hObject handle to slider_windowCenter (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'Value') returns position of slider
-% get(hObject,'Min') and get(hObject,'Max') to determine range of slider
-
-newCenter = get(hObject,'Value');
-range = get(handles.slider_windowWidth,'Value');
-selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
-
-handles.dispWindow{selectionIndex,1} = [newCenter-range/2 newCenter+range/2];
-handles.cBarChanged = true;
-
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-% --- Executes during object creation, after setting all properties.
-function slider_windowCenter_CreateFcn(hObject, eventdata, handles)
-% hObject handle to slider_windowCenter (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: slider controls usually have a light gray background.
-if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor',[.9 .9 .9]);
-end
-
-set(hObject,'Value',0.5);
-
-% --- Executes on slider movement.
-function slider_windowWidth_Callback(hObject, eventdata, handles)
-% hObject handle to slider_windowWidth (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'Value') returns position of slider
-% get(hObject,'Min') and get(hObject,'Max') to determine range of slider
-
-newWidth = get(hObject,'Value');
-center = get(handles.slider_windowCenter,'Value');
-selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
-handles.dispWindow{selectionIndex,1} = [center-newWidth/2 center+newWidth/2];
-handles.cBarChanged = true;
-
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-% --- Executes during object creation, after setting all properties.
-function slider_windowWidth_CreateFcn(hObject, eventdata, handles)
-% hObject handle to slider_windowWidth (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: slider controls usually have a light gray background.
-if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor',[.9 .9 .9]);
-end
-
-set(hObject,'Value',1.0);
-
-
-% --- Executes on selection change in popupmenu_chooseColormap.
-function popupmenu_chooseColormap_Callback(hObject, eventdata, handles)
-% hObject handle to popupmenu_chooseColormap (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_chooseColormap contents as cell array
-% contents{get(hObject,'Value')} returns selected item from popupmenu_chooseColormap
-
-index = get(hObject,'Value');
-strings = get(hObject,'String');
-
-selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
-
-switch selectionIndex
- case 2
- handles.ctColorMap = strings{index};
- case 3
- handles.doseColorMap = strings{index};
- otherwise
-end
-
-handles.cBarChanged = true;
-
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-% --- Executes during object creation, after setting all properties.
-function popupmenu_chooseColormap_CreateFcn(hObject, eventdata, handles)
-% hObject handle to popupmenu_chooseColormap (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: popupmenu controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-function edit_windowRange_Callback(hObject, eventdata, handles)
-% hObject handle to edit_windowRange (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of edit_windowRange as text
-% str2double(get(hObject,'String')) returns contents of edit_windowRange as a double
-
-selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
-vRange = str2num(get(hObject,'String'));
-% matlab adds a zero in the beginning when text field is changed
-if numel(vRange) == 3
- vRange = vRange(vRange~=0);
-end
-
-handles.dispWindow{selectionIndex,1} = sort(vRange);
-
-handles.cBarChanged = true;
-
- % compute new iso dose lines
-if selectionIndex > 2
- guidata(hObject,handles);
- handles = updateIsoDoseLineCache(handles);
-end
-
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-% --- Executes during object creation, after setting all properties.
-function edit_windowRange_CreateFcn(hObject, eventdata, handles)
-% hObject handle to edit_windowRange (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-set(hObject,'String','0 1');
-
-
-function edit_windowCenter_Callback(hObject, eventdata, handles)
-% hObject handle to edit_windowCenter (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of edit_windowCenter as text
-% str2double(get(hObject,'String')) returns contents of edit_windowCenter as a double
-
-newCenter = str2double(get(hObject,'String'));
-width = get(handles.slider_windowWidth,'Value');
-selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
-handles.dispWindow{selectionIndex,1} = [newCenter-width/2 newCenter+width/2];
-handles.cBarChanged = true;
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-% --- Executes during object creation, after setting all properties.
-function edit_windowCenter_CreateFcn(hObject, eventdata, handles)
-% hObject handle to edit_windowCenter (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-
-function edit_windowWidth_Callback(hObject, eventdata, handles)
-% hObject handle to edit_windowWidth (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'String') returns contents of edit_windowWidth as text
-% str2double(get(hObject,'String')) returns contents of edit_windowWidth as a double
-
-newWidth = str2double(get(hObject,'String'));
-center = get(handles.slider_windowCenter,'Value');
-selectionIndex = get(handles.popupmenu_chooseColorData,'Value');
-handles.dispWindow{selectionIndex,1} = [center-newWidth/2 center+newWidth/2];
-handles.cBarChanged = true;
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-
-% --- Executes during object creation, after setting all properties.
-function edit_windowWidth_CreateFcn(hObject, eventdata, handles)
-% hObject handle to edit_windowWidth (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: edit controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-
-% --------------------------------------------------------------------
-function uitoggletool8_ClickedCallback(hObject, eventdata, handles)
-% hObject handle to uitoggletool8 (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-%Check if on or off
-val = strcmp(get(hObject,'State'),'on');
-
-%Now we have to apply the new selection to our colormap options panel
-if ~val
- newSelection = 1;
-else
- %Chooses the selection from the highest state
- selections = get(handles.popupmenu_chooseColorData,'String');
- newSelection = numel(selections);
-end
-set(handles.popupmenu_chooseColorData,'Value',newSelection);
-
-handles.cBarChanged = true;
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-% --- Executes on slider movement.
-function sliderOpacity_Callback(hObject, eventdata, handles)
-% hObject handle to sliderOpacity (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-handles.doseOpacity = get(hObject,'Value');
-guidata(hObject,handles);
-UpdatePlot(handles);
-
-% --- Executes during object creation, after setting all properties.
-function sliderOpacity_CreateFcn(hObject, eventdata, handles)
-% hObject handle to sliderOpacity (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: slider controls usually have a light gray background.
-if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor',[.9 .9 .9]);
-end
-
-%% Data Cursors
-function cursorText = dataCursorUpdateFunction(obj,event_obj)
-% Display the position of the data cursor
-% obj Currently not used (empty)
-% event_obj Handle to event object
-% output_txt Data cursor text string (string or cell array of strings).
-
-target = findall(0,'Name','matRadGUI');
-
-% Get GUI data (maybe there is another way?)
-handles = guidata(target);
-
-% position of the data point to label
-pos = get(event_obj,'Position');
-
-%Different behavior for image and profile plot
-if get(handles.popupTypeOfPlot,'Value')==1 %Image view
- cursorText = cell(0,1);
- try
- if handles.State >= 1
- plane = get(handles.popupPlane,'Value');
- slice = round(get(handles.sliderSlice,'Value'));
-
- %Get the CT values
- ct = evalin('base','ct');
-
- %We differentiate between pos and ix, since the user may put
- %the datatip on an isoline which returns a continous position
- cubePos = zeros(1,3);
- cubePos(plane) = slice;
- cubePos(1:end ~= plane) = fliplr(pos);
- cubeIx = round(cubePos);
-
- %Here comes the index permutation stuff
- %Cube Index
- cursorText{end+1,1} = ['Cube Index: ' mat2str(cubeIx)];
- %Space Coordinates
- coords = zeros(1,3);
- coords(1) = cubePos(2)*ct.resolution.y;
- coords(2) = cubePos(1)*ct.resolution.x;
- coords(3) = cubePos(3)*ct.resolution.z;
- cursorText{end+1,1} = ['Space Coordinates: ' mat2str(coords,5) ' mm'];
-
- ctVal = ct.cubeHU{1}(cubeIx(1),cubeIx(2),cubeIx(3));
- cursorText{end+1,1} = ['HU Value: ' num2str(ctVal,3)];
- end
-
- %Add dose information if available
- if handles.State == 3
- %get result structure
- result = evalin('base','resultGUI');
-
- %Get all result names from popup
- resultNames = get(handles.popupDisplayOption,'String');
-
- %Display all values of fields found in the resultGUI struct
- for runResult = 1:numel(resultNames)
- name = resultNames{runResult};
- if isfield(result,name)
- field = result.(name);
- val = field(cubeIx(1),cubeIx(2),cubeIx(3));
- cursorText{end+1,1} = [name ': ' num2str(val,3)];
- end
- end
- end
- catch
- cursorText{end+1,1} = 'Error while retreiving Data!';
- end
-else %Profile view
- cursorText = cell(2,1);
- cursorText{1} = ['Radiological Depth: ' num2str(pos(1),3) ' mm'];
- cursorText{2} = [get(target,'DisplayName') ': ' num2str(pos(2),3)];
-end
-
-
-
-% --- Executes on selection change in popMenuBioOpt.
-function popMenuBioOpt_Callback(hObject, ~, handles)
-pln = evalin('base','pln');
-contentBioOpt = get(handles.popMenuBioOpt,'String');
-NewBioOptimization = contentBioOpt(get(handles.popMenuBioOpt,'Value'),:);
-
-if handles.State > 0
- if (strcmp(pln.propOpt.bioOptimization,'LEMIV_effect') && strcmp(NewBioOptimization,'LEMIV_RBExD')) ||...
- (strcmp(pln.propOpt.bioOptimization,'LEMIV_RBExD') && strcmp(NewBioOptimization,'LEMIV_effect'))
- % do nothing - re-optimization is still possible
- elseif ((strcmp(pln.propOpt.bioOptimization,'const_RBE') && strcmp(NewBioOptimization,'none')) ||...
- (strcmp(pln.propOpt.bioOptimization,'none') && strcmp(NewBioOptimization,'const_RBE'))) && isequal(pln.radiationMode,'protons')
- % do nothing - re-optimization is still possible
- else
- handles.State = 1;
- end
-end
-getPlnFromGUI(handles);
-
-UpdateState(handles);
-guidata(hObject,handles);
-
-% --- Executes during object creation, after setting all properties.
-function popMenuBioOpt_CreateFcn(hObject, eventdata, handles)
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-% --- Executes on button press in btn3Dview.
-function btn3Dview_Callback(hObject, eventdata, handles)
-% hObject handle to btn3Dview (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-if ~isfield(handles,'axesFig3D') || ~isfield(handles,'axesFig3D') || ~isgraphics(handles.axesFig3D)
- handles.fig3D = figure('Name','matRad 3D View');
- handles.axesFig3D = axes('Parent',handles.fig3D);
- view(handles.axesFig3D,3);
- try
- ct = evalin('base','ct');
-
- xlim(handles.axesFig3D,[0 ct.resolution.x*ct.cubeDim(2)]);
- ylim(handles.axesFig3D,[0 ct.resolution.y*ct.cubeDim(1)]);
- zlim(handles.axesFig3D,[0 ct.resolution.z*ct.cubeDim(3)]);
- catch
- end
-end
-%end
-
-UpdatePlot(handles);
-
-guidata(hObject,handles);
-
-% --- Executes on button press in radiobtnCT.
-function radiobtnCT_Callback(hObject, eventdata, handles)
-% hObject handle to radiobtnCT (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of radiobtnCT
-UpdatePlot(handles)
-
-% --- Executes on button press in radiobtnPlan.
-function radiobtnPlan_Callback(hObject, eventdata, handles)
-% hObject handle to radiobtnPlan (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of radiobtnPlan
-UpdatePlot(handles)
-
-
-% --- Executes on selection change in popupmenu_windowPreset.
-function popupmenu_windowPreset_Callback(hObject, eventdata, handles)
-% hObject handle to popupmenu_windowPreset (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_windowPreset contents as cell array
-% contents{get(hObject,'Value')} returns selected item from popupmenu_windowPreset
-
-selectionIndexCube = 2; % working on ct only
-selectionIndexWindow = get(handles.popupmenu_windowPreset,'Value');
-newCenter = handles.windowPresets(selectionIndexWindow).center;
-newWidth = handles.windowPresets(selectionIndexWindow).width;
-
-handles.dispWindow{selectionIndexCube,1} = [newCenter - newWidth/2 newCenter + newWidth/2];
-handles.cBarChanged = true;
-guidata(hObject,handles);
-UpdatePlot(handles);
-UpdateColormapOptions(handles);
-
-
-% --- Executes during object creation, after setting all properties.
-function popupmenu_windowPreset_CreateFcn(hObject, eventdata, handles)
-% hObject handle to popupmenu_windowPreset (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: popupmenu controls usually have a white background on Windows.
-% See ISPC and COMPUTER.
-if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor','white');
-end
-
-% setup ct window list
-% data and values from CERR https://github.com/adityaapte/CERR
-windowNames = {'Custom','Full','Abd/Med', 'Head', 'Liver', 'Lung', 'Spine', 'Vrt/Bone'};
-windowCenter = {NaN, NaN, -10, 45, 80, -500, 30, 400};
-windowWidth = {NaN, NaN, 330, 125, 305, 1500, 300, 1500};
-windowPresets = cell2struct([windowNames', windowCenter', windowWidth'], {'name', 'center', 'width'},2);
-
-
-handles.windowPresets = windowPresets;
-
-selectionList = {windowPresets(:).name};
-set(hObject,'String',selectionList(:));
-set(hObject,'Value',1);
-
-
-guidata(hObject,handles);
-
-% --- Executes on button press in checkbox_lockColormap.
-function checkbox_lockColormap_Callback(hObject, eventdata, handles)
-% hObject handle to checkbox_lockColormap (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of checkbox_lockColormap
-handles.colormapLocked = get(hObject,'Value');
-
-if handles.colormapLocked
- state = 'Off'; %'Inactive';
-else
- state = 'On';
-end
-
-set(handles.popupmenu_chooseColorData,'Enable',state);
-set(handles.popupmenu_windowPreset,'Enable',state);
-set(handles.slider_windowWidth,'Enable',state);
-set(handles.slider_windowCenter,'Enable',state);
-set(handles.edit_windowWidth,'Enable',state);
-set(handles.edit_windowCenter,'Enable',state);
-set(handles.edit_windowRange,'Enable',state);
-set(handles.popupmenu_chooseColormap,'Enable',state);
-
-
-guidata(hObject,handles);
-
-
-function cst = updateStructureTable(handles,cst)
-colorAssigned = true;
-
-% check whether all structures have an assigned color
-for i = 1:size(cst,1)
- if ~isfield(cst{i,5},'visibleColor')
- colorAssigned = false;
- break;
- elseif isempty(cst{i,5}.visibleColor)
- colorAssigned = false;
- break;
- end
-end
-
-% assign color if color assignment is not already present or inconsistent
-if colorAssigned == false
- m = 64;
- colorStep = ceil(m/size(cst,1));
- colors = colorcube(colorStep*size(cst,1));
- % spread individual VOI colors in the colorcube color palette
- colors = colors(1:colorStep:end,:);
-
- for i = 1:size(cst,1)
- cst{i,5}.visibleColor = colors(i,:);
- end
-end
-
-for s = 1:size(cst,1)
- handles.VOIPlotFlag(s) = cst{s,5}.Visible;
- clr = dec2hex(round(cst{s,5}.visibleColor(:)*255),2)';
- clr = ['#';clr(:)]';
- if handles.VOIPlotFlag(s)
- tmpString{s} = [''];
- else
- tmpString{s} = [''];
- end
-end
-set(handles.legendTable,'String',tmpString);
-
-%-- generates the CST table
-function cst = generateCstTable(handles,cst)
-
-cst = updateStructureTable(handles,cst);
-
-cstPanel = handles.uipanel3;
-
-%Get units in pixels to to calculate aspect ratio
-cstPanelPosUnit = get(cstPanel,'Units');
-set(cstPanel,'Units','pixels');
-cstPanelPosPix = get(cstPanel,'Position');
-set(cstPanel,'Units',cstPanelPosUnit);
-
-aspectRatio = cstPanelPosPix(3) / cstPanelPosPix(4);
-
-%Parameters for line height
-objHeight = 0.095;% 22;
-lineHeight = 0.1; %25; %Height of a table line
-yTopSep = 0.12;%40; %Separation of the first line from the top
-tableViewHeight = 1 - yTopSep;
-
-%Define the widths of the fields relatively
-buttonW = objHeight / aspectRatio; % Make button squared
-nameW = 3.5*buttonW;
-typeW = 3*buttonW;
-opW = buttonW;
-functionW = 6*buttonW;
-penaltyW = 2*buttonW;
-paramTitleW = 4*buttonW;
-paramW = 2*buttonW;
-fieldSep = 0.25*buttonW; %Separation between fields horizontally
-
-
-%Scrollbar
-%Check if a scrollbar already exists to get possible position of slider
-cstPanelChildren = get(cstPanel,'Children');
-cstVertTableScroll = findobj(cstPanelChildren,'Style','slider');
-if isempty(cstVertTableScroll)
- sliderPos = 0;
-else
- sliderPos = get(cstVertTableScroll,'Max') - get(cstVertTableScroll,'Value');
-end
-
-ypos = @(c) tableViewHeight - c*lineHeight + sliderPos;
-
-%Clean the whole panel for new setup
-delete(cstPanelChildren);
-
-%Creates a dummy axis to allow for the use of textboxes instead of uicontrol to be able to use the (la)tex interpreter
-tmpAxes = axes('Parent',cstPanel,'units','normalized','position',[0 0 1 1],'visible','off');
-
-organTypes = {'OAR', 'TARGET'};
-
-%columnname = {'VOI name','VOI type','priority','obj. / const.'};%,'penalty','dose', 'EUD','volume','robustness'};
-
-%Get all Classes & classNames
-mpkgObjectives = meta.package.fromName('DoseObjectives');
-mpkgConstraints = meta.package.fromName('DoseConstraints');
-classList = [mpkgObjectives.ClassList; mpkgConstraints.ClassList];
-
-classList = classList(not([classList.Abstract]));
-
-%Now get the "internal" name from the properties
-classNames = cell(2,numel(classList));
-for clIx = 1:numel(classList)
- cl = classList(clIx);
- pList = cl.PropertyList; %Get List of all properties
- pNameIx = arrayfun(@(p) strcmp(p.Name,'name'),pList); %get index of the "name" property
- p = pList(pNameIx); %select name property
- pName = p.DefaultValue; % get value / name
- classNames(:,clIx) = {cl.Name; pName}; %Store class name and display name
-end
-
-numOfObjectives = sum(cellfun(@numel,cst(:,6)));
-
-cnt = 0;
-
-newline = '\n';
-
-%Setup Headlines
-xPos = 0.01; %5
-
-h = uicontrol(cstPanel,'Style','text','String','+/-','Units','normalized','Position',[xPos ypos(cnt) buttonW objHeight],'TooltipString','Remove or add Constraint or Objective');
-tmp_pos = get(h,'Position');
-xPos = xPos + tmp_pos(3) + fieldSep;
-h = uicontrol(cstPanel,'Style','text','String','VOI name','Units','normalized','Position',[xPos ypos(cnt) nameW objHeight],'TooltipString','Name of the structure with objective/constraint');
-tmp_pos = get(h,'Position');
-xPos = xPos + tmp_pos(3) + fieldSep;
-h = uicontrol(cstPanel,'Style','text','String','VOI type','Units','normalized','Position',[xPos ypos(cnt) typeW objHeight],'TooltipString','Segmentation Classification');
-tmp_pos = get(h,'Position');
-xPos = xPos + tmp_pos(3) + fieldSep;
-h = uicontrol(cstPanel,'Style','text','String','OP','Units','normalized','Position',[xPos ypos(cnt) opW objHeight],'TooltipString',['Overlap Priority' char(10) '(Smaller number overlaps higher number)']);
-tmp_pos = get(h,'Position');
-xPos = xPos + tmp_pos(3) + fieldSep;
-h = uicontrol(cstPanel,'Style','text','String','Function','Units','normalized','Position',[xPos ypos(cnt) functionW objHeight],'TooltipString','Objective/Constraint function type');
-tmp_pos = get(h,'Position');
-xPos = xPos + tmp_pos(3) + fieldSep;
-h = uicontrol(cstPanel,'Style','text','String','p','Units','normalized','Position',[xPos ypos(cnt) penaltyW objHeight],'TooltipString','Optimization penalty');
-tmp_pos = get(h,'Position');
-xPos = xPos + tmp_pos(3) + fieldSep;
-h = uicontrol(cstPanel,'Style','text','String','| Parameters','Units','normalized','Position',[xPos ypos(cnt) paramTitleW objHeight],'TooltipString','List of parameters','HorizontalAlignment','left');
-tmp_pos = get(h,'Position');
-xPos = xPos + tmp_pos(3) + fieldSep;
-cnt = cnt + 1;
-
-%Create Objectives / Constraints controls
-for i = 1:size(cst,1)
- if strcmp(cst(i,3),'IGNORED')~=1
- %Compatibility Layer for old objective format
- if isstruct(cst{i,6})
- cst{i,6} = arrayfun(@matRad_DoseOptimizationFunction.convertOldOptimizationStruct,cst{i,6},'UniformOutput',false);
- end
- for j=1:numel(cst{i,6})
-
- obj = cst{i,6}{j};
-
- %Convert to class if not
- if ~isa(obj,'matRad_DoseOptimizationFunction')
- try
- obj = matRad_DoseOptimizationFunction.createInstanceFromStruct(obj);
- catch ME
- warning('Objective/Constraint not valid!\n%s',ME.message)
- continue;
- end
- end
-
- %VOI
- xPos = 0.01;%5;
-
- h = uicontrol(cstPanel,'Style','pushbutton','String','-','Units','normalized','Position',[xPos ypos(cnt) buttonW objHeight],'TooltipString','Remove Objective/Constraint','Callback',{@btObjRemove_Callback,handles},...
- 'UserData',[i,j]);
- tmp_pos = get(h,'Position');
- xPos = xPos + tmp_pos(3) + fieldSep;
- h = uicontrol(cstPanel','Style','edit','String',cst{i,2},'Units','normalized','Position',[xPos ypos(cnt) nameW objHeight],'TooltipString','Name',...
- 'Enable','inactive',... %Disable editing of name atm
- 'UserData',[i,2],'Callback',{@editCstParams_Callback,handles}); %Callback added, however, editing is disabled atm
- tmp_pos = get(h,'Position');
- xPos = xPos + tmp_pos(3) + fieldSep;
- h = uicontrol(cstPanel,'Style','popupmenu','String',organTypes','Value',find(strcmp(cst{i,3},organTypes)),'Units','normalized','Position',[xPos ypos(cnt) typeW objHeight],'TooltipString','Segmentation Classification',...
- 'UserData',[i,3],'Callback',{@editCstParams_Callback,handles});
- tmp_pos = get(h,'Position');
- xPos = xPos + tmp_pos(3) + fieldSep;
- h = uicontrol(cstPanel,'Style','edit','String',num2str(cst{i,5}.Priority),'Units','normalized','Position',[xPos ypos(cnt) opW objHeight],'TooltipString',['Overlap Priority' newline '(Smaller number overlaps higher number)'],...
- 'UserData',[i,5],'Callback',{@editCstParams_Callback,handles});
- tmp_pos = get(h,'Position');
- xPos = xPos + tmp_pos(3) + fieldSep;
-
- h = uicontrol(cstPanel,'Style','popupmenu','String',classNames(2,:)','Value',find(strcmp(obj.name,classNames(2,:))),'Units','normalized','Position',[xPos ypos(cnt) functionW objHeight],'TooltipString','Select Objective/Constraint',...
- 'UserData',{[i,j],classNames(1,:)},'Callback',{@changeObjFunction_Callback,handles});
- tmp_pos = get(h,'Position');
- xPos = xPos + tmp_pos(3) + fieldSep;
-
- %Check if we have an objective to display penalty
- if isa(obj,'DoseObjectives.matRad_DoseObjective')
- h = uicontrol(cstPanel,'Style','edit','String',num2str(obj.penalty),'Units','normalized','Position',[xPos ypos(cnt) penaltyW objHeight],'TooltipString','Objective Penalty','UserData',[i,j,0],'Callback',{@editObjParam_Callback,handles});
- else
- h = uicontrol(cstPanel,'Style','edit','String','----','Units','normalized','Position',[xPos ypos(cnt) penaltyW objHeight],'Enable','off');
- end
- tmp_pos = get(h,'Position');
- xPos = xPos + tmp_pos(3) + fieldSep;
-
- %Iterate through parameters
- for p = 1:numel(obj.parameterNames)
- h = text('Parent',tmpAxes,'String',['| ' obj.parameterNames{p} ':'],'VerticalAlignment','middle','Units','normalized','Position',[xPos ypos(cnt)+lineHeight/2],'Interpreter','tex','FontWeight','normal',...
- 'FontSize',get(cstPanel,'FontSize'),'FontName',get(cstPanel,'FontName'),'FontUnits',get(cstPanel,'FontUnits'),'FontWeight','normal');
- tmp_pos = get(h,'Extent');
- xPos = xPos + tmp_pos(3) + fieldSep;
-
- %Check if we have a cell and therefore a parameter list
- if iscell(obj.parameterTypes{p})
- h = uicontrol(cstPanel,'Style','popupmenu','String',obj.parameterTypes{p}','Value',obj.parameters{p},'TooltipString',obj.parameterNames{p},'Units','normalized','Position',[xPos ypos(cnt) paramW*2 objHeight],'UserData',[i,j,p],'Callback',{@editObjParam_Callback,handles});
- else
- h = uicontrol(cstPanel,'Style','edit','String',num2str(obj.parameters{p}),'TooltipString',obj.parameterNames{p},'Units','normalized','Position',[xPos ypos(cnt) paramW objHeight],'UserData',[i,j,p],'Callback',{@editObjParam_Callback,handles});
- end
-
- tmp_pos = get(h,'Position');
- xPos = xPos + tmp_pos(3) + fieldSep;
- end
-
- cnt = cnt +1;
- end
- end
-end
-xPos = 0.01; %5
-hAdd = uicontrol(cstPanel,'Style','pushbutton','String','+','Units','normalized','Position',[xPos ypos(cnt) buttonW objHeight],'TooltipString','Add Objective/Constraint','Callback',{@btObjAdd_Callback,handles});
-tmp_pos = get(hAdd,'Position');
-xPos = xPos + tmp_pos(3) + fieldSep;
-h = uicontrol(cstPanel,'Style','popupmenu','String',cst(:,2)','Units','normalized','Position',[xPos ypos(cnt) nameW objHeight]);
-set(hAdd,'UserData',h);
-
-%Calculate Scrollbar / Slider Position
-lastPos = ypos(cnt);
-firstPos = ypos(0);
-tableHeight = abs(firstPos - lastPos);
-
-exceedFac = tableHeight / tableViewHeight; %How much the current umber of rows exceeds the window height capacity
-if exceedFac > 1
- sliderFac = exceedFac - 1;
- uicontrol(cstPanel,'Style','slider','Units','normalized','Position',[0.975 0 0.025 1],'Min',0,'Max',ceil(sliderFac)*tableViewHeight,'SliderStep',[lineHeight tableViewHeight] ./ (ceil(sliderFac)*tableViewHeight),'Value',ceil(sliderFac)*tableViewHeight - sliderPos,'Callback',{@cstTableSlider_Callback,handles});
-end
-
-
-% --- Executes when uipanel3 is resized.
-function uipanel3_SizeChangedFcn(hObject, eventdata, handles)
-% hObject handle to uipanel3 (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-try
- generateCstTable(handles,evalin('base','cst'));
-catch
-end
-
-function btObjAdd_Callback(hObject, ~, handles)
-% hObject handle to btObjAdd
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-popupHandle = hObject.UserData;
-cstIndex = popupHandle.Value;
-
-cst = evalin('base','cst');
-%Add Standard Objective
-if strcmp(cst{cstIndex,3},'TARGET')
- cst{cstIndex,6}{end+1} = struct(DoseObjectives.matRad_SquaredDeviation);
-else
- cst{cstIndex,6}{end+1} = struct(DoseObjectives.matRad_SquaredOverdosing);
-end
-
-assignin('base','cst',cst);
-
-generateCstTable(handles,cst);
-
-function btObjRemove_Callback(hObject, ~, handles)
-% hObject handle to btObjRemove (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-ix = hObject.UserData;
-
-cst = evalin('base','cst');
-%Add Standard Objective
-
-cst{ix(1),6}(ix(2)) = [];
-
-assignin('base','cst',cst);
-
-generateCstTable(handles,cst);
-
-function editObjParam_Callback(hObject, ~, handles)
-% hObject handle to current objective parameter
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-ix = hObject.UserData;
-
-cst = evalin('base','cst');
-%Add Standard Objective
-
-%if the third index is 0 we changed the penalty
-%if we have a popupmenu selection we use value
-%otherwise we use the edit string
-
-if ix(3) == 0
- cst{ix(1),6}{ix(2)}.penalty = str2double(hObject.String);
-elseif isequal(hObject.Style,'popupmenu')
- cst{ix(1),6}{ix(2)}.parameters{ix(3)} = hObject.Value;
-else
- cst{ix(1),6}{ix(2)}.parameters{ix(3)} = str2double(hObject.String);
-end
-
-assignin('base','cst',cst);
-
-generateCstTable(handles,cst);
-
-function changeObjFunction_Callback(hObject, ~, handles)
-% hObject handle to objective popup
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-data = hObject.UserData;
-ix = data{1};
-classNames = data{2};
-classToCreate = classNames{hObject.Value};
-
-cst = evalin('base','cst');
-%Add Standard Objective
-
-%We just check if the user really wanted to change the objective to be
-%user-friendly
-currentObj = cst{ix(1),6}{ix(2)};
-currentClass = class(currentObj);
-if ~strcmp(currentClass,classToCreate)
- newObj = eval(classToCreate);
-
- % Only if we have a penalty value for optimization, apply the new one
- % Maybe this check should be more exact?
- if (isfield(currentObj,'penalty') || isprop(currentObj,'penalty')) && isprop(newObj,'penalty')
- newObj.penalty = currentObj.penalty;
- end
-
- cst{ix(1),6}{ix(2)} = struct(newObj);
-
- assignin('base','cst',cst);
-
- generateCstTable(handles,cst);
-end
-
-function editCstParams_Callback(hObject,~,handles)
-data = hObject.UserData;
-ix = data(1);
-col = data(2);
-
-cst = evalin('base','cst');
-
-switch col
- case 2
- cst{ix,col} = hObject.String;
- case 3
- cst{ix,col} = hObject.String{hObject.Value};
- case 5
- cst{ix,col}.Priority = uint32(str2double(hObject.String));
- otherwise
- warning('Wrong column assignment in GUI based cst setting');
-end
-
-assignin('base','cst',cst);
-
-generateCstTable(handles,cst);
-
-
-% --- Executes on button press in radiobutton3Dconf.
-function radiobutton3Dconf_Callback(hObject, eventdata, handles)
-% hObject handle to radiobutton3Dconf (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hint: get(hObject,'Value') returns toggle state of radiobutton3Dconf
-
-function x = matRad_checkForConnectedBixelRows(stf)
-
-x = true;
-
-for i = 1:size(stf,2)
-
- bixelPos = reshape([stf(i).ray.rayPos_bev],3,[]);
-
- rowCoords = unique(bixelPos(3,:));
-
- for j = 1:numel(rowCoords)
-
- increments = diff(bixelPos(1,rowCoords(j) == bixelPos(3,:)));
-
- % if we find one not connected row -> return false
- if numel(unique(increments)) > 1
- x = false;
- return;
- end
- end
-
-end
-
-% --- Executes on slider movement.
-function cstTableSlider_Callback(hObject, eventdata, handles)
-% hObject handle to cstTableSlider (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles structure with handles and user data (see GUIDATA)
-
-% Hints: get(hObject,'Value') returns position of slider
-% get(hObject,'Min') and get(hObject,'Max') to determine range of slider
-generateCstTable(handles,evalin('base','cst'));
-
-% --- Executes during object creation, after setting all properties.
-function cstTableSlider_CreateFcn(hObject, eventdata, handles)
-% hObject handle to cstTableSlider (see GCBO)
-% eventdata reserved - to be defined in a future version of MATLAB
-% handles empty - handles not created until after all CreateFcns called
-
-% Hint: slider controls usually have a light gray background.
-if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
- set(hObject,'BackgroundColor',[.9 .9 .9]);
-end
-
diff --git a/matRadLinux.prj b/matRadLinux.prj
deleted file mode 100644
index 751b8f20b..000000000
--- a/matRadLinux.prj
+++ /dev/null
@@ -1,248 +0,0 @@
-
-
-
- matRad
- ${PROJECT_ROOT}/matRad_resources/icon.ico
-
- ${PROJECT_ROOT}/matRad_resources/icon_48.png
- ${PROJECT_ROOT}/matRad_resources/icon_32.png
- ${PROJECT_ROOT}/matRad_resources/icon_24.png
- ${PROJECT_ROOT}/matRad_resources/icon_16.png
-
- 2.2
- matRad development team @ DKFZ
- matRad@dkfz.de
- German Cancer Research Center DKFZ
- matRad is an open source treatment planning system for radiation therapy written in Matlab.
-
- ${PROJECT_ROOT}/standalone/matRad_splashscreen.png
-
- /matRad
- option.installpath.user
- ${PROJECT_ROOT}/standalone/matRad_installscreen.png
-
- In the following directions, replace MR/v97 by the directory on the target machine where MATLAB is installed, or MR by the directory where the MATLAB Runtime is installed.
-
-(1) Set the environment variable XAPPLRESDIR to this value:
-
-MR/v97/X11/app-defaults
-
-
-(2) If the environment variable LD_LIBRARY_PATH is undefined, set it to the following:
-
-MR/v97/runtime/glnxa64:MR/v97/bin/glnxa64:MR/v97/sys/os/glnxa64:MR/v97/sys/opengl/lib/glnxa64
-
-If it is defined, set it to the following:
-
-${LD_LIBRARY_PATH}:MR/v97/runtime/glnxa64:MR/v97/bin/glnxa64:MR/v97/sys/os/glnxa64:MR/v97/sys/opengl/lib/glnxa64
- ${PROJECT_ROOT}/standalone/for_testing
- ${PROJECT_ROOT}/matRadLinux/for_redistribution_files_only
- ${PROJECT_ROOT}/standalone/for_redistribution
- ${PROJECT_ROOT}/matRadLinux
- false
-
- subtarget.standalone
-
- true
- false
- false
- matRad_installerLinux64_v_2_2
- matRad_installerLinux64_wRT_v_2_2
- matRad_installerLinux64_noRT
- false
- true
- matRad_consoleOutput.log
- false
- false
-
- Syntax
- matRadGUI -?
- matRadGUI varargin
- Input Arguments
- -? print help on how to use the application
- varargin input arguments
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${PROJECT_ROOT}/matRadGUI.m
-
-
- ${PROJECT_ROOT}/IO
- ${PROJECT_ROOT}/IO/matRad_exportGUI.fig
- ${PROJECT_ROOT}/IO/matRad_exportGUI.m
- ${PROJECT_ROOT}/IO/matRad_importGUI.fig
- ${PROJECT_ROOT}/IO/matRad_importGUI.m
- ${PROJECT_ROOT}/IO/matRad_importPatient.m
- ${PROJECT_ROOT}/IO/matRad_readCube.m
- ${PROJECT_ROOT}/IO/matRad_readNRRD.m
- ${PROJECT_ROOT}/IO/matRad_writeCube.m
- ${PROJECT_ROOT}/IO/matRad_writeMHA.m
- ${PROJECT_ROOT}/IO/matRad_writeNRRD.m
- ${PROJECT_ROOT}/IO/matRad_writeVTK.m
- ${PROJECT_ROOT}/carbon_Generic.mat
- ${PROJECT_ROOT}/dicomImport
- ${PROJECT_ROOT}/dicomImport/DKFZ_Logo.png
- ${PROJECT_ROOT}/dicomImport/matRad_calcHU.m
- ${PROJECT_ROOT}/dicomImport/matRad_calcWaterEqD.m
- ${PROJECT_ROOT}/dicomImport/matRad_convRtssContours2Indices.m
- ${PROJECT_ROOT}/dicomImport/matRad_createCst.m
- ${PROJECT_ROOT}/dicomImport/matRad_dummyCst.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicom.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomCt.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomGUI.fig
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomGUI.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomRTDose.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomRTPlan.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomRtss.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomSteeringParticles.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomSteeringPhotons.m
- ${PROJECT_ROOT}/dicomImport/matRad_importFieldShapes.m
- ${PROJECT_ROOT}/dicomImport/matRad_interpDicomCtCube.m
- ${PROJECT_ROOT}/dicomImport/matRad_interpDicomDoseCube.m
- ${PROJECT_ROOT}/dicomImport/matRad_listAllFiles.m
- ${PROJECT_ROOT}/dicomImport/matRad_loadHLUT.m
- ${PROJECT_ROOT}/dicomImport/matRad_progress.m
- ${PROJECT_ROOT}/dicomImport/matRad_scanDicomImportFolder.m
- ${PROJECT_ROOT}/dicomImport/matrad_logo.png
- ${PROJECT_ROOT}/matRadGUI.fig
- ${PROJECT_ROOT}/matRadGUI.m
- ${PROJECT_ROOT}/matRad_DijSampling.m
- ${PROJECT_ROOT}/matRad_addMargin.m
- ${PROJECT_ROOT}/matRad_calcCubes.m
- ${PROJECT_ROOT}/matRad_calcDVH.m
- ${PROJECT_ROOT}/matRad_calcGeoDists.m
- ${PROJECT_ROOT}/matRad_calcLQParameter.m
- ${PROJECT_ROOT}/matRad_calcLateralParticleCutOff.m
- ${PROJECT_ROOT}/matRad_calcParticleDose.m
- ${PROJECT_ROOT}/matRad_calcParticleDoseBixel.m
- ${PROJECT_ROOT}/matRad_calcPhotonDose.m
- ${PROJECT_ROOT}/matRad_calcPhotonDoseBixel.m
- ${PROJECT_ROOT}/matRad_calcQualityIndicators.m
- ${PROJECT_ROOT}/matRad_calcSigmaRashi.m
- ${PROJECT_ROOT}/matRad_computeSSD.m
- ${PROJECT_ROOT}/matRad_directApertureOptimization.m
- ${PROJECT_ROOT}/matRad_dispToConsole.m
- ${PROJECT_ROOT}/matRad_electronDensitiesToHU.m
- ${PROJECT_ROOT}/matRad_engelLeafSequencing.m
- ${PROJECT_ROOT}/matRad_fluenceOptimization.m
- ${PROJECT_ROOT}/matRad_generateStf.m
- ${PROJECT_ROOT}/matRad_getIsoCenter.m
- ${PROJECT_ROOT}/matRad_getRotationMatrix.m
- ${PROJECT_ROOT}/matRad_indicatorWrapper.m
- ${PROJECT_ROOT}/matRad_interp1.m
- ${PROJECT_ROOT}/matRad_progress.m
- ${PROJECT_ROOT}/matRad_rayTracing.m
- ${PROJECT_ROOT}/matRad_sequencing2ApertureInfo.m
- ${PROJECT_ROOT}/matRad_setOverlapPriorities.m
- ${PROJECT_ROOT}/matRad_showDVH.m
- ${PROJECT_ROOT}/matRad_showQualityIndicators.m
- ${PROJECT_ROOT}/matRad_siddonRayTracer.m
- ${PROJECT_ROOT}/matRad_siochiLeafSequencing.m
- ${PROJECT_ROOT}/matRad_visApertureInfo.m
- ${PROJECT_ROOT}/matRad_xiaLeafSequencing.m
- ${PROJECT_ROOT}/optimization
- ${PROJECT_ROOT}/optimization/matRad_backProjection.m
- ${PROJECT_ROOT}/optimization/matRad_calcInversDVH.m
- ${PROJECT_ROOT}/optimization/matRad_collapseDij.m
- ${PROJECT_ROOT}/photons_Generic.mat
- ${PROJECT_ROOT}/plotting
- ${PROJECT_ROOT}/plotting/colormaps/colormapTemplate.txt
- ${PROJECT_ROOT}/plotting/matRad_computeAllVoiSurfaces.m
- ${PROJECT_ROOT}/plotting/matRad_computeIsoDoseContours.m
- ${PROJECT_ROOT}/plotting/matRad_computeVoiContours.m
- ${PROJECT_ROOT}/plotting/matRad_computeVoiContoursWrapper.m
- ${PROJECT_ROOT}/plotting/matRad_getColormap.m
- ${PROJECT_ROOT}/plotting/matRad_plotAxisLabels.m
- ${PROJECT_ROOT}/plotting/matRad_plotColorbar.m
- ${PROJECT_ROOT}/plotting/matRad_plotCtSlice.m
- ${PROJECT_ROOT}/plotting/matRad_plotCtSlice3D.m
- ${PROJECT_ROOT}/plotting/matRad_plotDoseSlice.m
- ${PROJECT_ROOT}/plotting/matRad_plotDoseSlice3D.m
- ${PROJECT_ROOT}/plotting/matRad_plotIsoCenterMarker.m
- ${PROJECT_ROOT}/plotting/matRad_plotIsoDoseLines.m
- ${PROJECT_ROOT}/plotting/matRad_plotIsoDoseLines3D.m
- ${PROJECT_ROOT}/plotting/matRad_plotPlan3D.m
- ${PROJECT_ROOT}/plotting/matRad_plotVoiContourSlice.m
- ${PROJECT_ROOT}/plotting/matRad_plotVois3D.m
- ${PROJECT_ROOT}/protons_Generic.mat
- ${PROJECT_ROOT}/tools
- ${PROJECT_ROOT}/tools/matRad_getEnvironment.m
-
-
- ${PROJECT_ROOT}/BOXPHANTOM.mat
- ${PROJECT_ROOT}/HEAD_AND_NECK.mat
- ${PROJECT_ROOT}/LIVER.mat
- ${PROJECT_ROOT}/PROSTATE.mat
- ${PROJECT_ROOT}/TG119.mat
- ${PROJECT_ROOT}/readme_linux.txt
-
-
- ${PROJECT_ROOT}/BOXPHANTOM.mat
- ${PROJECT_ROOT}/HEAD_AND_NECK.mat
- ${PROJECT_ROOT}/LIVER.mat
- ${PROJECT_ROOT}/PROSTATE.mat
- ${PROJECT_ROOT}/TG119.mat
- ${PROJECT_ROOT}/matRad_calcDoseDirect.m
- ${PROJECT_ROOT}/matRad_calcDoseFillDij.m
- ${PROJECT_ROOT}/matRad_calcDoseInit.m
- ${PROJECT_ROOT}/matRad_calcDoseInitBeam.m
- ${PROJECT_ROOT}/matRad_getPhotonLQMParameters.m
- ${PROJECT_ROOT}/matRad_interp3.m
- ${PROJECT_ROOT}/matRad_interpRadDepth.m
- ${PROJECT_ROOT}/matRad_resizeCstToGrid.m
-
-
- /home/abbani/Dokumente/matRad/standalone/for_testing/run_matRad.sh
- /home/abbani/Dokumente/matRad/standalone/for_testing/splash.png
- /home/abbani/Dokumente/matRad/standalone/for_testing/matRad
- /home/abbani/Dokumente/matRad/standalone/for_testing/readme.txt
-
-
-
- /home/abbani/Dokumente/MATLAB
-
-
-
- true
- false
- false
- false
- false
- false
- true
- false
- 4.12.14-lp150.12.82-default
- false
- true
- glnxa64
- true
-
-
-
\ No newline at end of file
diff --git a/matRadMac.prj b/matRadMac.prj
deleted file mode 100644
index 531407157..000000000
--- a/matRadMac.prj
+++ /dev/null
@@ -1,247 +0,0 @@
-
-
-
- matRad
- ${PROJECT_ROOT}/matRad_resources/icon.ico
-
- ${PROJECT_ROOT}/matRad_resources/icon_48.png
- ${PROJECT_ROOT}/matRad_resources/icon_32.png
- ${PROJECT_ROOT}/matRad_resources/icon_24.png
- ${PROJECT_ROOT}/matRad_resources/icon_16.png
-
- 2.2
- matRad development team @ DKFZ
- matRad@dkfz.de
- German Cancer Research Center DKFZ
- matRad is an open source treatment planning system for radiation therapy written in Matlab.
-
- ${PROJECT_ROOT}/standalone/matRad_splashscreen.png
-
- /matRad
- option.installpath.user
- ${PROJECT_ROOT}/standalone/matRad_installscreen.png
-
- In the following directions, replace MR/v97 by the directory on the target machine where MATLAB is installed, or MR by the directory where the MATLAB Runtime is installed.
-
-(1) Set the environment variable XAPPLRESDIR to this value:
-
-MR/v97/X11/app-defaults
-
-
-(2) If the environment variable LD_LIBRARY_PATH is undefined, set it to the following:
-
-MR/v97/runtime/glnxa64:MR/v97/bin/glnxa64:MR/v97/sys/os/glnxa64:MR/v97/sys/opengl/lib/glnxa64
-
-If it is defined, set it to the following:
-
-${LD_LIBRARY_PATH}:MR/v97/runtime/glnxa64:MR/v97/bin/glnxa64:MR/v97/sys/os/glnxa64:MR/v97/sys/opengl/lib/glnxa64
- ${PROJECT_ROOT}/standalone/for_testing
- ${PROJECT_ROOT}/matRadMac/for_redistribution_files_only
- ${PROJECT_ROOT}/standalone/for_redistribution
- ${PROJECT_ROOT}/matRadMac
- false
-
- subtarget.standalone
-
- false
- true
- false
- matRad_installerMac64
- matRad_installerMac64_wRT
- matRad_installerMac64_noRT
- false
- true
- matRad_consoleOutput.log
- false
- false
-
- Syntax
- matRadGUI -?
- matRadGUI varargin
- Input Arguments
- -? print help on how to use the application
- varargin input arguments
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${PROJECT_ROOT}/matRadGUI.m
-
-
- ${PROJECT_ROOT}/IO
- ${PROJECT_ROOT}/IO/matRad_exportGUI.fig
- ${PROJECT_ROOT}/IO/matRad_exportGUI.m
- ${PROJECT_ROOT}/IO/matRad_importGUI.fig
- ${PROJECT_ROOT}/IO/matRad_importGUI.m
- ${PROJECT_ROOT}/IO/matRad_importPatient.m
- ${PROJECT_ROOT}/IO/matRad_readCube.m
- ${PROJECT_ROOT}/IO/matRad_readNRRD.m
- ${PROJECT_ROOT}/IO/matRad_writeCube.m
- ${PROJECT_ROOT}/IO/matRad_writeMHA.m
- ${PROJECT_ROOT}/IO/matRad_writeNRRD.m
- ${PROJECT_ROOT}/IO/matRad_writeVTK.m
- ${PROJECT_ROOT}/carbon_Generic.mat
- ${PROJECT_ROOT}/dicomImport
- ${PROJECT_ROOT}/dicomImport/DKFZ_Logo.png
- ${PROJECT_ROOT}/dicomImport/matRad_calcHU.m
- ${PROJECT_ROOT}/dicomImport/matRad_calcWaterEqD.m
- ${PROJECT_ROOT}/dicomImport/matRad_convRtssContours2Indices.m
- ${PROJECT_ROOT}/dicomImport/matRad_createCst.m
- ${PROJECT_ROOT}/dicomImport/matRad_dummyCst.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicom.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomCt.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomGUI.fig
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomGUI.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomRTDose.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomRTPlan.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomRtss.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomSteeringParticles.m
- ${PROJECT_ROOT}/dicomImport/matRad_importDicomSteeringPhotons.m
- ${PROJECT_ROOT}/dicomImport/matRad_importFieldShapes.m
- ${PROJECT_ROOT}/dicomImport/matRad_interpDicomCtCube.m
- ${PROJECT_ROOT}/dicomImport/matRad_interpDicomDoseCube.m
- ${PROJECT_ROOT}/dicomImport/matRad_listAllFiles.m
- ${PROJECT_ROOT}/dicomImport/matRad_loadHLUT.m
- ${PROJECT_ROOT}/dicomImport/matRad_progress.m
- ${PROJECT_ROOT}/dicomImport/matRad_scanDicomImportFolder.m
- ${PROJECT_ROOT}/dicomImport/matrad_logo.png
- ${PROJECT_ROOT}/matRadGUI.fig
- ${PROJECT_ROOT}/matRadGUI.m
- ${PROJECT_ROOT}/matRad_DijSampling.m
- ${PROJECT_ROOT}/matRad_addMargin.m
- ${PROJECT_ROOT}/matRad_calcCubes.m
- ${PROJECT_ROOT}/matRad_calcDVH.m
- ${PROJECT_ROOT}/matRad_calcGeoDists.m
- ${PROJECT_ROOT}/matRad_calcLQParameter.m
- ${PROJECT_ROOT}/matRad_calcLateralParticleCutOff.m
- ${PROJECT_ROOT}/matRad_calcParticleDose.m
- ${PROJECT_ROOT}/matRad_calcParticleDoseBixel.m
- ${PROJECT_ROOT}/matRad_calcPhotonDose.m
- ${PROJECT_ROOT}/matRad_calcPhotonDoseBixel.m
- ${PROJECT_ROOT}/matRad_calcQualityIndicators.m
- ${PROJECT_ROOT}/matRad_calcSigmaRashi.m
- ${PROJECT_ROOT}/matRad_computeSSD.m
- ${PROJECT_ROOT}/matRad_directApertureOptimization.m
- ${PROJECT_ROOT}/matRad_dispToConsole.m
- ${PROJECT_ROOT}/matRad_electronDensitiesToHU.m
- ${PROJECT_ROOT}/matRad_engelLeafSequencing.m
- ${PROJECT_ROOT}/matRad_fluenceOptimization.m
- ${PROJECT_ROOT}/matRad_generateStf.m
- ${PROJECT_ROOT}/matRad_getIsoCenter.m
- ${PROJECT_ROOT}/matRad_getRotationMatrix.m
- ${PROJECT_ROOT}/matRad_indicatorWrapper.m
- ${PROJECT_ROOT}/matRad_interp1.m
- ${PROJECT_ROOT}/matRad_progress.m
- ${PROJECT_ROOT}/matRad_rayTracing.m
- ${PROJECT_ROOT}/matRad_sequencing2ApertureInfo.m
- ${PROJECT_ROOT}/matRad_setOverlapPriorities.m
- ${PROJECT_ROOT}/matRad_showDVH.m
- ${PROJECT_ROOT}/matRad_showQualityIndicators.m
- ${PROJECT_ROOT}/matRad_siddonRayTracer.m
- ${PROJECT_ROOT}/matRad_siochiLeafSequencing.m
- ${PROJECT_ROOT}/matRad_visApertureInfo.m
- ${PROJECT_ROOT}/matRad_xiaLeafSequencing.m
- ${PROJECT_ROOT}/optimization
- ${PROJECT_ROOT}/optimization/matRad_backProjection.m
- ${PROJECT_ROOT}/optimization/matRad_calcInversDVH.m
- ${PROJECT_ROOT}/optimization/matRad_collapseDij.m
- ${PROJECT_ROOT}/photons_Generic.mat
- ${PROJECT_ROOT}/plotting
- ${PROJECT_ROOT}/plotting/colormaps/colormapTemplate.txt
- ${PROJECT_ROOT}/plotting/matRad_computeAllVoiSurfaces.m
- ${PROJECT_ROOT}/plotting/matRad_computeIsoDoseContours.m
- ${PROJECT_ROOT}/plotting/matRad_computeVoiContours.m
- ${PROJECT_ROOT}/plotting/matRad_computeVoiContoursWrapper.m
- ${PROJECT_ROOT}/plotting/matRad_getColormap.m
- ${PROJECT_ROOT}/plotting/matRad_plotAxisLabels.m
- ${PROJECT_ROOT}/plotting/matRad_plotColorbar.m
- ${PROJECT_ROOT}/plotting/matRad_plotCtSlice.m
- ${PROJECT_ROOT}/plotting/matRad_plotCtSlice3D.m
- ${PROJECT_ROOT}/plotting/matRad_plotDoseSlice.m
- ${PROJECT_ROOT}/plotting/matRad_plotDoseSlice3D.m
- ${PROJECT_ROOT}/plotting/matRad_plotIsoCenterMarker.m
- ${PROJECT_ROOT}/plotting/matRad_plotIsoDoseLines.m
- ${PROJECT_ROOT}/plotting/matRad_plotIsoDoseLines3D.m
- ${PROJECT_ROOT}/plotting/matRad_plotPlan3D.m
- ${PROJECT_ROOT}/plotting/matRad_plotVoiContourSlice.m
- ${PROJECT_ROOT}/plotting/matRad_plotVois3D.m
- ${PROJECT_ROOT}/protons_Generic.mat
- ${PROJECT_ROOT}/tools
- ${PROJECT_ROOT}/tools/matRad_getEnvironment.m
-
-
- ${PROJECT_ROOT}/BOXPHANTOM.mat
- ${PROJECT_ROOT}/HEAD_AND_NECK.mat
- ${PROJECT_ROOT}/LIVER.mat
- ${PROJECT_ROOT}/PROSTATE.mat
- ${PROJECT_ROOT}/TG119.mat
- ${PROJECT_ROOT}/readme_linux.txt
-
-
- ${PROJECT_ROOT}/BOXPHANTOM.mat
- ${PROJECT_ROOT}/HEAD_AND_NECK.mat
- ${PROJECT_ROOT}/LIVER.mat
- ${PROJECT_ROOT}/PROSTATE.mat
- ${PROJECT_ROOT}/TG119.mat
- ${PROJECT_ROOT}/matRad_calcDoseDirect.m
- ${PROJECT_ROOT}/matRad_calcDoseFillDij.m
- ${PROJECT_ROOT}/matRad_calcDoseInit.m
- ${PROJECT_ROOT}/matRad_calcDoseInitBeam.m
- ${PROJECT_ROOT}/matRad_getPhotonLQMParameters.m
- ${PROJECT_ROOT}/matRad_interp3.m
- ${PROJECT_ROOT}/matRad_interpRadDepth.m
- ${PROJECT_ROOT}/matRad_resizeCstToGrid.m
-
-
- /home/abbani/Dokumente/matRad/standalone/for_testing/run_matRad.sh
- /home/abbani/Dokumente/matRad/standalone/for_testing/splash.png
- /home/abbani/Dokumente/matRad/standalone/for_testing/matRad
- /home/abbani/Dokumente/matRad/standalone/for_testing/readme.txt
-
-
-
- /home/abbani/Dokumente/MATLAB
-
-
-
- true
- false
- false
- false
- false
- false
- true
- false
- 4.12.14-lp150.12.82-default
- false
- true
- glnxa64
- true
-
-
-
\ No newline at end of file
diff --git a/matRadToolbox.prj b/matRadToolbox.prj
deleted file mode 100644
index 07879866e..000000000
--- a/matRadToolbox.prj
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
- matRad
- matRad development team
- matrad@dkfz.de
- German Cancer Research Center DKFZ
- http://www.matrad.org
- matRad is an open source software for radiation treatment planning of intensity-modulated photon, proton, and carbon ion therapy. matRad is developed for educational and research purposes.
- ${PROJECT_ROOT}\dicomImport\matrad_logo.png
- 1.0
- ${PROJECT_ROOT}\matRad.mltbx
-
-
-
-
- 1b151558-6a49-4781-a6de-1d7b612fc638
- % List files contained in your toolbox folder that you would like to exclude
-% from packaging. Excludes should be listed relative to the toolbox folder.
-% Some examples of how to specify excludes are provided below:
-%
-% A single file in the toolbox folder:
-myManipulationScript.m
-%
-% A single file in a subfolder of the toolbox folder:
-.git/
-protons_g*.mat
-protons_HIT*.mat
-carbon_HIT*.mat
-% example/.svn
-%
-% All files in a subfolder of the toolbox folder:
-dicomImport\hlutLibrary\SIEMENS*
-%
-% All files of a certain name in all subfolders of the toolbox folder:
-% **/.svn
-%
-% All files matching a pattern in all subfolders of the toolbox folder:
-% **/*.bak
-%
- true
-
-
-
-
-
-
-
-
- false
- R2014b
- latest
- false
- true
- true
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- C:\Home\Bangertm\Git\matRad
-
-
- ${PROJECT_ROOT}\.gitignore
- ${PROJECT_ROOT}\AUTHORS.txt
- ${PROJECT_ROOT}\BOXPHANTOM.mat
- ${PROJECT_ROOT}\carbon_Generic.mat
- ${PROJECT_ROOT}\dicomImport
- ${PROJECT_ROOT}\examples
- ${PROJECT_ROOT}\HEAD_AND_NECK.mat
- ${PROJECT_ROOT}\IO
- ${PROJECT_ROOT}\LICENSES.txt
- ${PROJECT_ROOT}\LIVER.mat
- ${PROJECT_ROOT}\matRad.m
- ${PROJECT_ROOT}\matRad_addMargin.m
- ${PROJECT_ROOT}\matRad_calcCubes.m
- ${PROJECT_ROOT}\matRad_calcDoseDirect.m
- ${PROJECT_ROOT}\matRad_calcDVH.m
- ${PROJECT_ROOT}\matRad_calcGeoDists.m
- ${PROJECT_ROOT}\matRad_calcLateralParticleCutOff.m
- ${PROJECT_ROOT}\matRad_calcLQParameter.m
- ${PROJECT_ROOT}\matRad_calcParticleDose.m
- ${PROJECT_ROOT}\matRad_calcParticleDoseBixel.m
- ${PROJECT_ROOT}\matRad_calcPhotonDose.m
- ${PROJECT_ROOT}\matRad_calcPhotonDoseBixel.m
- ${PROJECT_ROOT}\matRad_calcPhotonDoseVmc.m
- ${PROJECT_ROOT}\matRad_calcQualityIndicators.m
- ${PROJECT_ROOT}\matRad_calcSigmaRashi.m
- ${PROJECT_ROOT}\matRad_computeSSD.m
- ${PROJECT_ROOT}\matRad_DijSampling.m
- ${PROJECT_ROOT}\matRad_directApertureOptimization.m
- ${PROJECT_ROOT}\matRad_dispToConsole.m
- ${PROJECT_ROOT}\matRad_electronDensitiesToHU.m
- ${PROJECT_ROOT}\matRad_engelLeafSequencing.m
- ${PROJECT_ROOT}\matRad_fluenceOptimization.m
- ${PROJECT_ROOT}\matRad_generateStf.m
- ${PROJECT_ROOT}\matRad_getIsoCenter.m
- ${PROJECT_ROOT}\matRad_getRotationMatrix.m
- ${PROJECT_ROOT}\matRad_indicatorWrapper.m
- ${PROJECT_ROOT}\matRad_interp1.m
- ${PROJECT_ROOT}\matRad_progress.m
- ${PROJECT_ROOT}\matRad_rayTracing.m
- ${PROJECT_ROOT}\matRad_sequencing2ApertureInfo.m
- ${PROJECT_ROOT}\matRad_setOverlapPriorities.m
- ${PROJECT_ROOT}\matRad_showDVH.m
- ${PROJECT_ROOT}\matRad_showQualityIndicators.m
- ${PROJECT_ROOT}\matRad_siddonRayTracer.m
- ${PROJECT_ROOT}\matRad_siochiLeafSequencing.m
- ${PROJECT_ROOT}\matRad_visApertureInfo.m
- ${PROJECT_ROOT}\matRad_xiaLeafSequencing.m
- ${PROJECT_ROOT}\matRadGUI.fig
- ${PROJECT_ROOT}\matRadGUI.m
- ${PROJECT_ROOT}\optimization
- ${PROJECT_ROOT}\photons_Generic.mat
- ${PROJECT_ROOT}\PITCHME.md
- ${PROJECT_ROOT}\PITCHME.yaml
- ${PROJECT_ROOT}\plotting
- ${PROJECT_ROOT}\PROSTATE.mat
- ${PROJECT_ROOT}\protons_Generic.mat
- ${PROJECT_ROOT}\README.md
- ${PROJECT_ROOT}\standalone
- ${PROJECT_ROOT}\submodules
- ${PROJECT_ROOT}\TG119.mat
- ${PROJECT_ROOT}\tools
- ${PROJECT_ROOT}\vmc++
-
-
- ${MATLAB_ROOT}\toolbox\matlab\demos\clown.mat
- ${MATLAB_ROOT}\toolbox\matlab\demos\flujet.mat
- ${MATLAB_ROOT}\toolbox\matlab\demos\mandrill.mat
- ${MATLAB_ROOT}\toolbox\matlab\demos\penny.mat
- ${MATLAB_ROOT}\toolbox\matlab\demos\seamount.mat
-
-
-
-
- C:\Home\Bangertm\Git\matRad\matRad.mltbx
-
-
-
- C:\Program Files\MATLAB\R2018a
-
-
-
- false
- false
- true
- false
- false
- false
- false
- false
- 10.0
- false
- true
- win64
- true
-
-
-
\ No newline at end of file
diff --git a/matRadWin.prj b/matRadWin.prj
deleted file mode 100644
index 71ecb5af9..000000000
--- a/matRadWin.prj
+++ /dev/null
@@ -1,220 +0,0 @@
-
-
- matRad
- ${PROJECT_ROOT}\matRad_resources\icon.ico
-
- ${PROJECT_ROOT}\matRad_resources\icon_48.png
- ${PROJECT_ROOT}\matRad_resources\icon_32.png
- ${PROJECT_ROOT}\matRad_resources\icon_24.png
- ${PROJECT_ROOT}\matRad_resources\icon_16.png
-
- 2.2
- matRad development team @ DKFZ
- matRad@dkfz.de
- German Cancer Research Center DKFZ
- matRad is an open source treatment planning system for radiation therapy written in Matlab.
-
- ${PROJECT_ROOT}\standalone\matRad_splashscreen.png
-
- \matRad
- option.installpath.programfiles
- ${PROJECT_ROOT}\standalone\matRad_installscreen.png
-
- ${PROJECT_ROOT}\standalone\for_testing
- ${PROJECT_ROOT}\standalone\for_redistribution
-
- true
- ${PROJECT_ROOT}\standalone\for_testing\.ctf
- ${PROJECT_ROOT}\standalone\for_testing\readme.txt
- subtarget.standalone
-
- true
- false
- false
- matRad_installerWin64
- MyAppInstaller_mcr
- MyAppInstaller_app
- false
- true
- matRad_consoleOutput.log
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${PROJECT_ROOT}\matRadGUI.m
-
-
- ${PROJECT_ROOT}\carbon_Generic.mat
- ${PROJECT_ROOT}\dicomImport\DKFZ_Logo.png
- ${PROJECT_ROOT}\dicomImport\hlutLibrary\matRad_default_carbon.hlut
- ${PROJECT_ROOT}\dicomImport\hlutLibrary\matRad_default_photons.hlut
- ${PROJECT_ROOT}\dicomImport\hlutLibrary\matRad_default_protons.hlut
- ${PROJECT_ROOT}\dicomImport\matRad_calcHU.m
- ${PROJECT_ROOT}\dicomImport\matRad_calcWaterEqD.m
- ${PROJECT_ROOT}\dicomImport\matRad_convRtssContours2Indices.m
- ${PROJECT_ROOT}\dicomImport\matRad_createCst.m
- ${PROJECT_ROOT}\dicomImport\matRad_dummyCst.m
- ${PROJECT_ROOT}\dicomImport\matRad_importDicom.m
- ${PROJECT_ROOT}\dicomImport\matRad_importDicomCt.m
- ${PROJECT_ROOT}\dicomImport\matRad_importDicomGUI.fig
- ${PROJECT_ROOT}\dicomImport\matRad_importDicomGUI.m
- ${PROJECT_ROOT}\dicomImport\matRad_importDicomRTDose.m
- ${PROJECT_ROOT}\dicomImport\matRad_importDicomRTPlan.m
- ${PROJECT_ROOT}\dicomImport\matRad_importDicomRtss.m
- ${PROJECT_ROOT}\dicomImport\matRad_importDicomSteeringParticles.m
- ${PROJECT_ROOT}\dicomImport\matRad_importDicomSteeringPhotons.m
- ${PROJECT_ROOT}\dicomImport\matRad_importFieldShapes.m
- ${PROJECT_ROOT}\dicomImport\matRad_interpDicomCtCube.m
- ${PROJECT_ROOT}\dicomImport\matRad_interpDicomDoseCube.m
- ${PROJECT_ROOT}\dicomImport\matRad_listAllFiles.m
- ${PROJECT_ROOT}\dicomImport\matRad_loadHLUT.m
- ${PROJECT_ROOT}\dicomImport\matrad_logo.png
- ${PROJECT_ROOT}\dicomImport\matRad_progress.m
- ${PROJECT_ROOT}\dicomImport\matRad_scanDicomImportFolder.m
- ${PROJECT_ROOT}\IO\matRad_exportGUI.fig
- ${PROJECT_ROOT}\IO\matRad_exportGUI.m
- ${PROJECT_ROOT}\IO\matRad_importGUI.fig
- ${PROJECT_ROOT}\IO\matRad_importGUI.m
- ${PROJECT_ROOT}\IO\matRad_importPatient.m
- ${PROJECT_ROOT}\IO\matRad_readCube.m
- ${PROJECT_ROOT}\IO\matRad_readNRRD.m
- ${PROJECT_ROOT}\IO\matRad_writeCube.m
- ${PROJECT_ROOT}\IO\matRad_writeMHA.m
- ${PROJECT_ROOT}\IO\matRad_writeNRRD.m
- ${PROJECT_ROOT}\IO\matRad_writeVTK.m
- ${PROJECT_ROOT}\matRad_addMargin.m
- ${PROJECT_ROOT}\matRad_calcCubes.m
- ${PROJECT_ROOT}\matRad_calcDVH.m
- ${PROJECT_ROOT}\matRad_calcGeoDists.m
- ${PROJECT_ROOT}\matRad_calcLateralParticleCutOff.m
- ${PROJECT_ROOT}\matRad_calcLQParameter.m
- ${PROJECT_ROOT}\matRad_calcParticleDose.m
- ${PROJECT_ROOT}\matRad_calcParticleDoseBixel.m
- ${PROJECT_ROOT}\matRad_calcPhotonDose.m
- ${PROJECT_ROOT}\matRad_calcPhotonDoseBixel.m
- ${PROJECT_ROOT}\matRad_calcPhotonDoseConf.m
- ${PROJECT_ROOT}\matRad_calcPhotonDoseVmc.m
- ${PROJECT_ROOT}\matRad_calcQualityIndicators.m
- ${PROJECT_ROOT}\matRad_calcRadDepthCubes.m
- ${PROJECT_ROOT}\matRad_calcSigmaRashi.m
- ${PROJECT_ROOT}\matRad_computeSSD.m
- ${PROJECT_ROOT}\matRad_DijSampling.m
- ${PROJECT_ROOT}\matRad_directApertureOptimization.m
- ${PROJECT_ROOT}\matRad_dispToConsole.m
- ${PROJECT_ROOT}\matRad_electronDensitiesToHU.m
- ${PROJECT_ROOT}\matRad_engelLeafSequencing.m
- ${PROJECT_ROOT}\matRad_fluenceOptimization.m
- ${PROJECT_ROOT}\matRad_generateStf.m
- ${PROJECT_ROOT}\matRad_getIsoCenter.m
- ${PROJECT_ROOT}\matRad_getRotationMatrix.m
- ${PROJECT_ROOT}\matRad_indicatorWrapper.m
- ${PROJECT_ROOT}\matRad_interp1.m
- ${PROJECT_ROOT}\matRad_progress.m
- ${PROJECT_ROOT}\matRad_rayTracing.m
- ${PROJECT_ROOT}\matRad_sequencing2ApertureInfo.m
- ${PROJECT_ROOT}\matRad_setOverlapPriorities.m
- ${PROJECT_ROOT}\matRad_showDVH.m
- ${PROJECT_ROOT}\matRad_showQualityIndicators.m
- ${PROJECT_ROOT}\matRad_siddonRayTracer.m
- ${PROJECT_ROOT}\matRad_siochiLeafSequencing.m
- ${PROJECT_ROOT}\matRad_visApertureInfo.m
- ${PROJECT_ROOT}\matRad_xiaLeafSequencing.m
- ${PROJECT_ROOT}\matRadGUI.fig
- ${PROJECT_ROOT}\matRadGUI.m
- ${PROJECT_ROOT}\optimization\ipopt.m
- ${PROJECT_ROOT}\optimization\ipopt.mexmaci64
- ${PROJECT_ROOT}\optimization\ipopt.mexw32
- ${PROJECT_ROOT}\optimization\ipopt.mexw64
- ${PROJECT_ROOT}\optimization\matRad_backProjection.m
- ${PROJECT_ROOT}\optimization\matRad_calcInversDVH.m
- ${PROJECT_ROOT}\optimization\matRad_collapseDij.m
- ${PROJECT_ROOT}\optimization\matRad_constFunc.m
- ${PROJECT_ROOT}\optimization\matRad_constFuncWrapper.m
- ${PROJECT_ROOT}\optimization\matRad_CWKeyPressedCallback.m
- ${PROJECT_ROOT}\optimization\matRad_daoApertureInfo2Vec.m
- ${PROJECT_ROOT}\optimization\matRad_daoConstFunc.m
- ${PROJECT_ROOT}\optimization\matRad_daoGetConstBounds.m
- ${PROJECT_ROOT}\optimization\matRad_daoGetJacobStruct.m
- ${PROJECT_ROOT}\optimization\matRad_daoGradFunc.m
- ${PROJECT_ROOT}\optimization\matRad_daoJacobFunc.m
- ${PROJECT_ROOT}\optimization\matRad_daoObjFunc.m
- ${PROJECT_ROOT}\optimization\matRad_daoVec2ApertureInfo.m
- ${PROJECT_ROOT}\optimization\matRad_getConstBounds.m
- ${PROJECT_ROOT}\optimization\matRad_getConstBoundsWrapper.m
- ${PROJECT_ROOT}\optimization\matRad_getJacobStruct.m
- ${PROJECT_ROOT}\optimization\matRad_gradFunc.m
- ${PROJECT_ROOT}\optimization\matRad_gradFuncWrapper.m
- ${PROJECT_ROOT}\optimization\matRad_IpoptIterFunc.m
- ${PROJECT_ROOT}\optimization\matRad_ipoptOptions.m
- ${PROJECT_ROOT}\optimization\matRad_jacobFunc.m
- ${PROJECT_ROOT}\optimization\matRad_jacobFuncWrapper.m
- ${PROJECT_ROOT}\optimization\matRad_objFunc.m
- ${PROJECT_ROOT}\optimization\matRad_objFuncWrapper.m
- ${PROJECT_ROOT}\photons_Generic.mat
- ${PROJECT_ROOT}\plotting\colormaps\colormapTemplate.txt
- ${PROJECT_ROOT}\plotting\matRad_computeAllVoiSurfaces.m
- ${PROJECT_ROOT}\plotting\matRad_computeIsoDoseContours.m
- ${PROJECT_ROOT}\plotting\matRad_computeVoiContours.m
- ${PROJECT_ROOT}\plotting\matRad_computeVoiContoursWrapper.m
- ${PROJECT_ROOT}\plotting\matRad_getColormap.m
- ${PROJECT_ROOT}\plotting\matRad_plotAxisLabels.m
- ${PROJECT_ROOT}\plotting\matRad_plotColorbar.m
- ${PROJECT_ROOT}\plotting\matRad_plotCtSlice.m
- ${PROJECT_ROOT}\plotting\matRad_plotCtSlice3D.m
- ${PROJECT_ROOT}\plotting\matRad_plotDoseSlice.m
- ${PROJECT_ROOT}\plotting\matRad_plotDoseSlice3D.m
- ${PROJECT_ROOT}\plotting\matRad_plotIsoCenterMarker.m
- ${PROJECT_ROOT}\plotting\matRad_plotIsoDoseLines.m
- ${PROJECT_ROOT}\plotting\matRad_plotIsoDoseLines3D.m
- ${PROJECT_ROOT}\plotting\matRad_plotPlan3D.m
- ${PROJECT_ROOT}\plotting\matRad_plotVoiContourSlice.m
- ${PROJECT_ROOT}\plotting\matRad_plotVois3D.m
- ${PROJECT_ROOT}\protons_Generic.mat
- ${PROJECT_ROOT}\tools\matRad_getEnvironment.m
- ${PROJECT_ROOT}\vmc++\matRad_createVmcBatchFile.m
- ${PROJECT_ROOT}\vmc++\matRad_createVmcInput.m
- ${PROJECT_ROOT}\vmc++\matRad_exportCtVmc.m
- ${PROJECT_ROOT}\vmc++\matRad_readDoseVmc.m
-
-
-
- d:\git\matRad\standalone\for_testing\splash.png
- d:\git\matRad\standalone\for_testing\readme.txt
- d:\git\matRad\standalone\for_testing\matRad.exe
-
-
-
- C:\Program Files\MATLAB\R2014a
-
-
-
- false
- false
- true
- false
- false
- false
- false
- false
- 6.1
- false
- true
- win64
- true
-
-
-
\ No newline at end of file
diff --git a/matRad_DijSampling.m b/matRad_DijSampling.m
deleted file mode 100644
index c30a7448f..000000000
--- a/matRad_DijSampling.m
+++ /dev/null
@@ -1,135 +0,0 @@
-function [ixNew,bixelDoseNew] = matRad_DijSampling(ix,bixelDose,radDepthV,rad_distancesSq,sType,Param)
-% matRad dij sampling function
-% This function samples.
-%
-% call
-% [ixNew,bixelDoseNew] =
-% matRad_DijSampling(ix,bixelDose,radDepthV,rad_distancesSq,sType,Param)
-%
-% input
-% ix: indices of voxels where we want to compute dose influence data
-% bixelDose: dose at specified locations as linear vector
-% radDepthV: radiological depth vector
-% rad_distancesSq: squared radial distance to the central ray
-% sType: can either be set to 'radius' or 'dose'. These are two different ways
-% to determine dose values that are keept as they are and dose values used for sampling
-% Param: In the case of radius based sampling, dose values having a radial
-% distance below r0 [mm] are keept anyway and sampling is only done beyond r0.
-% In the case of dose based sampling, dose values having a relative dose greater
-% the threshold [0...1] are keept and sampling is done for dose values below the relative threshold
-%
-% output
-% ixNew: reduced indices of voxels where we want to compute dose influence data
-% bixelDoseNew reduced dose at specified locations as linear vector
-%
-% References
-% [1] http://dx.doi.org/10.1118/1.1469633
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2016 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% define default parameters as a fallback
-defaultType = 'radius';
-deltaRadDepth = 5; % step size of radiological depth
-defaultLatCutOff = 25; % default lateral cut off
-defaultrelDoseThreshold = 0.01; % default relative dose threshold
-
-relDoseThreshold = defaultrelDoseThreshold;
-LatCutOff = defaultLatCutOff;
-Type = sType;
-
-% if the input index vector is of type logical convert it to linear indices
-if islogical(ix)
- ix = find(ix);
-end
-
-%% parse inputs
-if sum(strcmp(sType,{'radius','dose'})) == 0
- Type = defaultType;
-end
-
-% if an parameter is provided then use it
-if nargin>5
- if exist('Param','var')
- if strcmp(sType,'radius')
- LatCutOff = Param;
- elseif strcmp(sType,'dose')
- relDoseThreshold = Param;
- end
- end
-end
-
-%% remember dose values inside the inner core
-switch Type
- case {'radius'}
- ixCore = rad_distancesSq < LatCutOff^2; % get voxels indices having a smaller radial distance than r0
- case {'dose'}
- ixCore = bixelDose > relDoseThreshold * max(bixelDose); % get voxels indices having a greater dose than the thresholdDose
-end
-
-bixelDoseCore = bixelDose(ixCore); % save dose values that are not affected by sampling
-
-if all(ixCore)
- %% all bixels are in the core
- %exit function with core dose only
- ixNew = ix;
- bixelDoseNew = bixelDoseCore;
-else
- logIxTail = ~ixCore; % get voxels indices beyond r0
- linIxTail = find(logIxTail); % convert logical index to linear index
- numTail = numel(linIxTail);
- bixelDoseTail = bixelDose(linIxTail); % dose values that are going to be reduced by sampling
- ixTail = ix(linIxTail); % indices that are going to be reduced by sampling
-
- %% sample for each radiological depth the lateral halo dose
- radDepthTail = (radDepthV(linIxTail)); % get radiological depth in the tail
-
- % cluster radiological dephts to reduce computations
- B_r = int32(ceil(radDepthTail)); % cluster radiological depths;
- maxRadDepth = double(max(B_r));
- C = int32(linspace(0,maxRadDepth,round(maxRadDepth)/deltaRadDepth)); % coarse clustering of rad depths
-
- ixNew = zeros(numTail,1); % inizialize new index vector
- bixelDoseNew = zeros(numTail,1); % inizialize new dose vector
- linIx = int32(1:1:numTail)';
- IxCnt = 1;
-
- %% loop over clustered radiological depths
- for i = 1:numel(C)-1
- ixTmp = linIx(B_r >= C(i) & B_r < C(i+1)); % extracting sub indices
- if isempty(ixTmp)
- continue
- end
- subDose = bixelDoseTail(ixTmp); % get tail dose in current cluster
- subIx = ixTail(ixTmp); % get indices in current cluster
- thresholdDose = max(subDose);
- r = rand(numel(subDose),1); % get random samples
- ixSamp = r<=(subDose/thresholdDose);
- NumSamples = sum(ixSamp);
-
- ixNew(IxCnt:IxCnt+NumSamples-1,1) = subIx(ixSamp); % save new indices
- bixelDoseNew(IxCnt:IxCnt+NumSamples-1,1) = thresholdDose; % set the dose
- IxCnt = IxCnt + NumSamples;
- end
-
-
- % cut new vectors and add inner core values
- ixNew = [ix(ixCore); ixNew(1:IxCnt-1)];
- bixelDoseNew = [bixelDoseCore; bixelDoseNew(1:IxCnt-1)];
-end
-
-
-
-end
-
-
diff --git a/matRad_buildStandalone.m b/matRad_buildStandalone.m
new file mode 100644
index 000000000..6feebb3e2
--- /dev/null
+++ b/matRad_buildStandalone.m
@@ -0,0 +1,241 @@
+function buildResult = matRad_buildStandalone(varargin)
+% Compiles the standalone exectuable & packages installer using Matlab's
+% Compiler Toolbox
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2020 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+matRad_cfg = matRad_rc;
+
+matRadRoot = matRad_cfg.matRadRoot;
+standaloneFolder = fullfile(matRadRoot,'standalone');
+
+p = inputParser;
+
+p.addParameter('isRelease',false,@islogical); %By default we compile a snapshot of the current branch
+p.addParameter('compileWithRT',false,@islogical); %By default we don't package installers with runtime
+p.addParameter('buildDir',fullfile(matRadRoot,'build'),@(x) ischar(x) || isstring(x)); %Build directory
+p.addParameter('verbose',false,@islogical);
+p.addParameter('docker',false,@islogical);
+p.addParameter('java',false,@islogical);
+p.addParameter('python',false,@islogical);
+p.addParameter('json',[],@(x) isempty(x) || ischar(x) || isstring(x));
+
+p.parse(varargin{:});
+
+isRelease = p.Results.isRelease;
+compileWithRT = p.Results.compileWithRT;
+buildDir = p.Results.buildDir;
+buildDocker = all(p.Results.docker);
+buildJava = all(p.Results.java);
+buildPython = all(p.Results.python);
+json = p.Results.json;
+
+if all(p.Results.verbose)
+ verbose = 'On';
+else
+ verbose = 'Off';
+end
+
+if compileWithRT
+ rtOption = 'installer';
+else
+ rtOption = 'web';
+end
+
+try
+ mkdir(buildDir);
+catch ME
+ error(ME.identifier,'Could not create build directory %s\n Error:',buildDir,ME.message);
+end
+
+if buildDocker && isunix && ~ismac
+ warning('Can''t build docker container. Only works on linux!');
+ buildDocker = false;
+end
+
+
+[~,versionFull] = matRad_version();
+
+if isRelease
+ vernumApp = sprintf('%d.%d.%d',versionFull.major,versionFull.minor,versionFull.patch);
+ vernumInstall = vernumApp;
+ %vernumInstall = sprintf('%d.%d',versionFull.major,versionFull.minor);
+else
+ vernumApp = sprintf('%d.%d.%d.65534',versionFull.major,versionFull.minor,versionFull.patch);
+ vernumInstall = vernumApp;
+ %vernumInstall = sprintf('%d.%d',versionFull.major,versionFull.minor);
+end
+
+
+%elseif isempty(versionFull.commitID)
+% vernum = sprintf('%d.%d.%d.dev',versionFull.major,versionFull.minor,versionFull.patch);
+%else
+% vernum = sprintf('%d.%d.%d.%s/%s',versionFull.major,versionFull.minor,versionFull.patch,versionFull.branch,versionFull.commitID(1:8));
+%end
+
+buildResult.version = vernumInstall;
+buildResult.versionDetail = versionFull;
+
+%% Set Options and Compile
+try
+ buildOpts = compiler.build.StandaloneApplicationOptions('matRadGUI.m',...
+ 'OutputDir',buildDir,...
+ 'ExecutableIcon',fullfile(standaloneFolder,'matRad_icon.png'),...
+ 'AutodetectDataFiles','on',...
+ 'AdditionalFiles',{'matRad','thirdParty','matRad_rc.m'},...
+ 'EmbedArchive','on',...
+ 'ExecutableName','matRad',...
+ 'ExecutableSplashScreen',fullfile(standaloneFolder,'matRad_splashscreen.png'),...
+ 'ExecutableVersion',vernumApp,...
+ 'TreatInputsAsNumeric','off',...
+ 'Verbose',verbose);
+
+ if ispc
+ resultsStandalone = compiler.build.standaloneWindowsApplication(buildOpts);
+ else
+ resultsStandalone = compiler.build.standaloneApplication(buildOpts);
+ end
+
+ buildResult.standalone.compiledFiles = resultsStandalone.Files;
+
+catch ME
+ warning(ME.identifier,'Failed to compile standalone due to %s',ME.message);
+
+end
+
+%% Package
+if ispc
+ readmeFile = 'readme_standalone_windows.txt';
+ installerId = 'Win64';
+elseif ismac
+ readmeFile = 'readme_standalone_mac.txt';
+ [~,result] = system('uname -m');
+ if any(strfind(result,'ARM64')) %is m1mac
+ installerId = 'MacARM';
+ else
+ installerId = 'Mac64';
+ end
+
+else
+ readmeFile = 'readme_standalone_linux.txt';
+ installerId = 'Linux64';
+end
+
+try
+ packageOpts = compiler.package.InstallerOptions(resultsStandalone,...
+ 'AdditionalFiles',{fullfile(matRadRoot,'AUTHORS.txt'),fullfile(matRadRoot,'LICENSE.md'),fullfile(standaloneFolder,readmeFile)},...
+ 'ApplicationName','matRad',...
+ 'AuthorCompany','German Cancer Research Center (DKFZ)',...
+ 'AuthorEmail','contact@matRad.org',...
+ 'AuthorName','matRad development team @ DKFZ',...
+ 'Description','matRad is an open source software for radiation treatment planning of intensity-modulated photon, proton, and carbon ion therapy started in 2015 in the research group "Radiotherapy Optimization" within the Department of Medical Physics in Radiation Oncology at the German Cancer Research Center - DKFZ.', ... %\n\nmatRad targets education and research in radiotherapy treatment planning, where the software landscape is dominated by proprietary medical software. As of August 2022, matRad had more than 130 forks on GitHub and its development paper was cited more than 160 times (according to Google Scholar). matRad is entirely written in MATLAB and mostly compatible to GNU Octave.',...
+ 'InstallationNotes','matRad contains precompiled libraries and thirdParty software. In some cases, those precompiled libraries may not run out of the box on your system. Please contact us should this be the case. For the Third-Party licenses, check the thirdParty subfolder in the matRad installation directory.',...
+ 'InstallerIcon',fullfile(standaloneFolder,'matRad_icon.png'),...
+ 'InstallerLogo',fullfile(standaloneFolder,'matRad_installscreen.png'),...
+ 'InstallerSplash',fullfile(standaloneFolder,'matRad_splashscreen.png'),...
+ 'InstallerName',sprintf('matRad_installer%s_v%s',installerId,vernumApp),...
+ 'OutputDir',fullfile(buildDir,'installer'),...
+ 'RuntimeDelivery',rtOption,...
+ 'Summary','matRad is an open source treatment planning system for radiation therapy written in Matlab.',...
+ 'Version',vernumInstall,...
+ 'Verbose',verbose);
+ compiler.package.installer(resultsStandalone,'Options',packageOpts);
+
+ outFiles = dir([packageOpts.OutputDir filesep packageOpts.InstallerName '.*']);
+ outFiles = arrayfun(@(f) fullfile(f.folder,f.name),outFiles,'UniformOutput',false);
+
+ buildResult.standalone.installerFiles = outFiles;
+catch ME
+ warning(ME.identifier,'Failed to package standalone installer due to %s!',ME.message);
+end
+
+%% Python
+if buildPython
+ functionFiles = {'matRadGUI.m',...
+ 'matRad/matRad_generateStf.m',...
+ 'matRad/matRad_calcDoseForward.m',...
+ 'matRad/matRad_calcDoseInfluence.m',...
+ 'matRad/matRad_fluenceOptimization.m',...
+ 'matRad/matRad_directApertureOptimization.m',...
+ 'matRad/matRad_sequencing.m',...
+ 'matRad/matRad_planAnalysis.m'};
+
+ sampleGenerationFiles = {'matRad.m'};
+
+ try
+ pythonOpts = compiler.build.PythonPackageOptions(functionFiles,...
+ 'AdditionalFiles',{'matRad','thirdParty','matRad_rc.m'},...
+ 'SampleGenerationFiles',sampleGenerationFiles,...
+ 'Verbose',verbose, ...
+ 'PackageName','pyMatRad',...
+ 'OutputDir',fullfile(buildDir,'python'));
+
+ resultsPython = compiler.build.pythonPackage(pythonOpts);
+
+ buildResult.python.packageFiles = resultsPython.Files;
+ catch ME
+ warning(ME.identifier,'Java build failed due to %s!',ME.message);
+ end
+end
+
+%% Java
+if buildJava
+ javaOpts = compiler.build.JavaPackageOptions('matRadGUI.m',...
+ 'AdditionalFiles',{'matRad','thirdParty','matRad_rc.m'},...
+ 'Verbose',verbose);
+ try
+ resultsJava = compiler.build.javaPackage(javaOpts);
+ buildResult.java.packageFiles = resultsJava.Files;
+ catch ME
+ warning(ME.identifier,'Java build failed due to %s!',ME.message);
+ end
+end
+
+%% Docker
+if buildDocker
+ try
+ dockerOpts = compiler.package.DockerOptions(results,...
+ 'AdditionalInstructions','',...
+ 'AdditionalPackages','',...
+ 'ContainerUser','appuser',...
+ 'DockerContext',fullfile(buildDir,'docker'),...
+ 'ExecuteDockerBuild','on',...
+ 'ImageName','e0404/matrad');
+
+ compiler.package.docker(results,'Options',dockerOpts);
+ buildResult.docker.image = dockerOpts.ImageName;
+ catch ME
+ warning(ME.identifier,'Java build failed due to %s!',ME.message);
+ end
+end
+
+if ~isempty(json)
+ try
+ fH = fopen(json,'w');
+ jsonStr = jsonencode(buildResult,"PrettyPrint",true);
+ fwrite(fH,jsonStr);
+ fclose(fH);
+ catch ME
+ warning(ME.identifier,'Could not open JSON file for writing: %s',ME.message);
+ end
+end
+
+
+
+
+
+
diff --git a/matRad_calcCubes.m b/matRad_calcCubes.m
deleted file mode 100644
index 5eb0ec253..000000000
--- a/matRad_calcCubes.m
+++ /dev/null
@@ -1,126 +0,0 @@
-function resultGUI = matRad_calcCubes(w,dij,scenNum)
-% matRad computation of all cubes for the resultGUI struct
-% which is used as result container and for visualization in matRad's GUI
-%
-% call
-% resultGUI = matRad_calcCubes(w,dij)
-% resultGUI = matRad_calcCubes(w,dij,scenNum)
-%
-% input
-% w: bixel weight vector
-% dij: dose influence matrix
-% scenNum: optional: number of scenario to calculated (default 1)
-%
-% output
-% resultGUI: matRad result struct
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-if nargin < 3
- scenNum = 1;
-end
-
-resultGUI.w = w;
-
-% get bixel - beam correspondence
-for i = 1:dij.numOfBeams
- beamInfo(i).suffix = ['_beam', num2str(i)];
- beamInfo(i).logIx = (dij.beamNum == i);
-end
-beamInfo(dij.numOfBeams+1).suffix = '';
-beamInfo(dij.numOfBeams+1).logIx = true(size(w));
-%
-
-% compute physical dose for all beams individually and together
-for i = 1:length(beamInfo)
- resultGUI.(['physicalDose', beamInfo(i).suffix]) = reshape(full(dij.physicalDose{scenNum} * (resultGUI.w .* beamInfo(i).logIx)),dij.doseGrid.dimensions);
-end
-
-% consider RBE for protons
-if isfield(dij,'RBE')
- fprintf(['matRad: applying a constant RBE of ' num2str(dij.RBE) ' \n']);
- for i = 1:length(beamInfo)
- resultGUI.(['RBExDose', beamInfo(i).suffix]) = resultGUI.(['physicalDose', beamInfo(i).suffix]) * dij.RBE;
- end
-end
-
-% consider LET
-if isfield(dij,'mLETDose')
- for i = 1:length(beamInfo)
- LETDoseCube = dij.mLETDose{scenNum} * (resultGUI.w .* beamInfo(i).logIx);
- resultGUI.(['LET', beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
- ix = resultGUI.(['physicalDose', beamInfo(i).suffix]) > 0;
- resultGUI.(['LET', beamInfo(i).suffix])(ix) = LETDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix);
- end
-end
-
-if isfield(dij,'physicalDose_MCvar')
- resultGUI.physicalDose_MCvar = reshape(full(dij.physicalDose_MCvar{scenNum} * (resultGUI.w .* beamInfo(i).logIx)),dij.doseGrid.dimensions);
- resultGUI.physicalDose_MCstd = sqrt(resultGUI.physicalDose_MCvar);
- resultGUI.physicalDose_MCstdRel = resultGUI.physicalDose_MCstd ./ resultGUI.physicalDose;
-end
-
-% consider biological optimization for carbon ions
-if isfield(dij,'mAlphaDose') && isfield(dij,'mSqrtBetaDose')
-
- ix = dij.bx~=0;
-
- for i = 1:length(beamInfo)
- wBeam = (resultGUI.w .* beamInfo(i).logIx);
- resultGUI.(['effect', beamInfo(i).suffix]) = full(dij.mAlphaDose{scenNum} * wBeam + (dij.mSqrtBetaDose{scenNum} * wBeam).^2);
- resultGUI.(['effect', beamInfo(i).suffix]) = reshape(resultGUI.(['effect', beamInfo(i).suffix]),dij.doseGrid.dimensions);
-
- resultGUI.(['RBExDose', beamInfo(i).suffix]) = zeros(size(resultGUI.(['effect', beamInfo(i).suffix])));
- resultGUI.(['RBExDose', beamInfo(i).suffix])(ix) = (sqrt(dij.ax(ix).^2 + 4 .* dij.bx(ix) .* resultGUI.(['effect', beamInfo(i).suffix])(ix)) - dij.ax(ix))./(2.*dij.bx(ix));
-
- resultGUI.(['RBE', beamInfo(i).suffix]) = resultGUI.(['RBExDose', beamInfo(i).suffix])./resultGUI.(['physicalDose', beamInfo(i).suffix]);
-
- resultGUI.(['alpha', beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
- resultGUI.(['beta', beamInfo(i).suffix]) = zeros(dij.doseGrid.dimensions);
-
- AlphaDoseCube = full(dij.mAlphaDose{scenNum} * wBeam);
- resultGUI.(['alpha', beamInfo(i).suffix])(ix) = AlphaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix);
-
- SqrtBetaDoseCube = full(dij.mSqrtBetaDose{scenNum} * wBeam);
- resultGUI.(['beta', beamInfo(i).suffix])(ix) = (SqrtBetaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix)).^2;
- end
-end
-
-% group similar fields together
-resultGUI = orderfields(resultGUI);
-
-% interpolation if dose grid does not match ct grid
-if any(dij.ctGrid.dimensions~=dij.doseGrid.dimensions)
- myFields = fieldnames(resultGUI);
- for i = 1:numel(myFields)
-
- if numel(resultGUI.(myFields{i})) == dij.doseGrid.numOfVoxels
-
- % interpolate!
- resultGUI.(myFields{i}) = matRad_interp3(dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z, ...
- resultGUI.(myFields{i}), ...
- dij.ctGrid.x,dij.ctGrid.y',dij.ctGrid.z,'linear',0);
-
- end
-
- end
-end
-
-end
-
-
-
diff --git a/matRad_calcDoseDirect.m b/matRad_calcDoseDirect.m
deleted file mode 100644
index f34ef8497..000000000
--- a/matRad_calcDoseDirect.m
+++ /dev/null
@@ -1,86 +0,0 @@
-function resultGUI = matRad_calcDoseDirect(ct,stf,pln,cst,w)
-% matRad dose calculation wrapper bypassing dij calculation
-%
-% call
-% resultGUI = matRad_calcDoseDirect(ct,stf,pln,cst)
-% resultGUI = matRad_calcDoseDirect(ct,stf,pln,cst,w)
-%
-% input
-% ct: ct cube
-% stf: matRad steering information struct
-% pln: matRad plan meta information struct
-% cst: matRad cst struct
-% w: optional (if no weights available in stf): bixel weight
-% vector
-%
-% output
-% resultGUI: matRad result struct
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-calcDoseDirect = true;
-
-% check if weight vector is available, either in function call or in stf - otherwise dose calculation not possible
-if ~exist('w','var') && ~isfield([stf.ray],'weight')
- error('No weight vector available. Please provide w or add info to stf')
-end
-
-% copy bixel weight vector into stf struct
-if exist('w','var')
- if sum([stf.totalNumOfBixels]) ~= numel(w)
- error('weighting does not match steering information')
- end
- counter = 0;
- for i = 1:size(stf,2)
- for j = 1:stf(i).numOfRays
- for k = 1:stf(i).numOfBixelsPerRay(j)
- counter = counter + 1;
- stf(i).ray(j).weight(k) = w(counter);
- end
- end
- end
-else % weights need to be in stf!
- w = NaN*ones(sum([stf.totalNumOfBixels]),1);
- counter = 0;
- for i = 1:size(stf,2)
- for j = 1:stf(i).numOfRays
- for k = 1:stf(i).numOfBixelsPerRay(j)
- counter = counter + 1;
- w(counter) = stf(i).ray(j).weight(k);
- end
- end
- end
-end
-
-% dose calculation
-if strcmp(pln.radiationMode,'photons')
- dij = matRad_calcPhotonDose(ct,stf,pln,cst,calcDoseDirect);
- %dij = matRad_calcPhotonDoseVmc(ct,stf,pln,cst,5000,4,calcDoseDirect);
-elseif strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon')
- dij = matRad_calcParticleDose(ct,stf,pln,cst,calcDoseDirect);
-end
-
-% calculate cubes; use uniform weights here, weighting with actual fluence
-% already performed in dij construction
-resultGUI = matRad_calcCubes(ones(pln.propStf.numOfBeams,1),dij);
-
-% remember original fluence weights
-resultGUI.w = w;
-
-
-
-
diff --git a/matRad_calcDoseDirectMC.m b/matRad_calcDoseDirectMC.m
deleted file mode 100644
index b13e358fa..000000000
--- a/matRad_calcDoseDirectMC.m
+++ /dev/null
@@ -1,108 +0,0 @@
-function resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w,nHistories)
-% matRad function to bypass dij calculation for MC dose calculation
-% matRad dose calculation wrapper for MC dose calculation algorithms
-% bypassing dij calculation for MC dose calculation algorithms.
-%
-% call
-% resultGUI = matRad_calcDoseDirecMC(ct,stf,pln,cst)
-% resultGUI = matRad_calcDoseDirecMC(ct,stf,pln,cst,w)
-% resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,nHistories)
-% resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w,nHistories)
-%
-% input
-% ct: ct cube
-% stf: matRad steering information struct
-% pln: matRad plan meta information struct
-% cst: matRad cst struct
-% w: (optional, if no weights available in stf): bixel weight
-% vector
-% nHistories: (optional) number of histories
-%
-% output
-% resultGUI: matRad result struct
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-matRad_cfg = MatRad_Config.instance();
-
-calcDoseDirect = true;
-
-if nargin < 6 || ~exist('nHistories')
- nHistories = matRad_cfg.propMC.direct_defaultHistories;
- matRad_cfg.dispInfo('Using default number of Histories: %d\n',nHistories);
-end
-
-% check if weight vector is available, either in function call or in stf - otherwise dose calculation not possible
-if ~exist('w','var') && ~isfield([stf.ray],'weight')
- matRad_cfg.dispError('No weight vector available. Please provide w or add info to stf');
-end
-
-% copy bixel weight vector into stf struct
-if exist('w','var')
- if sum([stf.totalNumOfBixels]) ~= numel(w)
- matRad_cfg.dispError('weighting does not match steering information');
- end
- counter = 0;
- for i = 1:size(stf,2)
- for j = 1:stf(i).numOfRays
- for k = 1:stf(i).numOfBixelsPerRay(j)
- counter = counter + 1;
- stf(i).ray(j).weight(k) = w(counter);
- end
- end
- end
-else % weights need to be in stf!
- w = NaN*ones(sum([stf.totalNumOfBixels]),1);
- counter = 0;
- for i = 1:size(stf,2)
- for j = 1:stf(i).numOfRays
- for k = 1:stf(i).numOfBixelsPerRay(j)
- counter = counter + 1;
- w(counter) = stf(i).ray(j).weight(k);
- end
- end
- end
-end
-
-% dose calculation
-if strcmp(pln.radiationMode,'protons')
- dij = matRad_calcParticleDoseMC(ct,stf,pln,cst,nHistories,calcDoseDirect);
- % hack dij struct
- dij.numOfBeams = 1;
- dij.beamNum = 1;
- % calculate cubes; use uniform weights here, weighting with actual fluence
- % already performed in dij construction
- resultGUI = matRad_calcCubes(sum(w),dij);
- % remember original fluence weights
- resultGUI.w = w;
-elseif strcmp(pln.radiationMode,'photons')
- nCasePerBixel = nHistories;
- visBool = false;
- dij = matRad_calcPhotonDoseMC(ct,stf,pln,cst,nCasePerBixel,visBool);
- resultGUI = matRad_calcCubes(w,dij);
-else
- matRad_cfg.dispError('Forward MC only implemented for protons and photons.');
-end
-
-
-
-
-
-
-
-
diff --git a/matRad_calcDoseFillDij.m b/matRad_calcDoseFillDij.m
deleted file mode 100644
index 79f05cb11..000000000
--- a/matRad_calcDoseFillDij.m
+++ /dev/null
@@ -1,45 +0,0 @@
-% save computation time and memory
-% by sequentially filling the sparse matrix dose.dij from the cell array
-if mod(counter,numOfBixelsContainer) == 0 || counter == dij.totalNumOfBixels
-
- if calcDoseDirect
- if isfield(stf(1).ray(1),'weight') && numel(stf(i).ray(j).weight) >= k
-
- % score physical dose
- dij.physicalDose{1}(:,i) = dij.physicalDose{1}(:,i) + stf(i).ray(j).weight(k) * doseTmpContainer{1,1};
-
- if isfield(dij,'mLETDose')
- dij.mLETDose{1}(:,i) = dij.mLETDose{1}(:,i) + stf(i).ray(j).weight(k) * letDoseTmpContainer{1,1};
- end
-
- if (isequal(pln.propOpt.bioOptimization,'LEMIV_effect') || isequal(pln.propOpt.bioOptimization,'LEMIV_RBExD')) ...
- && strcmp(pln.radiationMode,'carbon')
-
- % score alpha and beta matrices
- dij.mAlphaDose{1}(:,i) = dij.mAlphaDose{1}(:,i) + stf(i).ray(j).weight(k) * alphaDoseTmpContainer{1,1};
- dij.mSqrtBetaDose{1}(:,i) = dij.mSqrtBetaDose{1}(:,i) + stf(i).ray(j).weight(k) * betaDoseTmpContainer{1,1};
-
- end
- else
-
- error(['No weight available for beam ' num2str(i) ', ray ' num2str(j) ', bixel ' num2str(k)]);
-
- end
- else
-
- dij.physicalDose{1}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [doseTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,1}];
-
- if isfield(dij,'mLETDose')
- dij.mLETDose{1}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [letDoseTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,1}];
- end
-
- if (isequal(pln.propOpt.bioOptimization,'LEMIV_effect') || isequal(pln.propOpt.bioOptimization,'LEMIV_RBExD')) ...
- && strcmp(pln.radiationMode,'carbon')
-
- dij.mAlphaDose{1}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [alphaDoseTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,1}];
- dij.mSqrtBetaDose{1}(:,(ceil(counter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:counter) = [betaDoseTmpContainer{1:mod(counter-1,numOfBixelsContainer)+1,1}];
- end
-
- end
-
-end
\ No newline at end of file
diff --git a/matRad_calcDoseInit.m b/matRad_calcDoseInit.m
deleted file mode 100644
index a46485057..000000000
--- a/matRad_calcDoseInit.m
+++ /dev/null
@@ -1,148 +0,0 @@
-
-matRad_cfg = MatRad_Config.instance();
-
-% default: dose influence matrix computation
-if ~exist('calcDoseDirect','var')
- calcDoseDirect = false;
-end
-
-% to guarantee downwards compatibility with data that does not have
-% ct.x/y/z
-if ~any(isfield(ct,{'x','y','z'}))
- ct.x = ct.resolution.x*[0:ct.cubeDim(2)-1]-ct.resolution.x/2;
- ct.y = ct.resolution.y*[0:ct.cubeDim(1)-1]-ct.resolution.y/2;
- ct.z = ct.resolution.z*[0:ct.cubeDim(3)-1]-ct.resolution.z/2;
-end
-
-% set grids
-if ~isfield(pln,'propDoseCalc') || ...
- ~isfield(pln.propDoseCalc,'doseGrid') || ...
- ~isfield(pln.propDoseCalc.doseGrid,'resolution')
- % default values
- dij.doseGrid.resolution = matRad_cfg.propDoseCalc.defaultResolution;
-else
- % take values from pln strcut
- dij.doseGrid.resolution.x = pln.propDoseCalc.doseGrid.resolution.x;
- dij.doseGrid.resolution.y = pln.propDoseCalc.doseGrid.resolution.y;
- dij.doseGrid.resolution.z = pln.propDoseCalc.doseGrid.resolution.z;
-end
-
-dij.doseGrid.x = ct.x(1):dij.doseGrid.resolution.x:ct.x(end);
-dij.doseGrid.y = ct.y(1):dij.doseGrid.resolution.y:ct.y(end);
-dij.doseGrid.z = ct.z(1):dij.doseGrid.resolution.z:ct.z(end);
-
-dij.doseGrid.dimensions = [numel(dij.doseGrid.y) numel(dij.doseGrid.x) numel(dij.doseGrid.z)];
-dij.doseGrid.numOfVoxels = prod(dij.doseGrid.dimensions);
-
-dij.ctGrid.resolution.x = ct.resolution.x;
-dij.ctGrid.resolution.y = ct.resolution.y;
-dij.ctGrid.resolution.z = ct.resolution.z;
-
-dij.ctGrid.x = ct.x;
-dij.ctGrid.y = ct.y;
-dij.ctGrid.z = ct.z;
-
-dij.ctGrid.dimensions = [numel(dij.ctGrid.y) numel(dij.ctGrid.x) numel(dij.ctGrid.z)];
-dij.ctGrid.numOfVoxels = prod(dij.ctGrid.dimensions);
-
-% adjust isocenter internally for different dose grid
-offset = [dij.doseGrid.resolution.x - dij.ctGrid.resolution.x ...
- dij.doseGrid.resolution.y - dij.ctGrid.resolution.y ...
- dij.doseGrid.resolution.z - dij.ctGrid.resolution.z];
-
-for i = 1:numel(stf)
- stf(i).isoCenter = stf(i).isoCenter + offset;
-end
-
-%set up HU to rED or rSP conversion
-if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'useGivenEqDensityCube')
- disableHUconversion = matRad_cfg.propDoseCalc.defaultUseGivenEqDensityCube;
-else
- disableHUconversion = pln.propDoseCalc.useGivenEqDensityCube;
-end
-
-%If we want to omit HU conversion check if we have a ct.cube ready
-if disableHUconversion && ~isfield(ct,'cube')
- matRad_cfg.dispWarning('HU Conversion requested to be omitted but no ct.cube exists! Will override and do the conversion anyway!');
- disableHUconversion = false;
-end
-
-% calculate rED or rSP from HU
-if disableHUconversion
- matRad_cfg.dispInfo('Omitting HU to rED/rSP conversion and using existing ct.cube!\n');
-else
- ct = matRad_calcWaterEqD(ct, pln);
-end
-
-% meta information for dij
-dij.numOfBeams = pln.propStf.numOfBeams;
-dij.numOfScenarios = 1;
-dij.numOfRaysPerBeam = [stf(:).numOfRays];
-dij.totalNumOfBixels = sum([stf(:).totalNumOfBixels]);
-dij.totalNumOfRays = sum(dij.numOfRaysPerBeam);
-
-% check if full dose influence data is required
-if calcDoseDirect
- numOfColumnsDij = length(stf);
- numOfBixelsContainer = 1;
-else
- numOfColumnsDij = dij.totalNumOfBixels;
- numOfBixelsContainer = ceil(dij.totalNumOfBixels/10);
-end
-
-% set up arrays for book keeping
-dij.bixelNum = NaN*ones(numOfColumnsDij,1);
-dij.rayNum = NaN*ones(numOfColumnsDij,1);
-dij.beamNum = NaN*ones(numOfColumnsDij,1);
-
-
-% Allocate space for dij.physicalDose sparse matrix
-for i = 1:dij.numOfScenarios
- dij.physicalDose{i} = spalloc(dij.doseGrid.numOfVoxels,numOfColumnsDij,1);
-end
-
-% Allocate memory for dose_temp cell array
-doseTmpContainer = cell(numOfBixelsContainer,dij.numOfScenarios);
-
-% take only voxels inside patient
-VctGrid = [cst{:,4}];
-VctGrid = unique(vertcat(VctGrid{:}));
-
-% ignore densities outside of contours
-if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'ignoreOutsideDensities')
- ignoreOutsideDensities = matRad_cfg.propDoseCalc.defaultIgnoreOutsideDensities;
-else
- ignoreOutsideDensities = pln.propDoseCalc.ignoreOutsideDensities;
-end
-
-if ignoreOutsideDensities
- eraseCtDensMask = ones(prod(ct.cubeDim),1);
- eraseCtDensMask(VctGrid) = 0;
- for i = 1:ct.numOfCtScen
- ct.cube{i}(eraseCtDensMask == 1) = 0;
- end
-end
-
-% Convert CT subscripts to linear indices.
-[yCoordsV_vox, xCoordsV_vox, zCoordsV_vox] = ind2sub(ct.cubeDim,VctGrid);
-
-% receive linear indices and grid locations from the dose grid
-tmpCube = zeros(ct.cubeDim);
-tmpCube(VctGrid) = 1;
-% interpolate cube
-VdoseGrid = find(matRad_interp3(dij.ctGrid.x, dij.ctGrid.y, dij.ctGrid.z,tmpCube, ...
- dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'nearest'));
-
-% Convert CT subscripts to coarse linear indices.
-[yCoordsV_voxDoseGrid, xCoordsV_voxDoseGrid, zCoordsV_voxDoseGrid] = ind2sub(dij.doseGrid.dimensions,VdoseGrid);
-
-% load base data% load machine file
-fileName = [pln.radiationMode '_' pln.machine];
-try
- load([fileparts(mfilename('fullpath')) filesep 'basedata' filesep fileName]);
-catch
- matRad_cfg.dispError('Could not find the following machine file: %s\n',fileName);
-end
-
-% compute SSDs
-stf = matRad_computeSSD(stf,ct);
diff --git a/matRad_calcDoseInitBeam.m b/matRad_calcDoseInitBeam.m
deleted file mode 100644
index d1a2fa484..000000000
--- a/matRad_calcDoseInitBeam.m
+++ /dev/null
@@ -1,55 +0,0 @@
-matRad_cfg.dispInfo('Beam %d of %d:\n',i,dij.numOfBeams);
-
-% remember beam and bixel number
-if calcDoseDirect
- dij.beamNum(i) = i;
- dij.rayNum(i) = i;
- dij.bixelNum(i) = i;
-end
-
-bixelsPerBeam = 0;
-
-% convert voxel indices to real coordinates using iso center of beam i
-xCoordsV = xCoordsV_vox(:)*ct.resolution.x-stf(i).isoCenter(1);
-yCoordsV = yCoordsV_vox(:)*ct.resolution.y-stf(i).isoCenter(2);
-zCoordsV = zCoordsV_vox(:)*ct.resolution.z-stf(i).isoCenter(3);
-coordsV = [xCoordsV yCoordsV zCoordsV];
-
-xCoordsVdoseGrid = xCoordsV_voxDoseGrid(:)*dij.doseGrid.resolution.x-stf(i).isoCenter(1);
-yCoordsVdoseGrid = yCoordsV_voxDoseGrid(:)*dij.doseGrid.resolution.y-stf(i).isoCenter(2);
-zCoordsVdoseGrid = zCoordsV_voxDoseGrid(:)*dij.doseGrid.resolution.z-stf(i).isoCenter(3);
-coordsVdoseGrid = [xCoordsVdoseGrid yCoordsVdoseGrid zCoordsVdoseGrid];
-
-% Get Rotation Matrix
-% Do not transpose matrix since we usage of row vectors &
-% transformation of the coordinate system need double transpose
-
-rotMat_system_T = matRad_getRotationMatrix(stf(i).gantryAngle,stf(i).couchAngle);
-
-% Rotate coordinates (1st couch around Y axis, 2nd gantry movement)
-rot_coordsV = coordsV*rotMat_system_T;
-rot_coordsVdoseGrid = coordsVdoseGrid*rotMat_system_T;
-
-rot_coordsV(:,1) = rot_coordsV(:,1)-stf(i).sourcePoint_bev(1);
-rot_coordsV(:,2) = rot_coordsV(:,2)-stf(i).sourcePoint_bev(2);
-rot_coordsV(:,3) = rot_coordsV(:,3)-stf(i).sourcePoint_bev(3);
-
-rot_coordsVdoseGrid(:,1) = rot_coordsVdoseGrid(:,1)-stf(i).sourcePoint_bev(1);
-rot_coordsVdoseGrid(:,2) = rot_coordsVdoseGrid(:,2)-stf(i).sourcePoint_bev(2);
-rot_coordsVdoseGrid(:,3) = rot_coordsVdoseGrid(:,3)-stf(i).sourcePoint_bev(3);
-
-% calculate geometric distances
-geoDistVdoseGrid{1}= sqrt(sum(rot_coordsVdoseGrid.^2,2));
-
-% Calculate radiological depth cube
-matRad_cfg.dispInfo('matRad: calculate radiological depth cube... ');
-radDepthVctGrid = matRad_rayTracing(stf(i),ct,VctGrid,rot_coordsV,effectiveLateralCutoff);
-matRad_cfg.dispInfo('done.\n');
-
-% interpolate radiological depth cube to dose grid resolution
-radDepthVdoseGrid = matRad_interpRadDepth...
- (ct,1,VctGrid,VdoseGrid,dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z,radDepthVctGrid);
-
-% limit rotated coordinates to positions where ray tracing is availabe
-rot_coordsVdoseGrid = rot_coordsVdoseGrid(~isnan(radDepthVdoseGrid{1}),:);
-
\ No newline at end of file
diff --git a/matRad_calcGeoDists.m b/matRad_calcGeoDists.m
deleted file mode 100644
index ef9c30b50..000000000
--- a/matRad_calcGeoDists.m
+++ /dev/null
@@ -1,108 +0,0 @@
-function [ix,rad_distancesSq,isoLatDistsX,isoLatDistsZ] = ...
- matRad_calcGeoDists(rot_coords_bev, ...
- sourcePoint_bev, ...
- targetPoint_bev, ...
- SAD, ...
- radDepthIx, ...
- lateralCutOff)
-% matRad calculation of lateral distances from central ray
-% used for dose calcultion
-%
-% call
-% [ix,rad_distancesSq,isoLatDistsX,isoLatDistsZ] = ...
-% matRad_calcGeoDists(rot_coords_bev, ...
-% sourcePoint_bev, ...
-% targetPoint_bev, ...
-% SAD, ...
-% radDepthIx, ...
-% lateralCutOff)
-%
-% input
-% rot_coords_bev: coordinates in bev of the voxels with index V,
-% where also ray tracing results are availabe
-% sourcePoint_bev: source point in voxel coordinates in beam's eye view
-% targetPoint_bev: target point in voxel coordinated in beam's eye view
-% SAD: source-to-axis distance
-% radDepthIx: sub set of voxels for which radiological depth
-% calculations are available
-% lateralCutOff: lateral cutoff specifying the neighbourhood for
-% which dose calculations will actually be performed
-%
-% output
-% ix: indices of voxels where we want to compute dose
-% influence data
-% rad_distancesSq: squared radial distance to the central ray (where the
-% actual computation of the radiological depth takes place)
-% isoLatDistsX: lateral x-distance to the central ray projected to
-% iso center plane
-% isoLatDistsZ: lateral z-distance to the central ray projected to
-% iso center plane
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% ROTATE A SINGLE BEAMLET AND ALIGN WITH BEAMLET WHO PASSES THROUGH
-% ISOCENTER
-
-% Put [0 0 0] position in the source point for beamlet who passes through
-% isocenter
-a = -sourcePoint_bev';
-
-% Normalize the vector
-a = a/norm(a);
-
-% Put [0 0 0] position in the source point for a single beamlet
-b = (targetPoint_bev - sourcePoint_bev)';
-
-% Normalize the vector
-b = b/norm(b);
-
-% Define function for obtain rotation matrix.
-if all(a==b) % rotation matrix corresponds to eye matrix if the vectors are the same
- rot_coords_temp = rot_coords_bev;
-else
- % Define rotation matrix
- ssc = @(v) [0 -v(3) v(2); v(3) 0 -v(1); -v(2) v(1) 0];
- R = eye(3) + ssc(cross(a,b)) + ssc(cross(a,b))^2*(1-dot(a,b))/(norm(cross(a,b))^2);
-
- % Rotate every CT voxel
- rot_coords_temp = rot_coords_bev*R;
-end
-
-% Put [0 0 0] position CT in center of the beamlet.
-latDistsX = rot_coords_temp(:,1) + sourcePoint_bev(1);
-latDistsZ = rot_coords_temp(:,3) + sourcePoint_bev(3);
-
-% check of radial distance exceeds lateral cutoff (projected to iso center)
-rad_distancesSq = latDistsX.^2 + latDistsZ.^2;
-subsetMask = rad_distancesSq ./ rot_coords_temp(:,2).^2 <= lateralCutOff^2 /SAD^2;
-
-% return index list within considered voxels
-ix = radDepthIx(subsetMask);
-
-% return radial distances squared
-if nargout > 1
- rad_distancesSq = rad_distancesSq(subsetMask);
-end
-
-% return x & z distance
-if nargout > 2
- isoLatDistsX = latDistsX(subsetMask)./rot_coords_temp(subsetMask,2)*SAD;
- isoLatDistsZ = latDistsZ(subsetMask)./rot_coords_temp(subsetMask,2)*SAD;
-end
-
-
diff --git a/matRad_calcLateralParticleCutOff.m b/matRad_calcLateralParticleCutOff.m
deleted file mode 100644
index b4eea1860..000000000
--- a/matRad_calcLateralParticleCutOff.m
+++ /dev/null
@@ -1,342 +0,0 @@
-function [ machine ] = matRad_calcLateralParticleCutOff(machine,cutOffLevel,stf,visBool)
-% matRad function to calculate a depth dependend lateral cutoff
-% for each pristine particle beam
-%
-% call
-% [ machine ] = matRad_calcLateralParticleCutOff( machine,cutOffLevel,stf,visBool )
-%
-% input
-% machine: machine base data file
-% cutOffLevel: cut off level - number between 0 and 1
-% stf: matRad steering information struct
-% visBool: toggle visualization (optional)
-%
-% output
-% machine: machine base data file including an additional field representing the lateral
-% cutoff
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-if cutOffLevel <= 0.98
- warning('a lateral cut off below 0.98 may result in an inaccurate dose calculation')
-end
-
-conversionFactor = 1.6021766208e-02;
-
-% function handle for calculating depth dose for APM
-sumGauss = @(x,mu,SqSigma,w) ((1./sqrt(2*pi*ones(numel(x),1) * SqSigma') .* ...
- exp(-bsxfun(@minus,x,mu').^2 ./ (2* ones(numel(x),1) * SqSigma' ))) * w);
-
-if (cutOffLevel < 0 || cutOffLevel > 1)
- warning('lateral cutoff is out of range - using default cut off of 0.99')
- cutOffLevel = 0.99;
-end
-% define some variables needed for the cutoff calculation
-vX = [0 logspace(-1,3,1200)]; % [mm]
-
-% integration steps
-r_mid = 0.5*(vX(1:end-1) + vX(2:end))'; % [mm]
-dr = (vX(2:end) - vX(1:end-1))';
-radialDist_sq = r_mid.^2;
-
-% number of depth points for which a lateral cutoff is determined
-numDepthVal = 35;
-
-% helper function for energy selection
-round2 = @(a,b)round(a*10^b)/10^b;
-
-% extract SSD for each bixel
-vSSD = ones(1,length([stf.ray(:).energy]));
-cnt = 1;
-for i = 1:length(stf.ray)
- vSSD(cnt:cnt+numel([stf.ray(i).energy])-1) = stf.ray(i).SSD;
- cnt = cnt + numel(stf.ray(i).energy);
-end
-
-% setup energy, focus index, sigma look up table - only consider unique rows
-[energySigmaLUT,ixUnique] = unique([[stf.ray(:).energy]; [stf.ray(:).focusIx] ; vSSD]','rows');
-rangeShifterLUT = [stf.ray(:).rangeShifter];
-rangeShifterLUT = rangeShifterLUT(1,ixUnique);
-
-% find the largest inital beam width considering focus index, SSD and range shifter for each individual energy
-for i = 1:size(energySigmaLUT,1)
-
- % find index of maximum used energy (round to keV for numerical reasons
- energyIx = max(round2(energySigmaLUT(i,1),4)) == round2([machine.data.energy],4);
-
- currFoci = energySigmaLUT(i,2);
- sigmaIni = matRad_interp1(machine.data(energyIx).initFocus.dist(currFoci,:)',...
- machine.data(energyIx).initFocus.sigma(currFoci,:)',...
- energySigmaLUT(i,3));
- sigmaIni_sq = sigmaIni^2;
-
- % consider range shifter for protons if applicable
- if strcmp(machine.meta.radiationMode,'protons') && rangeShifterLUT(i).eqThickness > 0 && ~strcmp(machine.meta.machine,'Generic')
-
- %get max range shift
- sigmaRashi = matRad_calcSigmaRashi(machine.data(energyIx).energy, ...
- rangeShifterLUT(i), ...
- energySigmaLUT(i,3));
-
- % add to initial sigma in quadrature
- sigmaIni_sq = sigmaIni_sq + sigmaRashi.^2;
-
- end
-
- energySigmaLUT(i,4) = sigmaIni_sq;
-
-end
-
-% find for each individual energy the broadest inital beam width
-uniqueEnergies = unique(energySigmaLUT(:,1));
-largestSigmaSq4uniqueEnergies = NaN * ones(numel(uniqueEnergies),1);
-ix_Max = NaN * ones(numel(uniqueEnergies),1);
-for i = 1:numel(uniqueEnergies)
- [largestSigmaSq4uniqueEnergies(i), ix_Max(i)] = max(energySigmaLUT(uniqueEnergies(i) == energySigmaLUT(:,1),4));
-end
-
-% get energy indices for looping
-vEnergiesIx = find(ismember([machine.data(:).energy],uniqueEnergies(:,1)));
-cnt = 0;
-
-% loop over all entries in the machine.data struct
-for energyIx = vEnergiesIx
-
- % set default depth cut off - finite value will be set during first iteration
- depthDoseCutOff = inf;
-
- % get the current integrated depth dose profile
- if isstruct(machine.data(energyIx).Z)
- idd_org = sumGauss(machine.data(energyIx).depths,machine.data(energyIx).Z.mean,...
- machine.data(energyIx).Z.width.^2,...
- machine.data(energyIx).Z.weight) * conversionFactor;
- else
- idd_org = machine.data(energyIx).Z * conversionFactor;
- end
-
- [~,peakIxOrg] = max(idd_org);
-
- % get indices for which a lateral cutoff should be calculated
- cumIntEnergy = cumtrapz(machine.data(energyIx).depths,idd_org);
-
- peakTailRelation = 0.5;
- numDepthValToPeak = ceil(numDepthVal*peakTailRelation); % number of depth values from 0 to peak position
- numDepthValTail = ceil(numDepthVal*(1-peakTailRelation)); % number of depth values behind peak position
- energyStepsToPeak = cumIntEnergy(peakIxOrg)/numDepthValToPeak;
- energyStepsTail = (cumIntEnergy(end)-cumIntEnergy(peakIxOrg))/numDepthValTail;
- % make sure to include 0, peak position and end position
- vEnergySteps = unique([0:energyStepsToPeak:cumIntEnergy(peakIxOrg) cumIntEnergy(peakIxOrg) ...
- cumIntEnergy(peakIxOrg+1):energyStepsTail:cumIntEnergy(end) cumIntEnergy(end)]);
-
- [cumIntEnergy,ix] = unique(cumIntEnergy);
- depthValues = matRad_interp1(cumIntEnergy,machine.data(energyIx).depths(ix),vEnergySteps);
-
- if isstruct(machine.data(energyIx).Z)
- idd = sumGauss(depthValues,machine.data(energyIx).Z.mean,...
- machine.data(energyIx).Z.width.^2,...
- machine.data(energyIx).Z.weight) * conversionFactor;
- else
- idd = matRad_interp1(machine.data(energyIx).depths,machine.data(energyIx).Z,depthValues) * conversionFactor;
- end
-
- cnt = cnt +1 ;
- % % calculate dose in spot
- baseData = machine.data(energyIx);
- baseData.LatCutOff.CompFac = 1;
-
- for j = 1:numel(depthValues)
-
- % save depth value
- machine.data(energyIx).LatCutOff.depths(j) = depthValues(j);
-
- if cutOffLevel == 1
- machine.data(energyIx).LatCutOff.CompFac = 1;
- machine.data(energyIx).LatCutOff.CutOff(j) = Inf;
- else
-
- % calculate dose
- dose_r = matRad_calcParticleDoseBixel(depthValues(j) + baseData.offset, radialDist_sq, largestSigmaSq4uniqueEnergies(cnt), baseData);
-
- cumArea = cumsum(2*pi.*r_mid.*dose_r.*dr);
- relativeTolerance = 0.5; %in [%]
- if abs((cumArea(end)./(idd(j)))-1)*100 > relativeTolerance
- warning('LateralParticleCutOff: shell integration is wrong !')
- end
-
- IX = find(cumArea >= idd(j) * cutOffLevel,1, 'first');
- machine.data(energyIx).LatCutOff.CompFac = cutOffLevel^-1;
-
- if isempty(IX)
- depthDoseCutOff = Inf;
- warning('LateralParticleCutOff: Couldnt find lateral cut off !')
- elseif isnumeric(IX)
- depthDoseCutOff = r_mid(IX);
- end
-
- machine.data(energyIx).LatCutOff.CutOff(j) = depthDoseCutOff;
-
- end
- end
-end
-
-%% visualization
-if visBool
-
- % determine which pencil beam should be plotted
- subIx = ceil(numel(vEnergiesIx)/2);
- energyIx = vEnergiesIx(subIx);
-
- baseData = machine.data(energyIx);
- focusIx = energySigmaLUT(ix_Max(subIx),2);
- maxSSD = energySigmaLUT(ix_Max(subIx),3);
- rangeShifter = rangeShifterLUT(ix_Max(subIx));
- TmpCompFac = baseData.LatCutOff.CompFac;
- baseData.LatCutOff.CompFac = 1;
-
- % plot 3D cutoff at one specific depth on a rather sparse grid
- sStep = 0.5;
- vLatX = -100 : sStep : 100; % [mm]
- dimX = numel(vLatX);
- midPos = round(length(vLatX)/2);
- [X,Y] = meshgrid(vLatX,vLatX);
-
- radDepths = [0:sStep:machine.data(energyIx).depths(end)] + machine.data(energyIx).offset;
- radialDist_sq = (X.^2 + Y.^2);
- radialDist_sq = radialDist_sq(:);
- mDose = zeros(dimX,dimX,numel(radDepths));
- vDoseInt = zeros(numel(radDepths),1);
-
- for kk = 1:numel(radDepths)
-
- % calculate initial focus sigma
- sigmaIni = matRad_interp1(machine.data(energyIx).initFocus.dist(focusIx,:)', ...
- machine.data(energyIx).initFocus.sigma(focusIx,:)',maxSSD);
- sigmaIni_sq = sigmaIni^2;
-
- % consider range shifter for protons if applicable
- if rangeShifter.eqThickness > 0 && strcmp(pln.radiationMode,'protons')
-
- % compute!
- sigmaRashi = matRad_calcSigmaRashi(machine.data(energyIx).energy,rangeShifter,maxSSD);
-
- % add to initial sigma in quadrature
- sigmaIni_sq = sigmaIni_sq + sigmaRashi^2;
-
- end
-
- mDose(:,:,kk) = reshape(matRad_calcParticleDoseBixel(radDepths(kk), radialDist_sq, sigmaIni_sq,baseData),[dimX dimX]);
-
- [~,IX] = min(abs((machine.data(energyIx).LatCutOff.depths + machine.data(energyIx).offset) - radDepths(kk)));
- TmpCutOff = machine.data(energyIx).LatCutOff.CutOff(IX);
- vXCut = vX(vX<=TmpCutOff);
-
- % integration steps
- r_mid_Cut = (0.5*(vXCut(1:end-1) + vXCut(2:end)))'; % [mm]
- dr_Cut = (vXCut(2:end) - vXCut(1:end-1))';
- radialDist_sqCut = r_mid_Cut.^2;
-
- dose_r_Cut = matRad_calcParticleDoseBixel(radDepths(kk), radialDist_sqCut(:), sigmaIni_sq,baseData);
-
- cumAreaCut = cumsum(2*pi.*r_mid_Cut.*dose_r_Cut.*dr_Cut);
-
- if ~isempty(cumAreaCut)
- vDoseInt(kk) = cumAreaCut(end);
- end
- end
-
- % obtain maximum dose
- if isstruct(machine.data(energyIx).Z)
- idd = sumGauss(depthValues,machine.data(energyIx).Z.mean,...
- machine.data(energyIx).Z.width.^2,...
- machine.data(energyIx).Z.weight) * conversionFactor;
- else
- idd = matRad_interp1(machine.data(energyIx).depths,machine.data(energyIx).Z,depthValues) * conversionFactor;
- end
-
- [~,peakixDepth] = max(idd);
- dosePeakPos = matRad_calcParticleDoseBixel(machine.data(energyIx).depths(peakixDepth), 0, sigmaIni_sq, baseData);
-
- vLevelsDose = dosePeakPos.*[0.01 0.05 0.1 0.9];
- doseSlice = squeeze(mDose(midPos,:,:));
- figure,set(gcf,'Color',[1 1 1]);
- subplot(311),h=imagesc(squeeze(mDose(midPos,:,:)));hold on;
- set(h,'AlphaData', .8*double(doseSlice>0));
- contour(doseSlice,vLevelsDose,'LevelListMode','manual','LineWidth',2);hold on
-
- ax = gca;
- ax.XTickLabelMode = 'manual';
- ax.XTickLabel = strsplit(num2str(ax.XTick*sStep + machine.data(energyIx).offset),' ')';
- ax.YTickLabelMode = 'manual';
- ax.YTickLabel = strsplit(num2str(ax.YTick*sStep + machine.data(energyIx).offset),' ')';
-
- plot(1+(machine.data(energyIx).LatCutOff.depths)*sStep^-1,...
- machine.data(energyIx).LatCutOff.CutOff * sStep^-1 + midPos,'rx');
-
- legend({'isodose 1%,5%,10% 90%','calculated cutoff'}) ,colorbar,set(gca,'FontSize',12),xlabel('z [mm]'),ylabel('x [mm]');
-
- entry = machine.data(energyIx);
- if isstruct(entry.Z)
- idd = sumGauss(entry.depths,entry.Z.mean,entry.Z.width.^2,entry.Z.weight);
- else
- idd = machine.data(energyIx).Z;
- end
- subplot(312),plot(machine.data(energyIx).depths,idd*conversionFactor,'k','LineWidth',2),grid on,hold on
- plot(radDepths - machine.data(energyIx).offset,vDoseInt,'r--','LineWidth',2),hold on,
- plot(radDepths - machine.data(energyIx).offset,vDoseInt * TmpCompFac,'bx','LineWidth',1),hold on,
- legend({'original IDD',['cut off IDD at ' num2str(cutOffLevel) '%'],'cut off IDD with compensation'},'Location','northwest'),
- xlabel('z [mm]'),ylabel('[MeV cm^2 /(g * primary)]'),set(gca,'FontSize',12)
-
- totEnergy = trapz(machine.data(energyIx).depths,idd*conversionFactor) ;
- totEnergyCutOff = trapz(radDepths,vDoseInt * TmpCompFac) ;
- relDiff = ((totEnergy/totEnergyCutOff)-1)*100;
- title(['rel diff of integral dose ' num2str(relDiff) '%']);
- baseData.LatCutOff.CompFac = TmpCompFac;
-
- subplot(313),
- if isfield(machine.data(energyIx),'sigma1')
- yyaxis left;
- plot(machine.data(energyIx).LatCutOff.depths,machine.data(energyIx).LatCutOff.CutOff,'LineWidth',2),hold on
- plot(machine.data(energyIx).depths,(machine.data(energyIx).sigma1),':','LineWidth',2),grid on,hold on,ylabel('mm')
- yyaxis right;
- plot(machine.data(energyIx).depths,(machine.data(energyIx).sigma2),'-.','LineWidth',2),grid on,hold on,ylabel('mm')
- legend({'Cutoff','sigma1','sigma2'});
- else
- yyaxis left;plot(machine.data(energyIx).LatCutOff.depths,machine.data(energyIx).LatCutOff.CutOff,'LineWidth',2),hold on,ylabel('mm')
- yyaxis right;subplot(313),plot(machine.data(energyIx).depths,machine.data(energyIx).sigma,'LineWidth',2),grid on,hold on
- legend({'Cutoff','sigma'});ylabel('mm')
- end
-
- set(gca,'FontSize',12),xlabel('z [mm]'), ylabel('mm')
-
- % plot cutoff of different energies
- figure,set(gcf,'Color',[1 1 1]);
- cnt = 1;
- for i = vEnergiesIx
- plot(machine.data(i).LatCutOff.depths,machine.data(i).LatCutOff.CutOff,'LineWidth',1.5),hold on
- cellLegend{cnt} = [num2str(machine.data(i).energy) ' MeV'];
- cnt = cnt + 1;
- end
- grid on, grid minor,xlabel('depth in [mm]'),ylabel('lateral cutoff in [mm]')
- title(['cutoff level = ' num2str(cutOffLevel)]),
- ylim = get(gca,'Ylim'); set(gca,'Ylim',[0 ylim(2)+3]), legend(cellLegend)
-end
-
-
-
-
-end
-
diff --git a/matRad_calcParticleDose.m b/matRad_calcParticleDose.m
deleted file mode 100644
index 9efdb8aff..000000000
--- a/matRad_calcParticleDose.m
+++ /dev/null
@@ -1,305 +0,0 @@
-function dij = matRad_calcParticleDose(ct,stf,pln,cst,calcDoseDirect)
-% matRad particle dose calculation wrapper
-%
-% call
-% dij = matRad_calcParticleDose(ct,stf,pln,cst,calcDoseDirect)
-%
-% input
-% ct: ct cube
-% stf: matRad steering information struct
-% pln: matRad plan meta information struct
-% cst: matRad cst struct
-% calcDoseDirect: boolian switch to bypass dose influence matrix
-% computation and directly calculate dose; only makes
-% sense in combination with matRad_calcDoseDirect.m
-%
-% output
-% dij: matRad dij struct
-%
-% References
-% [1] http://iopscience.iop.org/0031-9155/41/8/005
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-matRad_cfg = MatRad_Config.instance();
-
-% init dose calc
-matRad_calcDoseInit;
-
-% initialize waitbar
-figureWait = waitbar(0,'calculate dose influence matrix for particles...');
-% prevent closure of waitbar and show busy state
-set(figureWait,'pointer','watch');
-
-% helper function for energy selection
-round2 = @(a,b)round(a*10^b)/10^b;
-
-if (isequal(pln.propOpt.bioOptimization,'LEMIV_effect') || isequal(pln.propOpt.bioOptimization,'LEMIV_RBExD')) ...
- && strcmp(pln.radiationMode,'carbon')
-
- alphaDoseTmpContainer = cell(numOfBixelsContainer,dij.numOfScenarios);
- betaDoseTmpContainer = cell(numOfBixelsContainer,dij.numOfScenarios);
- for i = 1:dij.numOfScenarios
- dij.mAlphaDose{i} = spalloc(dij.doseGrid.numOfVoxels,numOfColumnsDij,1);
- dij.mSqrtBetaDose{i} = spalloc(dij.doseGrid.numOfVoxels,numOfColumnsDij,1);
- end
-
-elseif isequal(pln.propOpt.bioOptimization,'const_RBExD') && strcmp(pln.radiationMode,'protons')
- dij.RBE = 1.1;
- matRad_cfg.dispInfo('matRad: Using a constant RBE of %g\n',dij.RBE);
-end
-
-if isfield(pln,'propDoseCalc') && ...
- isfield(pln.propDoseCalc,'calcLET') && ...
- pln.propDoseCalc.calcLET
- if isfield(machine.data,'LET')
- letDoseTmpContainer = cell(numOfBixelsContainer,dij.numOfScenarios);
- % Allocate space for dij.dosexLET sparse matrix
- for i = 1:dij.numOfScenarios
- dij.mLETDose{i} = spalloc(dij.doseGrid.numOfVoxels,numOfColumnsDij,1);
- end
- else
- matRad_cfg.dispWarning('LET not available in the machine data. LET will not be calculated.');
- end
-end
-
-% generates tissue class matrix for biological optimization
-if (isequal(pln.propOpt.bioOptimization,'LEMIV_effect') || isequal(pln.propOpt.bioOptimization,'LEMIV_RBExD')) ...
- && strcmp(pln.radiationMode,'carbon')
-
- if isfield(machine.data,'alphaX') && isfield(machine.data,'betaX')
-
- matRad_cfg.dispInfo('matRad: loading biological base data... ');
- vTissueIndex = zeros(size(VdoseGrid,1),1);
- dij.ax = zeros(dij.doseGrid.numOfVoxels,1);
- dij.bx = zeros(dij.doseGrid.numOfVoxels,1);
-
- cst = matRad_setOverlapPriorities(cst);
-
- % resizing cst to dose cube resolution
- cst = matRad_resizeCstToGrid(cst,dij.ctGrid.x,dij.ctGrid.y,dij.ctGrid.z,...
- dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z);
- % retrieve photon LQM parameter for the current dose grid voxels
- [dij.ax,dij.bx] = matRad_getPhotonLQMParameters(cst,dij.doseGrid.numOfVoxels,1,VdoseGrid);
-
- for i = 1:size(cst,1)
-
- % check if cst is compatiable
- if ~isempty(cst{i,5}) && isfield(cst{i,5},'alphaX') && isfield(cst{i,5},'betaX')
-
- % check if base data contains alphaX and betaX
- IdxTissue = find(ismember(machine.data(1).alphaX,cst{i,5}.alphaX) & ...
- ismember(machine.data(1).betaX,cst{i,5}.betaX));
-
- % check consitency of biological baseData and cst settings
- if ~isempty(IdxTissue)
- isInVdoseGrid = ismember(VdoseGrid,cst{i,4}{1});
- vTissueIndex(isInVdoseGrid) = IdxTissue;
- else
- matRad_cfg.dispError('biological base data and cst inconsistent\n');
- end
-
- else
- vTissueIndex(row) = 1;
- matRad_cfg.dispInfo(['matRad: tissue type of ' cst{i,2} ' was set to 1\n']);
- end
- end
- matRad_cfg.dispInfo('done.\n');
-
- else
-
- matRad_cfg.dispError('base data is incomplement - alphaX and/or betaX is missing');
-
- end
-
-% issue warning if biological optimization not possible
-elseif sum(strcmp(pln.propOpt.bioOptimization,{'LEMIV_effect','LEMIV_RBExD'}))>0 && ~strcmp(pln.radiationMode,'carbon') ||...
- ~strcmp(pln.radiationMode,'protons') && strcmp(pln.propOpt.bioOptimization,'const_RBExD')
- warndlg([pln.propOpt.bioOptimization ' optimization not possible with ' pln.radiationMode '- physical optimization is carried out instead.']);
- pln.propOpt.bioOptimization = 'none';
-end
-
-% lateral cutoff for raytracing and geo calculations
-effectiveLateralCutoff = matRad_cfg.propDoseCalc.defaultGeometricCutOff;
-
-matRad_cfg.dispInfo('matRad: Particle dose calculation...\n');
-counter = 0;
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-for i = 1:length(stf) % loop over all beams
-
- % init beam
- matRad_calcDoseInitBeam;
-
- % Determine lateral cutoff
- matRad_cfg.dispInfo('matRad: calculate lateral cutoff...');
- cutOffLevel = matRad_cfg.propDoseCalc.defaultLateralCutOff;
- visBoolLateralCutOff = 0;
- machine = matRad_calcLateralParticleCutOff(machine,cutOffLevel,stf(i),visBoolLateralCutOff);
- matRad_cfg.dispInfo('done.\n');
-
- for j = 1:stf(i).numOfRays % loop over all rays
-
- if ~isempty(stf(i).ray(j).energy)
-
- % find index of maximum used energy (round to keV for numerical
- % reasons
- energyIx = max(round2(stf(i).ray(j).energy,4)) == round2([machine.data.energy],4);
-
- maxLateralCutoffDoseCalc = max(machine.data(energyIx).LatCutOff.CutOff);
-
- % Ray tracing for beam i and ray j
- [ix,radialDist_sq] = matRad_calcGeoDists(rot_coordsVdoseGrid, ...
- stf(i).sourcePoint_bev, ...
- stf(i).ray(j).targetPoint_bev, ...
- machine.meta.SAD, ...
- find(~isnan(radDepthVdoseGrid{1})), ...
- maxLateralCutoffDoseCalc);
-
- radDepths = radDepthVdoseGrid{1}(ix);
-
- % just use tissue classes of voxels found by ray tracer
- if (isequal(pln.propOpt.bioOptimization,'LEMIV_effect') || isequal(pln.propOpt.bioOptimization,'LEMIV_RBExD')) ...
- && strcmp(pln.radiationMode,'carbon')
- vTissueIndex_j = vTissueIndex(ix,:);
- end
-
- for k = 1:stf(i).numOfBixelsPerRay(j) % loop over all bixels per ray
-
- counter = counter + 1;
- bixelsPerBeam = bixelsPerBeam + 1;
-
- % Display progress and update text only 200 times
- if mod(bixelsPerBeam,max(1,round(stf(i).totalNumOfBixels/200))) == 0
- matRad_progress(bixelsPerBeam/max(1,round(stf(i).totalNumOfBixels/200)),...
- floor(stf(i).totalNumOfBixels/max(1,round(stf(i).totalNumOfBixels/200))));
- end
-
- % update waitbar only 100 times if it is not closed
- if mod(counter,round(dij.totalNumOfBixels/100)) == 0 && ishandle(figureWait)
- waitbar(counter/dij.totalNumOfBixels,figureWait);
- end
-
- % remember beam and bixel number
- if ~calcDoseDirect
- dij.beamNum(counter) = i;
- dij.rayNum(counter) = j;
- dij.bixelNum(counter) = k;
- end
-
- % find energy index in base data
- energyIx = find(round2(stf(i).ray(j).energy(k),4) == round2([machine.data.energy],4));
-
- % create offset vector to account for additional offsets modelled in the base data and a potential
- % range shifter. In the following, we only perform dose calculation for voxels having a radiological depth
- % that is within the limits of the base data set (-> machine.data(i).dephts). By this means, we only allow
- % interpolations in matRad_calcParticleDoseBixel() and avoid extrapolations.
- offsetRadDepth = machine.data(energyIx).offset - stf(i).ray(j).rangeShifter(k).eqThickness;
-
- % find depth depended lateral cut off
- if cutOffLevel >= 1
- currIx = radDepths <= machine.data(energyIx).depths(end) + offsetRadDepth;
- elseif cutOffLevel < 1 && cutOffLevel > 0
- % perform rough 2D clipping
- currIx = radDepths <= machine.data(energyIx).depths(end) + offsetRadDepth & ...
- radialDist_sq <= max(machine.data(energyIx).LatCutOff.CutOff.^2);
-
- % peform fine 2D clipping
- if length(machine.data(energyIx).LatCutOff.CutOff) > 1
- currIx(currIx) = matRad_interp1((machine.data(energyIx).LatCutOff.depths + offsetRadDepth)',...
- (machine.data(energyIx).LatCutOff.CutOff.^2)', radDepths(currIx)) >= radialDist_sq(currIx);
- end
- else
- matRad_cfg.dispError('cutoff must be a value between 0 and 1')
- end
-
- % empty bixels may happen during recalculation of error
- % scenarios -> skip to next bixel
- if ~any(currIx)
- continue;
- end
-
- % adjust radDepth according to range shifter
- currRadDepths = radDepths(currIx) + stf(i).ray(j).rangeShifter(k).eqThickness;
-
- % calculate initial focus sigma
- sigmaIni = matRad_interp1(machine.data(energyIx).initFocus.dist (stf(i).ray(j).focusIx(k),:)', ...
- machine.data(energyIx).initFocus.sigma(stf(i).ray(j).focusIx(k),:)',stf(i).ray(j).SSD);
- sigmaIni_sq = sigmaIni^2;
-
- % consider range shifter for protons if applicable
- if stf(i).ray(j).rangeShifter(k).eqThickness > 0 && strcmp(pln.radiationMode,'protons')
-
- % compute!
- sigmaRashi = matRad_calcSigmaRashi(machine.data(energyIx).energy, ...
- stf(i).ray(j).rangeShifter(k), ...
- stf(i).ray(j).SSD);
-
- % add to initial sigma in quadrature
- sigmaIni_sq = sigmaIni_sq + sigmaRashi^2;
-
- end
-
- % calculate particle dose for bixel k on ray j of beam i
- bixelDose = matRad_calcParticleDoseBixel(...
- currRadDepths, ...
- radialDist_sq(currIx), ...
- sigmaIni_sq, ...
- machine.data(energyIx));
-
- % dij sampling is exluded for particles until we investigated the influence of voxel sampling for particles
- %relDoseThreshold = 0.02; % sample dose values beyond the relative dose
- %Type = 'dose';
- %[currIx,bixelDose] = matRad_DijSampling(currIx,bixelDose,radDepths(currIx),radialDist_sq(currIx),Type,relDoseThreshold);
-
- % Save dose for every bixel in cell array
- doseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,1} = sparse(VdoseGrid(ix(currIx)),1,bixelDose,dij.doseGrid.numOfVoxels,1);
-
- if isfield(dij,'mLETDose')
- % calculate particle LET for bixel k on ray j of beam i
- depths = machine.data(energyIx).depths + machine.data(energyIx).offset;
- bixelLET = matRad_interp1(depths,machine.data(energyIx).LET,currRadDepths);
-
- % Save LET for every bixel in cell array
- letDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,1} = sparse(VdoseGrid(ix(currIx)),1,bixelLET.*bixelDose,dij.doseGrid.numOfVoxels,1);
- end
-
- if (isequal(pln.propOpt.bioOptimization,'LEMIV_effect') || isequal(pln.propOpt.bioOptimization,'LEMIV_RBExD')) ...
- && strcmp(pln.radiationMode,'carbon')
- % calculate alpha and beta values for bixel k on ray j of
- [bixelAlpha, bixelBeta] = matRad_calcLQParameter(...
- currRadDepths,...
- vTissueIndex_j(currIx,:),...
- machine.data(energyIx));
-
- alphaDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,1} = sparse(VdoseGrid(ix(currIx)),1,bixelAlpha.*bixelDose,dij.doseGrid.numOfVoxels,1);
- betaDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,1} = sparse(VdoseGrid(ix(currIx)),1,sqrt(bixelBeta).*bixelDose,dij.doseGrid.numOfVoxels,1);
- end
-
- matRad_calcDoseFillDij;
-
- end
-
- end
-
- end
-end
-
-%Close Waitbar
-if ishandle(figureWait)
- delete(figureWait);
-end
-
-
diff --git a/matRad_calcParticleDoseBixel.m b/matRad_calcParticleDoseBixel.m
deleted file mode 100644
index 97e9480c6..000000000
--- a/matRad_calcParticleDoseBixel.m
+++ /dev/null
@@ -1,73 +0,0 @@
-function dose = matRad_calcParticleDoseBixel(radDepths, radialDist_sq, sigmaIni_sq, baseData)
-% matRad visualization of two-dimensional dose distributions
-% on ct including segmentation
-%
-% call
-% dose = matRad_calcParticleDoseBixel(radDepths, radialDist_sq, sigmaIni_sq, baseData)
-%
-% input
-% radDepths: radiological depths
-% radialDist_sq: squared radial distance in BEV from central ray
-% sigmaIni_sq: initial Gaussian sigma^2 of beam at patient surface
-% baseData: base data required for particle dose calculation
-%
-% output
-% dose: particle dose at specified locations as linear vector
-%
-% References
-% [1] http://iopscience.iop.org/0031-9155/41/8/005
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% add potential offset
-depths = baseData.depths + baseData.offset;
-
-% convert from MeV cm^2/g per primary to Gy mm^2 per 1e6 primaries
-conversionFactor = 1.6021766208e-02;
-
-if ~isfield(baseData,'sigma')
-
- % interpolate depth dose, sigmas, and weights
- X = matRad_interp1(depths,[conversionFactor*baseData.Z baseData.sigma1 baseData.weight baseData.sigma2],radDepths);
-
- % set dose for query > tabulated depth dose values to zero
- X(radDepths > max(depths),1) = 0;
-
- % compute lateral sigmas
- sigmaSq_Narr = X(:,2).^2 + sigmaIni_sq;
- sigmaSq_Bro = X(:,4).^2 + sigmaIni_sq;
-
- % calculate lateral profile
- L_Narr = exp( -radialDist_sq ./ (2*sigmaSq_Narr))./(2*pi*sigmaSq_Narr);
- L_Bro = exp( -radialDist_sq ./ (2*sigmaSq_Bro ))./(2*pi*sigmaSq_Bro );
- L = baseData.LatCutOff.CompFac * ((1-X(:,3)).*L_Narr + X(:,3).*L_Bro);
-
- dose = X(:,1).*L;
-else
-
- % interpolate depth dose and sigma
- X = matRad_interp1(depths,[conversionFactor*baseData.Z baseData.sigma],radDepths);
-
- %compute lateral sigma
- sigmaSq = X(:,2).^2 + sigmaIni_sq;
-
- % calculate dose
- dose = baseData.LatCutOff.CompFac * exp( -radialDist_sq ./ (2*sigmaSq)) .* X(:,1) ./(2*pi*sigmaSq);
-
- end
-
-% check if we have valid dose values
-if any(isnan(dose)) || any(dose<0)
- error('Error in particle dose calculation.');
-end
diff --git a/matRad_calcParticleDoseMC.m b/matRad_calcParticleDoseMC.m
deleted file mode 100644
index bd705be3f..000000000
--- a/matRad_calcParticleDoseMC.m
+++ /dev/null
@@ -1,320 +0,0 @@
-function dij = matRad_calcParticleDoseMC(ct,stf,pln,cst,nCasePerBixel,calcDoseDirect)
-% matRad MCsqaure monte carlo photon dose calculation wrapper
-%
-% call
-% dij = matRad_calcParticleDoseMc(ct,stf,pln,cst,calcDoseDirect)
-%
-% input
-% ct: matRad ct struct
-% stf: atRad steering information struct
-% pln: matRad plan meta information struct
-% cst: matRad cst struct
-% nCasePerBixel: number of histories per beamlet
-% calcDoseDirect: binary switch to enable forward dose
-% calcualtion
-% output
-% dij: matRad dij struct
-%
-% References
-%
-% https://aapm.onlinelibrary.wiley.com/doi/abs/10.1118/1.4943377
-% http://www.openmcsquare.org/
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-matRad_cfg = MatRad_Config.instance();
-
-% check if valid machine
-if ~strcmp(pln.radiationMode,'protons') || ~strcmp(pln.machine,'generic_MCsquare')
- matRad_cfg.dispError('Wrong radiation modality and/or machine. For now MCsquare requires machine generic_MCsquare!');
-end
-
-
-if nargin < 5
- % set number of particles simulated per pencil beam
- nCasePerBixel = matRad_cfg.propMC.MCsquare_defaultHistories;
- matRad_cfg.dispInfo('Using default number of Histories per Bixel: %d\n',nCasePerBixel);
-end
-
-if nargin < 6
- calcDoseDirect = false;
-end
-
-if isfield(pln,'propMC') && isfield(pln.propMC,'outputVariance')
- matRad_cfg.dispWarning('Variance scoring for MCsquare not yet supported.');
-end
-
-if ~strcmp(pln.radiationMode,'protons')
- errordlg('MCsquare is only supported for protons');
-end
-
-env = matRad_getEnvironment();
-
-%% check if binaries are available
-%Executables for simulation
-if ispc
- if exist('MCSquare_windows.exe','file') ~= 2
- matRad_cfg.dispError('Could not find MCsquare binary.\n');
- else
- mcSquareBinary = 'MCSquare_windows.exe';
- end
-elseif ismac
- if exist('MCsquare_mac','file') ~= 2
- matRad_cfg.dispError('Could not find MCsquare binary.\n');
- else
- mcSquareBinary = './MCsquare_mac';
- end
- %error('MCsquare binaries not available for mac OS.\n');
-elseif isunix
- if exist('MCsquare_linux','file') ~= 2
- matRad_cfg.dispError('Could not find MCsquare binary.\n');
- else
- mcSquareBinary = './MCsquare_linux';
- end
-end
-
-%Mex interface for import of sparse matrix
-if ~calcDoseDirect && ~matRad_checkMexFileExists('matRad_sparseBeamletsReaderMCsquare')
- matRad_cfg.dispWarning('Compiled sparse reader interface not found. Trying to compile it on the fly!');
- try
- matRad_compileMCsquareSparseReader();
- catch MException
- matRad_cfg.dispError('Could not find/generate mex interface for reading the sparse matrix. \nCause of error:\n%s\n Please compile it yourself.',MException.message);
- end
-end
-
-
-% set and change to MCsquare binary folder
-currFolder = pwd;
-fullfilename = mfilename('fullpath');
-MCsquareFolder = [fullfilename(1:find(fullfilename==filesep,1,'last')) 'MCsquare' filesep 'bin'];
-
-% cd to MCsquare folder (necessary for binary)
-cd(MCsquareFolder);
-
-%Check Materials
-if ~exist([MCsquareFolder filesep 'Materials'],'dir') || ~exist(fullfile(MCsquareFolder,'Materials','list.dat'),'file')
- matRad_cfg.dispInfo('First call of MCsquare: unzipping Materials...');
- unzip('Materials.zip');
- matRad_cfg.dispInfo('Done');
-end
-
-% Since MCsquare 1.1 only allows similar resolution in x&y, we do some
-% extra checks on that before calling calcDoseInit. First, we make sure a
-% dose grid resolution is set in the pln struct
-if ~isfield(pln,'propDoseCalc') ...
- || ~isfield(pln.propDoseCalc,'doseGrid') ...
- || ~isfield(pln.propDoseCalc.doseGrid,'resolution') ...
- || ~all(isfield(pln.propDoseCalc.doseGrid.resolution,{'x','y','z'}))
-
- %Take default values
- pln.propDoseCalc.doseGrid.resolution = matRad_cfg.propDoseCalc.defaultResolution;
-end
-
-% Now we check for different x/y
-if pln.propDoseCalc.doseGrid.resolution.x ~= pln.propDoseCalc.doseGrid.resolution.y
- pln.propDoseCalc.doseGrid.resolution.x = mean([pln.propDoseCalc.doseGrid.resolution.x pln.propDoseCalc.doseGrid.resolution.y]);
- pln.propDoseCalc.doseGrid.resolution.y = pln.propDoseCalc.doseGrid.resolution.x;
- matRad_cfg.dispWarning('Anisotropic resolution in axial plane for dose calculation with MCsquare not possible\nUsing average x = y = %g mm\n',pln.propDoseCalc.doseGrid.resolution.x);
-end
-
-%Now we can run calcDoseInit as usual
-matRad_calcDoseInit;
-
-%Issue a warning when we have more than 1 scenario
-if dij.numOfScenarios ~= 1
- matRad_cfg.dispWarning('MCsquare is only implemented for single scenario use at the moment. Will only use the first Scenario for Monte Carlo calculation!');
-end
-
-% prefill ordering of MCsquare bixels
-dij.MCsquareCalcOrder = NaN*ones(dij.totalNumOfBixels,1);
-
-% We need to adjust the offset used in matRad_calcDoseInit
-mcSquareAddIsoCenterOffset = [dij.doseGrid.resolution.x/2 dij.doseGrid.resolution.y/2 dij.doseGrid.resolution.z/2] ...
- - [dij.ctGrid.resolution.x dij.ctGrid.resolution.y dij.ctGrid.resolution.z];
-mcSquareAddIsoCenterOffset = mcSquareAddIsoCenterOffset - offset;
-
-% for MCsquare we explicitly downsample the ct to the dose grid (might not
-% be necessary in future MCsquare versions with separated grids)
-for s = 1:dij.numOfScenarios
- HUcube{s} = matRad_interp3(dij.ctGrid.x, dij.ctGrid.y', dij.ctGrid.z,ct.cubeHU{s}, ...
- dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'linear');
-end
-
-% Explicitly setting the number of threads for MCsquare, 0 is all available
-nbThreads = 0;
-
-% set relative dose cutoff for storage in dose influence matrix, we use the
-% default value for the lateral cutoff here
-relDoseCutoff = 1 - matRad_cfg.propDoseCalc.defaultLateralCutOff;
-% set absolute calibration factor
-% convert from eV/g/primary to Gy 1e6 primaries
-absCalibrationFactorMC2 = 1.602176e-19 * 1.0e+9;
-
-if isequal(pln.propOpt.bioOptimization,'const_RBExD')
- dij.RBE = 1.1;
- matRad_cfg.dispInfo('matRad: Using a constant RBE of %g\n',dij.RBE);
-end
-
-% MCsquare settings
-MCsquareConfigFile = 'MCsquareConfig.txt';
-
-MCsquareConfig = MatRad_MCsquareConfig;
-
-MCsquareConfig.BDL_Plan_File = 'currBixels.txt';
-MCsquareConfig.CT_File = 'MC2patientCT.mhd';
-MCsquareConfig.Num_Threads = nbThreads;
-MCsquareConfig.RNG_Seed = 1234;
-MCsquareConfig.Num_Primaries = nCasePerBixel;
-
-% turn simulation of individual beamlets
-MCsquareConfig.Beamlet_Mode = ~calcDoseDirect;
-% turn of writing of full dose cube
-MCsquareConfig.Dose_MHD_Output = calcDoseDirect;
-% turn on sparse output
-MCsquareConfig.Dose_Sparse_Output = ~calcDoseDirect;
-% set threshold of sparse matrix generation
-MCsquareConfig.Dose_Sparse_Threshold = relDoseCutoff;
-
-% write patient data
-MCsquareBinCubeResolution = [dij.doseGrid.resolution.x ...
- dij.doseGrid.resolution.y ...
- dij.doseGrid.resolution.z];
-matRad_writeMhd(HUcube{1},MCsquareBinCubeResolution,MCsquareConfig.CT_File);
-
-
-
-counter = 0;
-for i = 1:length(stf)
- stfMCsquare(i).gantryAngle = mod(180-stf(i).gantryAngle,360); %Different MCsquare geometry
- stfMCsquare(i).couchAngle = stf(i).couchAngle;
- stfMCsquare(i).isoCenter = stf(i).isoCenter + mcSquareAddIsoCenterOffset;
- stfMCsquare(i).energies = unique([stf(i).ray.energy]);
-
- % allocate empty target point container
- for j = 1:numel(stfMCsquare(i).energies)
- stfMCsquare(i).energyLayer(j).targetPoints = [];
- stfMCsquare(i).energyLayer(j).numOfPrimaries = [];
- stfMCsquare(i).energyLayer(j).rayNum = [];
- stfMCsquare(i).energyLayer(j).bixelNum = [];
- end
-
- for j = 1:stf(i).numOfRays
- for k = 1:stf(i).numOfBixelsPerRay(j)
- counter = counter + 1;
- dij.beamNum(counter) = i;
- dij.rayNum(counter) = j;
- dij.bixelNum(counter) = k;
- end
-
- for k = 1:numel(stfMCsquare(i).energies)
- if any(stf(i).ray(j).energy == stfMCsquare(i).energies(k))
- stfMCsquare(i).energyLayer(k).rayNum = [stfMCsquare(i).energyLayer(k).rayNum j];
- stfMCsquare(i).energyLayer(k).bixelNum = [stfMCsquare(i).energyLayer(k).bixelNum ...
- find(stf(i).ray(j).energy == stfMCsquare(i).energies(k))];
- stfMCsquare(i).energyLayer(k).targetPoints = [stfMCsquare(i).energyLayer(k).targetPoints; ...
- -stf(i).ray(j).rayPos_bev(1) stf(i).ray(j).rayPos_bev(3)];
- if calcDoseDirect
- stfMCsquare(i).energyLayer(k).numOfPrimaries = [stfMCsquare(i).energyLayer(k).numOfPrimaries ...
- round(stf(i).ray(j).weight(stf(i).ray(j).energy == stfMCsquare(i).energies(k))*MCsquareConfig.Num_Primaries)];
- else
- stfMCsquare(i).energyLayer(k).numOfPrimaries = [stfMCsquare(i).energyLayer(k).numOfPrimaries ...
- MCsquareConfig.Num_Primaries];
- end
- end
- end
-
- end
-
-end
-
-% remember order
-counterMCsquare = 0;
-MCsquareOrder = NaN * ones(dij.totalNumOfBixels,1);
-for i = 1:length(stf)
- for j = 1:numel(stfMCsquare(i).energies)
- for k = 1:numel(stfMCsquare(i).energyLayer(j).numOfPrimaries)
- counterMCsquare = counterMCsquare + 1;
- ix = find(i == dij.beamNum & ...
- stfMCsquare(i).energyLayer(j).rayNum(k) == dij.rayNum & ...
- stfMCsquare(i).energyLayer(j).bixelNum(k) == dij.bixelNum);
-
- MCsquareOrder(ix) = counterMCsquare;
- end
- end
-end
-
-if any(isnan(MCsquareOrder))
- matRad_cfg.dispError('Invalid ordering of Beamlets for MCsquare computation!');
-end
-
-%% MC computation and dij filling
-matRad_writeMCsquareinputAllFiles(MCsquareConfigFile,MCsquareConfig,stfMCsquare);
-
-% run MCsquare
-[status,cmdout] = system([mcSquareBinary ' ' MCsquareConfigFile],'-echo');
-
-mask = false(dij.doseGrid.numOfVoxels,1);
-mask(VdoseGrid) = true;
-
-% read sparse matrix
-if ~calcDoseDirect
- dij.physicalDose{1} = absCalibrationFactorMC2 * matRad_sparseBeamletsReaderMCsquare ( ...
- [MCsquareConfig.Output_Directory filesep 'Sparse_Dose.bin'], ...
- dij.doseGrid.dimensions, ...
- dij.totalNumOfBixels, ...
- mask);
-else
- cube = matRad_readMhd(MCsquareConfig.Output_Directory,'Dose.mhd');
- dij.physicalDose{1} = sparse(VdoseGrid,ones(numel(VdoseGrid),1), ...
- absCalibrationFactorMC2 * cube(VdoseGrid), ...
- dij.doseGrid.numOfVoxels,1);
-end
-
-% reorder influence matrix to comply with matRad default ordering
-if MCsquareConfig.Beamlet_Mode
- dij.physicalDose{1} = dij.physicalDose{1}(:,MCsquareOrder);
-end
-
-matRad_cfg.dispInfo('matRad: done!\n');
-
-try
- % wait 0.1s for closing all waitbars
- allWaitBarFigures = findall(0,'type','figure','tag','TMWWaitbar');
- delete(allWaitBarFigures);
- pause(0.1);
-catch
-end
-
-%% clear all data
-delete([MCsquareConfig.CT_File(1:end-4) '.*']);
-delete('currBixels.txt');
-delete('MCsquareConfig.txt');
-
-%For Octave temporarily disable confirmation for recursive rmdir
-if strcmp(env,'OCTAVE')
- rmdirConfirmState = confirm_recursive_rmdir(0);
-end
-rmdir(MCsquareConfig.Output_Directory,'s');
-
-%Reset to old confirmatoin state
-if strcmp(env,'OCTAVE')
- confirm_recursive_rmdir(rmdirConfirmState);
-end
-
-% cd back
-cd(currFolder);
-
-end
diff --git a/matRad_calcPhotonDose.m b/matRad_calcPhotonDose.m
deleted file mode 100644
index f6468d269..000000000
--- a/matRad_calcPhotonDose.m
+++ /dev/null
@@ -1,315 +0,0 @@
-function dij = matRad_calcPhotonDose(ct,stf,pln,cst,calcDoseDirect)
-% matRad photon dose calculation wrapper
-%
-% call
-% dij = matRad_calcPhotonDose(ct,stf,pln,cst,calcDoseDirect)
-%
-% input
-% ct: ct cube
-% stf: matRad steering information struct
-% pln: matRad plan meta information struct
-% cst: matRad cst struct
-% calcDoseDirect: boolian switch to bypass dose influence matrix
-% computation and directly calculate dose; only makes
-% sense in combination with matRad_calcDoseDirect.m
-%
-% output
-% dij: matRad dij struct
-%
-% References
-% [1] http://www.ncbi.nlm.nih.gov/pubmed/8497215
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-matRad_cfg = MatRad_Config.instance();
-
-% initialize
-matRad_calcDoseInit;
-
-[env, ~] = matRad_getEnvironment();
-
-switch env
- case 'MATLAB'
- rng('default');
- case 'OCTAVE'
- rand('seed',0)
-end
-
-% issue warning if biological optimization not possible
-if sum(strcmp(pln.propOpt.bioOptimization,{'effect','RBExD'}))>0
- warndlg('Effect based and RBE optimization not available for photons - physical optimization is carried out instead.');
- pln.bioOptimization = 'none';
-end
-
-% initialize waitbar
-figureWait = waitbar(0,'calculate dose influence matrix for photons...');
-% show busy state
-set(figureWait,'pointer','watch');
-
-% set lateral cutoff value
-if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'geometricCutOff')
- pln.propDoseCalc.geometricCutOff = matRad_cfg.propDoseCalc.defaultGeometricCutOff; % [mm]
-end
-
-lateralCutoff = pln.propDoseCalc.geometricCutOff;
-
-if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'kernelCutOff')
- pln.propDoseCalc.kernelCutOff = matRad_cfg.propDoseCalc.defaultKernelCutOff; % [mm]
-end
-
-% set kernel cutoff value (determines how much of the kernel is used. This
-% value is separated from lateralCutOff to obtain accurate large open fields)
-kernelCutoff = pln.propDoseCalc.kernelCutOff;
-
-if kernelCutoff < lateralCutoff
- matRad_cfg.dispWarning('Kernel Cut-Off ''%f mm'' cannot be smaller than geometric lateral cutoff ''%f mm''. Using ''%f mm''!',kernelCutoff,lateralCutoff,lateralCutoff);
- kernelCutoff = lateralCutoff;
-end
-
-% toggle custom primary fluence on/off. if 0 we assume a homogeneous
-% primary fluence, if 1 we use measured radially symmetric data
-if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'useCustomPrimaryPhotonFluence')
- useCustomPrimFluenceBool = matRad_cfg.propDoseCalc.defaultUseCustomPrimaryPhotonFluence;
-else
- useCustomPrimFluenceBool = pln.propDoseCalc.useCustomPrimaryPhotonFluence;
-end
-
-
-% 0 if field calc is bixel based, 1 if dose calc is field based
-isFieldBasedDoseCalc = strcmp(num2str(pln.propStf.bixelWidth),'field');
-
-%% kernel convolution
-% set up convolution grid
-if isFieldBasedDoseCalc
- % get data from DICOM import
- intConvResolution = pln.propStf.collimation.convResolution;
- fieldWidth = pln.propStf.collimation.fieldWidth;
-else
- intConvResolution = .5; % [mm]
- fieldWidth = pln.propStf.bixelWidth;
-end
-
-% calculate field size and distances
-fieldLimit = ceil(fieldWidth/(2*intConvResolution));
-[F_X,F_Z] = meshgrid(-fieldLimit*intConvResolution: ...
- intConvResolution: ...
- (fieldLimit-1)*intConvResolution);
-
-% gaussian filter to model penumbra from (measured) machine output / see
-% diploma thesis siggel 4.1.2 -> https://github.com/e0404/matRad/wiki/Dose-influence-matrix-calculation
-if isfield(machine.data,'penumbraFWHMatIso')
- penumbraFWHM = machine.data.penumbraFWHMatIso;
-else
- penumbraFWHM = 5;
- matRad_cfg.dispWarning('photon machine file does not contain measured penumbra width in machine.data.penumbraFWHMatIso. Assuming 5 mm.');
-end
-
-sigmaGauss = penumbraFWHM / sqrt(8*log(2)); % [mm]
-% use 5 times sigma as the limits for the gaussian convolution
-gaussLimit = ceil(5*sigmaGauss/intConvResolution);
-[gaussFilterX,gaussFilterZ] = meshgrid(-gaussLimit*intConvResolution: ...
- intConvResolution: ...
- (gaussLimit-1)*intConvResolution);
-gaussFilter = 1/(2*pi*sigmaGauss^2/intConvResolution^2) * exp(-(gaussFilterX.^2+gaussFilterZ.^2)/(2*sigmaGauss^2) );
-gaussConvSize = 2*(fieldLimit + gaussLimit);
-
-if ~isFieldBasedDoseCalc
- % Create fluence matrix
- F = ones(floor(fieldWidth/intConvResolution));
-
- if ~useCustomPrimFluenceBool
- % gaussian convolution of field to model penumbra
- F = real(ifft2(fft2(F,gaussConvSize,gaussConvSize).*fft2(gaussFilter,gaussConvSize,gaussConvSize)));
- end
-end
-
-% get kernel size and distances
-if kernelCutoff > machine.data.kernelPos(end)
- kernelCutoff = machine.data.kernelPos(end);
-end
-
-kernelLimit = ceil(kernelCutoff/intConvResolution);
-[kernelX, kernelZ] = meshgrid(-kernelLimit*intConvResolution: ...
- intConvResolution: ...
- (kernelLimit-1)*intConvResolution);
-
-% precalculate convolved kernel size and distances
-kernelConvLimit = fieldLimit + gaussLimit + kernelLimit;
-[convMx_X, convMx_Z] = meshgrid(-kernelConvLimit*intConvResolution: ...
- intConvResolution: ...
- (kernelConvLimit-1)*intConvResolution);
-% calculate also the total size and distance as we need this during convolution extensively
-kernelConvSize = 2*kernelConvLimit;
-
-% define an effective lateral cutoff where dose will be calculated. note
-% that storage within the influence matrix may be subject to sampling
-effectiveLateralCutoff = lateralCutoff + fieldWidth/sqrt(2);
-
-counter = 0;
-matRad_cfg.dispInfo('matRad: Photon dose calculation...\n');
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-for i = 1:dij.numOfBeams % loop over all beams
-
- matRad_calcDoseInitBeam;
-
- % get index of central ray or closest to the central ray
- [~,center] = min(sum(reshape([stf(i).ray.rayPos_bev],3,[]).^2));
-
- % get correct kernel for given SSD at central ray (nearest neighbor approximation)
- [~,currSSDIx] = min(abs([machine.data.kernel.SSD]-stf(i).ray(center).SSD));
- % Display console message.
- matRad_cfg.dispInfo('\tSSD = %g mm ...\n',machine.data.kernel(currSSDIx).SSD);
-
- kernelPos = machine.data.kernelPos;
- kernel1 = machine.data.kernel(currSSDIx).kernel1;
- kernel2 = machine.data.kernel(currSSDIx).kernel2;
- kernel3 = machine.data.kernel(currSSDIx).kernel3;
-
- % Evaluate kernels for all distances, interpolate between values
- kernel1Mx = interp1(kernelPos,kernel1,sqrt(kernelX.^2+kernelZ.^2),'linear',0);
- kernel2Mx = interp1(kernelPos,kernel2,sqrt(kernelX.^2+kernelZ.^2),'linear',0);
- kernel3Mx = interp1(kernelPos,kernel3,sqrt(kernelX.^2+kernelZ.^2),'linear',0);
-
- % convolution here if no custom primary fluence and no field based dose calc
- if ~useCustomPrimFluenceBool && ~isFieldBasedDoseCalc
-
- % Display console message.
- matRad_cfg.dispInfo('\tUniform primary photon fluence -> pre-compute kernel convolution...\n');
-
- % 2D convolution of Fluence and Kernels in fourier domain
- convMx1 = real(ifft2(fft2(F,kernelConvSize,kernelConvSize).* fft2(kernel1Mx,kernelConvSize,kernelConvSize)));
- convMx2 = real(ifft2(fft2(F,kernelConvSize,kernelConvSize).* fft2(kernel2Mx,kernelConvSize,kernelConvSize)));
- convMx3 = real(ifft2(fft2(F,kernelConvSize,kernelConvSize).* fft2(kernel3Mx,kernelConvSize,kernelConvSize)));
-
- % Creates an interpolant for kernes from vectors position X and Z
- if strcmp(env,'MATLAB')
- Interp_kernel1 = griddedInterpolant(convMx_X',convMx_Z',convMx1','linear','none');
- Interp_kernel2 = griddedInterpolant(convMx_X',convMx_Z',convMx2','linear','none');
- Interp_kernel3 = griddedInterpolant(convMx_X',convMx_Z',convMx3','linear','none');
- else
- Interp_kernel1 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx1,x,y,'linear',NaN);
- Interp_kernel2 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx2,x,y,'linear',NaN);
- Interp_kernel3 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx3,x,y,'linear',NaN);
- end
- end
-
- for j = 1:stf(i).numOfRays % loop over all rays / for photons we only have one bixel per ray!
-
- counter = counter + 1;
- bixelsPerBeam = bixelsPerBeam + 1;
-
- % convolution here if custom primary fluence OR field based dose calc
- if useCustomPrimFluenceBool || isFieldBasedDoseCalc
-
- % overwrite field opening if necessary
- if isFieldBasedDoseCalc
- F = stf(i).ray(j).shape;
- end
-
- % prepare primary fluence array
- primaryFluence = machine.data.primaryFluence;
- r = sqrt( (F_X-stf(i).ray(j).rayPos(1)).^2 + (F_Z-stf(i).ray(j).rayPos(3)).^2 );
- Psi = interp1(primaryFluence(:,1)',primaryFluence(:,2)',r,'linear',0);
-
- % apply the primary fluence to the field
- Fx = F .* Psi;
-
- % convolve with the gaussian
- Fx = real( ifft2(fft2(Fx,gaussConvSize,gaussConvSize).* fft2(gaussFilter,gaussConvSize,gaussConvSize)) );
-
- % 2D convolution of Fluence and Kernels in fourier domain
- convMx1 = real( ifft2(fft2(Fx,kernelConvSize,kernelConvSize).* fft2(kernel1Mx,kernelConvSize,kernelConvSize)) );
- convMx2 = real( ifft2(fft2(Fx,kernelConvSize,kernelConvSize).* fft2(kernel2Mx,kernelConvSize,kernelConvSize)) );
- convMx3 = real( ifft2(fft2(Fx,kernelConvSize,kernelConvSize).* fft2(kernel3Mx,kernelConvSize,kernelConvSize)) );
-
- % Creates an interpolant for kernes from vectors position X and Z
- if strcmp(env,'MATLAB')
- Interp_kernel1 = griddedInterpolant(convMx_X',convMx_Z',convMx1','linear','none');
- Interp_kernel2 = griddedInterpolant(convMx_X',convMx_Z',convMx2','linear','none');
- Interp_kernel3 = griddedInterpolant(convMx_X',convMx_Z',convMx3','linear','none');
- else
- Interp_kernel1 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx1,x,y,'linear',NaN);
- Interp_kernel2 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx2,x,y,'linear',NaN);
- Interp_kernel3 = @(x,y)interp2(convMx_X(1,:),convMx_Z(:,1),convMx3,x,y,'linear',NaN);
- end
-
- end
-
- % Display progress and update text only 200 times
- if mod(bixelsPerBeam,max(1,round(stf(i).totalNumOfBixels/200))) == 0
- matRad_progress(bixelsPerBeam/max(1,round(stf(i).totalNumOfBixels/200)),...
- floor(stf(i).totalNumOfBixels/max(1,round(stf(i).totalNumOfBixels/200))));
- end
- % update waitbar only 100 times
- if mod(counter,round(dij.totalNumOfBixels/100)) == 0 && ishandle(figureWait)
- waitbar(counter/dij.totalNumOfBixels);
- end
-
- % remember beam and bixel number
- if ~calcDoseDirect
- dij.beamNum(counter) = i;
- dij.rayNum(counter) = j;
- dij.bixelNum(counter) = 1;
- else
- k = 1;
- end
-
- % Ray tracing for beam i and bixel j
- [ix,rad_distancesSq,isoLatDistsX,isoLatDistsZ] = matRad_calcGeoDists(rot_coordsVdoseGrid, ...
- stf(i).sourcePoint_bev, ...
- stf(i).ray(j).targetPoint_bev, ...
- machine.meta.SAD, ...
- find(~isnan(radDepthVdoseGrid{1})), ...
- effectiveLateralCutoff);
-
- % empty bixels may happen during recalculation of error
- % scenarios -> skip to next bixel
- if isempty(ix)
- continue;
- end
-
-
- % calculate photon dose for beam i and bixel j
- bixelDose = matRad_calcPhotonDoseBixel(machine.meta.SAD,machine.data.m,...
- machine.data.betas, ...
- Interp_kernel1,...
- Interp_kernel2,...
- Interp_kernel3,...
- radDepthVdoseGrid{1}(ix),...
- geoDistVdoseGrid{1}(ix),...
- isoLatDistsX,...
- isoLatDistsZ);
-
- % sample dose only for bixel based dose calculation
- if ~isFieldBasedDoseCalc
- r0 = 20 + stf(i).bixelWidth; % [mm] sample beyond the inner core
- Type = 'radius';
- [ix,bixelDose] = matRad_DijSampling(ix,bixelDose,radDepthVdoseGrid{1}(ix),rad_distancesSq,Type,r0);
- end
-
- % Save dose for every bixel in cell array
- doseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,1} = sparse(VdoseGrid(ix),1,bixelDose,dij.doseGrid.numOfVoxels,1);
-
- matRad_calcDoseFillDij;
-
- end
-end
-
-%Close Waitbar
-if ishandle(figureWait)
- delete(figureWait);
-end
-
diff --git a/matRad_calcPhotonDoseBixel.m b/matRad_calcPhotonDoseBixel.m
deleted file mode 100644
index 24c5924c4..000000000
--- a/matRad_calcPhotonDoseBixel.m
+++ /dev/null
@@ -1,65 +0,0 @@
-function dose = matRad_calcPhotonDoseBixel(SAD,m,betas,Interp_kernel1,...
- Interp_kernel2,Interp_kernel3,radDepths,geoDists,...
- isoLatDistsX,isoLatDistsZ)
-% matRad photon dose calculation for an individual bixel
-%
-% call
-% dose = matRad_calcPhotonDoseBixel(SAD,m,betas,Interp_kernel1,...
-% Interp_kernel2,Interp_kernel3,radDepths,geoDists,...
-% isoLatDistsX,isoLatDistsZ)
-%
-% input
-% SAD: source to axis distance
-% m: absorption in water (part of the dose calc base
-% data)
-% betas: beta parameters for the parameterization of the
-% three depth dose components
-% Interp_kernel1/2/3: kernels for dose calculation
-% radDepths: radiological depths
-% geoDists: geometrical distance from virtual photon source
-% isoLatDistsX: lateral distance in X direction in BEV from central
-% ray at iso center plane
-% isoLatDistsZ: lateral distance in Z direction in BEV from central
-% ray at iso center plane
-%
-% output
-% dose: photon dose at specified locations as linear vector
-%
-% References
-% [1] http://www.ncbi.nlm.nih.gov/pubmed/8497215
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% Define function_Di
-func_Di = @(beta,x) beta/(beta-m) * (exp(-m*x) - exp(-beta*x));
-
-% Calulate lateral distances using grid interpolation.
-lat1 = Interp_kernel1(isoLatDistsX,isoLatDistsZ);
-lat2 = Interp_kernel2(isoLatDistsX,isoLatDistsZ);
-lat3 = Interp_kernel3(isoLatDistsX,isoLatDistsZ);
-
-% now add everything together (eq 19 w/o inv sq corr -> see below)
-dose = lat1 .* func_Di(betas(1),radDepths) + ...
- lat2 .* func_Di(betas(2),radDepths) + ...
- lat3 .* func_Di(betas(3),radDepths);
-
-% inverse square correction
-dose = dose .* (SAD./geoDists(:)).^2;
-
-% check if we have valid dose values and adjust numerical instabilities
-% from fft convolution
-dose(dose < 0 & dose > -1e-14) = 0;
-if any(isnan(dose)) || any(dose<0)
- error('Error in photon dose calculation.');
-end
diff --git a/matRad_calcPhotonDoseMC.m b/matRad_calcPhotonDoseMC.m
deleted file mode 100644
index 1fbc0595c..000000000
--- a/matRad_calcPhotonDoseMC.m
+++ /dev/null
@@ -1,342 +0,0 @@
-function dij = matRad_calcPhotonDoseMC(ct,stf,pln,cst,nCasePerBixel,visBool)
-% matRad ompMC monte carlo photon dose calculation wrapper
-%
-% call
-% dij = matRad_calcPhotonDoseMc(ct,stf,pln,cst,visBool)
-%
-% input
-% ct: matRad ct struct
-% stf: matRad steering information struct
-% pln: matRad plan meta information struct
-% cst: matRad cst struct
-% visBool: binary switch to enable visualization
-% output
-% dij: matRad dij struct
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2018 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-matRad_cfg = MatRad_Config.instance();
-
-tic
-
-% disable visualiazation by default
-if nargin < 6
- visBool = false;
-end
-
-if nargin < 5
- nCasePerBixel = matRad_cfg.propMC.ompMC_defaultHistories;
- matRad_cfg.dispInfo('Using default number of Histories per Bixel: %d\n',nCasePerBixel);
-end
-
-fileFolder = fileparts(mfilename('fullpath'));
-
-if ~matRad_checkMexFileExists('omc_matrad') %exist('matRad_ompInterface','file') ~= 3
- matRad_cfg.dispWarning('Compiled mex interface not found. Trying to compile the ompMC interface on the fly!');
- try
- matRad_compileOmpMCInterface();
- catch MException
- matRad_cfg.dispError('Could not find/generate mex interface for MC dose calculation.\nCause of error:\n%s\n Please compile it yourself (preferably with OpenMP support).',MException.message);
- end
-end
-
-matRad_calcDoseInit;
-
-% gaussian filter to model penumbra from (measured) machine output / see diploma thesis siggel 4.1.2
-if isfield(machine.data,'penumbraFWHMatIso')
- penumbraFWHM = machine.data.penumbraFWHMatIso;
-else
- penumbraFWHM = 5;
- matRad_cfg.dispWarning('photon machine file does not contain measured penumbra width in machine.data.penumbraFWHMatIso. Assuming 5 mm.');
-end
-
-sourceFWHM = penumbraFWHM * machine.meta.SCD/(machine.meta.SAD - machine.meta.SCD);
-sigmaGauss = sourceFWHM / sqrt(8*log(2)); % [mm]
-
-% set up arrays for book keeping
-dij.bixelNum = NaN*ones(dij.totalNumOfBixels,1);
-dij.rayNum = NaN*ones(dij.totalNumOfBixels,1);
-dij.beamNum = NaN*ones(dij.totalNumOfBixels,1);
-
-dij.numHistoriesPerBeamlet = nCasePerBixel;
-
-omcFolder = [matRad_cfg.matRadRoot filesep 'ompMC'];
-%omcFolder = [matRad_cfg.matRadRoot filesep 'submodules' filesep 'ompMC'];
-
-%% Setup OmpMC options / parameters
-
-%display options
-ompMCoptions.verbose = matRad_cfg.logLevel - 1;
-
-% start MC control
-ompMCoptions.nHistories = nCasePerBixel;
-ompMCoptions.nSplit = 20;
-ompMCoptions.nBatches = 10;
-ompMCoptions.randomSeeds = [97 33];
-
-%start source definition
-ompMCoptions.spectrumFile = [omcFolder filesep 'spectra' filesep 'mohan6.spectrum'];
-ompMCoptions.monoEnergy = 0.1;
-ompMCoptions.charge = 0;
-ompMCoptions.sourceGeometry = 'gaussian';
-ompMCoptions.sourceGaussianWidth = 0.1*sigmaGauss;
-
-% start MC transport
-ompMCoptions.dataFolder = [omcFolder filesep 'data' filesep];
-ompMCoptions.pegsFile = [omcFolder filesep 'pegs4' filesep '700icru.pegs4dat'];
-ompMCoptions.pgs4formFile = [omcFolder filesep 'pegs4' filesep 'pgs4form.dat'];
-
-ompMCoptions.global_ecut = 0.7;
-ompMCoptions.global_pcut = 0.010;
-
-% Relative Threshold for dose
-ompMCoptions.relDoseThreshold = 1 - matRad_cfg.propDoseCalc.defaultLateralCutOff;
-
-% Output folders
-ompMCoptions.outputFolder = [omcFolder filesep 'output' filesep];
-
-% Create Material Density Cube
-material = cell(4,5);
-material{1,1} = 'AIR700ICRU';
-material{1,2} = -1024;
-material{1,3} = -974;
-material{1,4} = 0.001;
-material{1,5} = 0.044;
-material{2,1} = 'LUNG700ICRU';
-material{2,2} = -974;
-material{2,3} = -724;
-material{2,4} = 0.044;
-material{2,5} = 0.302;
-material{3,1} = 'ICRUTISSUE700ICRU';
-material{3,2} = -724;
-material{3,3} = 101;
-material{3,4} = 0.302;
-material{3,5} = 1.101;
-material{4,1} = 'ICRPBONE700ICRU';
-material{4,2} = 101;
-material{4,3} = 1976;
-material{4,4} = 1.101;
-material{4,5} = 2.088;
-
-% conversion from HU to densities & materials
-for s = 1:dij.numOfScenarios
-
- HUcube{s} = matRad_interp3(dij.ctGrid.x,dij.ctGrid.y',dij.ctGrid.z,ct.cubeHU{s}, ...
- dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'nearest');
-
- % projecting out of bounds HU values where necessary
- if max(HUcube{s}(:)) > material{end,3}
- matRad_cfg.dispWarning('Projecting out of range HU values');
- HUcube{s}(HUcube{s}(:) > material{end,3}) = material{end,3};
- end
- if min(HUcube{s}(:)) < material{1,2}
- matRad_cfg.dispWarning('Projecting out of range HU values');
- HUcube{s}(HUcube{s}(:) < material{1,2}) = material{1,2};
- end
-
- % find material index
- cubeMatIx{s} = NaN*ones(dij.doseGrid.dimensions,'int32');
- for i = size(material,1):-1:1
- cubeMatIx{s}(HUcube{s} <= material{i,3}) = i;
- end
-
- % create an artificial HU lookup table
- hlut = [];
- for i = 1:size(material,1)
- hlut = [hlut;material{i,2} material{i,4};material{i,3}-1e-10 material{i,5}]; % add eps for interpolation
- end
-
- cubeRho{s} = interp1(hlut(:,1),hlut(:,2),HUcube{s});
-
-end
-
-ompMCgeo.material = material;
-
-scale = 10; % to convert to cm
-
-ompMCgeo.xBounds = (dij.doseGrid.resolution.y * (0.5 + [0:dij.doseGrid.dimensions(1)])) ./ scale;
-ompMCgeo.yBounds = (dij.doseGrid.resolution.x * (0.5 + [0:dij.doseGrid.dimensions(2)])) ./ scale;
-ompMCgeo.zBounds = (dij.doseGrid.resolution.z * (0.5 + [0:dij.doseGrid.dimensions(3)])) ./ scale;
-
-%% debug visualization
-if visBool
-
- figure
- hold on
-
- axis equal
-
- % ct box
- ctCorner1 = [ompMCgeo.xBounds(1) ompMCgeo.yBounds(1) ompMCgeo.zBounds(1)];
- ctCorner2 = [ompMCgeo.xBounds(end) ompMCgeo.yBounds(end) ompMCgeo.zBounds(end)];
- plot3([ctCorner1(1) ctCorner2(1)],[ctCorner1(2) ctCorner1(2)],[ctCorner1(3) ctCorner1(3)],'k' )
- plot3([ctCorner1(1) ctCorner2(1)],[ctCorner2(2) ctCorner2(2)],[ctCorner1(3) ctCorner1(3)],'k' )
- plot3([ctCorner1(1) ctCorner1(1)],[ctCorner1(2) ctCorner2(2)],[ctCorner1(3) ctCorner1(3)],'k' )
- plot3([ctCorner2(1) ctCorner2(1)],[ctCorner1(2) ctCorner2(2)],[ctCorner1(3) ctCorner1(3)],'k' )
- plot3([ctCorner1(1) ctCorner2(1)],[ctCorner1(2) ctCorner1(2)],[ctCorner2(3) ctCorner2(3)],'k' )
- plot3([ctCorner1(1) ctCorner2(1)],[ctCorner2(2) ctCorner2(2)],[ctCorner2(3) ctCorner2(3)],'k' )
- plot3([ctCorner1(1) ctCorner1(1)],[ctCorner1(2) ctCorner2(2)],[ctCorner2(3) ctCorner2(3)],'k' )
- plot3([ctCorner2(1) ctCorner2(1)],[ctCorner1(2) ctCorner2(2)],[ctCorner2(3) ctCorner2(3)],'k' )
- plot3([ctCorner1(1) ctCorner1(1)],[ctCorner1(2) ctCorner1(2)],[ctCorner1(3) ctCorner2(3)],'k' )
- plot3([ctCorner2(1) ctCorner2(1)],[ctCorner1(2) ctCorner1(2)],[ctCorner1(3) ctCorner2(3)],'k' )
- plot3([ctCorner1(1) ctCorner1(1)],[ctCorner2(2) ctCorner2(2)],[ctCorner1(3) ctCorner2(3)],'k' )
- plot3([ctCorner2(1) ctCorner2(1)],[ctCorner2(2) ctCorner2(2)],[ctCorner1(3) ctCorner2(3)],'k' )
-
- xlabel('x [cm]')
- ylabel('y [cm]')
- zlabel('z [cm]')
-
- rotate3d on
-
-end
-
-%% Create beamlet source
-useCornersSCD = true; %false -> use ISO corners
-
-numOfBixels = [stf(:).numOfRays];
-beamSource = zeros(dij.numOfBeams, 3);
-
-bixelCorner = zeros(dij.totalNumOfBixels,3);
-bixelSide1 = zeros(dij.totalNumOfBixels,3);
-bixelSide2 = zeros(dij.totalNumOfBixels,3);
-
-counter = 0;
-
-for i = 1:dij.numOfBeams % loop over all beams
-
- % define beam source in physical coordinate system in cm
- beamSource(i,:) = (stf(i).sourcePoint + stf(i).isoCenter)/10;
-
- for j = 1:stf(i).numOfRays % loop over all rays / for photons we only have one bixel per ray!
-
- counter = counter + 1;
-
- dij.beamNum(counter) = i;
- dij.rayNum(counter) = j;
- dij.bixelNum(counter) = j;
-
- if useCornersSCD
- beamletCorners = stf(i).ray(j).rayCorners_SCD;
- else
- beamletCorners = stf(i).ray(j).beamletCornersAtIso;
- end
-
- % get bixel corner and delimiting vectors.
- % a) change coordinate system (Isocenter cs-> physical cs) and units mm -> cm
- currCorner = (beamletCorners(1,:) + stf(i).isoCenter) ./ scale;
- bixelCorner(counter,:) = currCorner;
- bixelSide1(counter,:) = (beamletCorners(2,:) + stf(i).isoCenter) ./ scale - currCorner;
- bixelSide2(counter,:) = (beamletCorners(4,:) + stf(i).isoCenter) ./ scale - currCorner;
-
- if visBool
- for k = 1:4
- currCornerVis = (beamletCorners(k,:) + stf(i).isoCenter)/10;
- % rays connecting source and ray corner
- plot3([beamSource(i,1) currCornerVis(1)],[beamSource(i,2) currCornerVis(2)],[beamSource(i,3) currCornerVis(3)],'b')
- % connection between corners
- lRayCorner = (beamletCorners(mod(k,4) + 1,:) + stf(i).isoCenter)/10;
- plot3([lRayCorner(1) currCornerVis(1)],[lRayCorner(2) currCornerVis(2)],[lRayCorner(3) currCornerVis(3)],'r')
- end
- end
-
- end
-
-end
-
-ompMCsource.nBeams = dij.numOfBeams;
-ompMCsource.iBeam = dij.beamNum(:);
-
-% Switch x and y directions to match ompMC cs.
-ompMCsource.xSource = beamSource(:,2);
-ompMCsource.ySource = beamSource(:,1);
-ompMCsource.zSource = beamSource(:,3);
-
-ompMCsource.nBixels = sum(numOfBixels(:));
-ompMCsource.xCorner = bixelCorner(:,2);
-ompMCsource.yCorner = bixelCorner(:,1);
-ompMCsource.zCorner = bixelCorner(:,3);
-
-ompMCsource.xSide1 = bixelSide1(:,2);
-ompMCsource.ySide1 = bixelSide1(:,1);
-ompMCsource.zSide1 = bixelSide1(:,3);
-
-ompMCsource.xSide2 = bixelSide2(:,2);
-ompMCsource.ySide2 = bixelSide2(:,1);
-ompMCsource.zSide2 = bixelSide2(:,3);
-
-if visBool
- plot3(ompMCsource.ySource,ompMCsource.xSource,ompMCsource.zSource,'rx')
-end
-
-%% Call the OmpMC interface
-
-%ompMC for matRad returns dose/history * nHistories.
-% This factor calibrates to 1 Gy in a %(5x5)cm^2 open field (1 bixel) at
-% 5cm depth for SSD = 900 which corresponds to the calibration for the
-% analytical base data.
-absCalibrationFactor = 3.49056 * 1e12; %Approximate!
-
-%Now we have to calibrate to the the beamlet width.
-absCalibrationFactor = absCalibrationFactor * (pln.propStf.bixelWidth/50)^2;
-
-matRad_cfg.dispInfo('matRad: OmpMC photon dose calculation... \n');
-
-outputVariance = matRad_cfg.propMC.ompMC_defaultOutputVariance;
-
-if isfield(pln,'propMC') && isfield(pln.propMC,'outputVariance')
- outputVariance = pln.propMC.outputVariance;
-end
-
-
-%run over all scenarios
-for s = 1:dij.numOfScenarios
- ompMCgeo.isoCenter = [stf(:).isoCenter];
-
- %Run the Monte Carlo simulation and catch possible mex-interface
- %issues
- try
- %If we ask for variance, a field in the dij will be filled
- if outputVariance
- [dij.physicalDose{s},dij.physicalDose_MCvar{s}] = omc_matrad(cubeRho{s},cubeMatIx{s},ompMCgeo,ompMCsource,ompMCoptions);
- else
- [dij.physicalDose{s}] = omc_matrad(cubeRho{s},cubeMatIx{s},ompMCgeo,ompMCsource,ompMCoptions);
- end
- catch ME
- errorString = [ME.message '\nThis error was thrown by the MEX-interface of ompMC.\nMex interfaces can raise compatability issues which may be resolved by compiling them by hand directly on your particular system.'];
- matRad_cfg.dispError(errorString);
- end
-
- %Calibrate the dose with above factor
- dij.physicalDose{s} = dij.physicalDose{s} * absCalibrationFactor;
- if isfield(dij,'physicalDose_MCvar')
- dij.physicalDose_MCvar{s} = dij.physicalDose_MCvar{s} * absCalibrationFactor^2;
- end
-end
-
-matRad_cfg.dispInfo('matRad: MC photon dose calculation done!\n');
-matRad_cfg.dispInfo(evalc('toc'));
-
-try
- % wait 0.1s for closing all waitbars
- allWaitBarFigures = findall(0,'type','figure','tag','TMWWaitbar');
- delete(allWaitBarFigures);
- pause(0.1);
-catch
-end
-
-end
diff --git a/matRad_calcSigmaRashi.m b/matRad_calcSigmaRashi.m
deleted file mode 100644
index 1802ce3da..000000000
--- a/matRad_calcSigmaRashi.m
+++ /dev/null
@@ -1,85 +0,0 @@
-function sigmaRashi = matRad_calcSigmaRashi(energy,rangeShifter,SSD)
-% calculation of additional beam broadening due to the use of range shifters
-% (only for protons)
-%
-% call
-% sigmaRashi = matRad_calcSigmaRashi(energy,rangeShifter,SSD)
-%
-% input
-% energy: initial particle energy
-% rangeShifter: structure defining range shifter geometry
-% SSD: source to surface distance
-%
-% output
-% sigmaRashi: sigma of range shifter (to be added ^2) in mm
-%
-% References
-% https://www.ncbi.nlm.nih.gov/pubmed/12375823
-% https://www.ncbi.nlm.nih.gov/pubmed/12701891
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% distance of range shifter to patient surface
-rashiDist = SSD - rangeShifter.sourceRashiDistance;
-
-% convert to mm
-zS = rashiDist / 10; % [cm] (after division)
-t = rangeShifter.eqThickness / 10; % [cm] (after division)
-
-% --> everything here is in [cm]
-
-a1 = 0.21; % [1]
-a2 = 0.77; % [1]
-c0 = 0.0191027; % [cm]
-c1 = 0.0204539; % [1]
-alpha = 0.0022; % [cm MeV ^ (-p)] %
-p = 1.77; % [1] % Exponent of range-energy relation
-
-% convert energy to range
-R = alpha * (energy ^ p);
-
-% check if valid computation possible or range shifter to thick
-if t / R >= 0.95
- error('Computation of range shifter sigma invalid.');
-end
-
-% Improved HONG's Formula
-s = t / R;
-sigmaT = (a1 * s + a2 * s ^ 2) * (c0 + c1 * R);
-
-PSPpmma = 1.165;
-PrSFpmma = 0.816;
-% PSP_air = 0.00107;
-% PrSF_air = 991;
-C = 13.6; % MeV
-L = 36.1; % cm
-
-F1part1 = (2 * C ^ 2 * alpha ^ (2 / p)) / (4 * L * (2 / p - 1));
-F1part2 = (((1 - s) ^ (2 - 2 / p) - 1) / (2 / p - 2) - s);
-F1part3 = R ^ (2 - 2 / p);
-
-F1 = F1part1 * F1part2 * F1part3;
-
-F2part1 = (C ^ 2 * alpha ^ (2 / p)) / (4 * L * (2 / p - 1));
-F2part2 = (((1 - s) ^ (1 - 2 / p) - 1)) / 1;
-F2part3 = R ^ (1 - 2 / p);
-
-F2 = F2part1 * F2part2 * F2part3;
-
-sigmaProjSq = PrSFpmma ^ 2 * (sigmaT ^ 2 + F1 .* zS * PSPpmma + F2 * (zS * PSPpmma) .^ 2); % [cm ^ 2]
-
-% <-- below in [mm]
-
-sigmaRashi = 10 * sqrt(sigmaProjSq); % [mm] (after factor 10)
-
diff --git a/matRad_compileStandalone.m b/matRad_compileStandalone.m
deleted file mode 100644
index 79fb608ab..000000000
--- a/matRad_compileStandalone.m
+++ /dev/null
@@ -1,204 +0,0 @@
-function matRad_compileStandalone(varargin)
-% Compiles the standalone exectuable & packages installer using Matlab's
-% Compiler Toolbox
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2020 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-p = inputParser;
-
-p.addParameter('isRelease',false,@islogical); %By default we compile a snapshot of the current branch
-p.addParameter('compileWithRT',false,@islogical); %By default we don't package installers with runtime
-p.addParameter('compileWithoutRT',true,@islogical); %By default we do package installers without runtime
-p.addParameter('projectFile','matRad.prj',@(x) exist(x,2)); %Default template prj file
-p.addParameter('override',false,@islogical); %If set we do not care for any argumetn but use the bare *.prj file specified
-
-p.parse(varargin{:});
-
-isRelease = p.Results.isRelease;
-compileWithRT = p.Results.compileWithRT;
-compileWithoutRT = p.Results.compileWithoutRT;
-projectFile = p.Results.projectFile;
-override = p.Results.override;
-
-
-if ~compileWithRT && ~compileWithoutRT
- error('No target specified!');
-end
-
-[~,versionFull] = matRad_version();
-
-%Create Standalone with and without runtime
-xDoc = xmlread(projectFile);
-
-if ~override
-
- if ismac % Mac platform
- suffix = 'Mac';
- installSuffix = 'app';
- xDoc.getElementsByTagName('unix').item(0).getFirstChild.setData('true');
- xDoc.getElementsByTagName('mac').item(0).getFirstChild.setData('true');
- xDoc.getElementsByTagName('windows').item(0).getFirstChild.setData('false');
- xDoc.getElementsByTagName('linux').item(0).getFirstChild.setData('false');
- xDoc.getElementsByTagName('arch').item(0).getFirstChild.setData('maci64');
-
- elseif isunix % Linux platform
- suffix = 'Linux';
- installSuffix = 'install';
- xDoc.getElementsByTagName('unix').item(0).getFirstChild.setData('true');
- xDoc.getElementsByTagName('mac').item(0).getFirstChild.setData('false');
- xDoc.getElementsByTagName('windows').item(0).getFirstChild.setData('false');
- xDoc.getElementsByTagName('linux').item(0).getFirstChild.setData('true');
- xDoc.getElementsByTagName('arch').item(0).getFirstChild.setData('glnxa64');
-
- elseif ispc % Windows platform
- suffix = 'Win';
- installSuffix = 'exe';
- xDoc.getElementsByTagName('unix').item(0).getFirstChild.setData('false');
- xDoc.getElementsByTagName('mac').item(0).getFirstChild.setData('false');
- xDoc.getElementsByTagName('windows').item(0).getFirstChild.setData('true');
- xDoc.getElementsByTagName('linux').item(0).getFirstChild.setData('false');
- xDoc.getElementsByTagName('arch').item(0).getFirstChild.setData('Win64');
- else
- error('Platform not supported')
- end
-
- %Set MATLAB root
- xDoc.getElementsByTagName('root').item(0).getFirstChild.setData(matlabroot);
-
- %Replace the file separator in the file tags
- files = xDoc.getElementsByTagName('file');
- for i=0:files.getLength - 1
- fileName=strrep(strrep(files.item(i).getFirstChild.getData().toCharArray()','/',filesep),'\',filesep);
- files.item(i).getFirstChild.setData(fileName);
-
- %change the file separator in the 'location' attribute of the file tag
- %(if it exists)
- if hasAttributes(files.item(i))
- location=strrep(strrep(files.item(i).getAttribute('location').toCharArray()','/',filesep),'\',filesep);
- files.item(i).setAttribute('location',location);
- end
- end
-
- %Replace the file separator in the attributes and children tags of configuration
- config = xDoc.getElementsByTagName('configuration');
- config.item(0).setAttribute('file',['${PROJECT_ROOT}' filesep projectFile]);
- config.item(0).setAttribute('location','${PROJECT_ROOT}');
- config.item(0).setAttribute('preferred-package-location',['${PROJECT_ROOT}' filesep 'standalone' filesep 'for_redistribution']);
-
- tags = config.item(0).getChildNodes;
-
- for i=0:tags.getLength - 1
- if ~isempty(tags.item(i).getFirstChild)
- fileName=strrep(strrep(tags.item(i).getFirstChild.getData().toCharArray()','/',filesep),'\',filesep);
- tags.item(i).getFirstChild.setData(java.lang.String(fileName));
- end
- end
-
- %Get version and Filenames
- %version= replace(xDoc.getElementsByTagName('param.version').item(0).getFirstChild.getData().toCharArray()','.','_');
-
- vernum = sprintf('%d.%d.%d',versionFull.major,versionFull.minor,versionFull.patch);
-
- tmp_verstr = ['v' vernum];
- if ~isRelease && ~isempty(versionFull.commitID) && ~isempty(versionFull.branch)
- branchinfo = [versionFull.branch '-' versionFull.commitID(1:8)];
- tmp_verstr = [tmp_verstr '_' branchinfo];
- addDescription = ['!!!matRad development version build from branch/commit ' branchinfo '!!!'];
- desc = xDoc.getElementsByTagName('param.description').item(0).getFirstChild.getData().toCharArray()';
- desc = [addDescription ' ' desc];
- xDoc.getElementsByTagName('param.description').item(0).getFirstChild.setData(desc);
- end
-
- %The Application compiler only allows major and minor version number
- vernum_appCompiler = sprintf('%d.%d',versionFull.major,versionFull.minor);
-
- filename_webRT =['matRad_installer' suffix '64_' tmp_verstr];
- filename_withRT =['matRad_installer' suffix '64_wRT_' tmp_verstr];
- xDoc.getElementsByTagName('param.web.mcr.name').item(0).getFirstChild.setData(filename_webRT);
- xDoc.getElementsByTagName('param.package.mcr.name').item(0).getFirstChild.setData(filename_withRT);
- xDoc.getElementsByTagName('param.version').item(0).getFirstChild.setData(vernum_appCompiler);
-
-
-
- %check if the files exist, and delete them
- if isfolder(['standalone' filesep 'for_redistribution' filesep filename_withRT '.' installSuffix])
- rmdir(['standalone' filesep 'for_redistribution' filesep filename_withRT '.' installSuffix],'s')
- rehash
- end
-
- if isfile(['standalone' filesep 'for_redistribution' filesep filename_withRT '.' installSuffix])
- delete(['standalone' filesep 'for_redistribution' filesep filename_withRT '.' installSuffix]);
- end
-
- if isfolder(['standalone' filesep 'for_redistribution' filesep filename_webRT '.' installSuffix])
- rmdir(['standalone' filesep 'for_redistribution' filesep filename_webRT '.' installSuffix],'s')
- rehash
- end
- if isfile(['standalone' filesep 'for_redistribution' filesep filename_webRT '.' installSuffix])
- delete(['standalone' filesep 'for_redistribution' filesep filename_webRT '.' installSuffix]);
- end
-
- %compile with runtime
- if compileWithRT
- xDoc.getElementsByTagName('param.web.mcr').item(0).getFirstChild.setData('false');
- xDoc.getElementsByTagName('param.package.mcr').item(0).getFirstChild.setData('true');
-
- xmlwrite(projectFile,xDoc);
-
- applicationCompiler('-package',projectFile);
-
- % loop until the file is created
- waitForCompilation(['standalone' filesep 'for_redistribution' filesep filename_withRT '.' installSuffix],1000);
- end
-
- if compileWithoutRT
- %compile without runtime
- xDoc.getElementsByTagName('param.web.mcr').item(0).getFirstChild.setData('true');
- xDoc.getElementsByTagName('param.package.mcr').item(0).getFirstChild.setData('false');
-
- xmlwrite(projectFile,xDoc);
-
- applicationCompiler('-package',projectFile);
-
- % loop until the file is created
- waitForCompilation(['standalone' filesep 'for_redistribution' filesep filename_webRT '.' installSuffix],1000);
- end
- disp('Done');
-else %No automatic compilation from template, override with prj file
- applicationCompiler('-package',projectFile);
- %No waiting here because we do not know the output file
-end
-
-end
-
-function waitForCompilation(path,waittime)
-tic
-worked = false;
-while toc0 && (~isfield(dij,'mAlphaDose') || ~isfield(dij,'mSqrtBetaDose')) && strcmp(pln.radiationMode,'carbon')
- warndlg('Alpha and beta matrices for effect based and RBE optimization not available - physical optimization is carried out instead.');
- pln.propOpt.bioOptimization = 'none';
-end
-
-% consider VOI priorities
-cst = matRad_setOverlapPriorities(cst);
-
-
-% check & adjust objectives and constraints internally for fractionation
-for i = 1:size(cst,1)
- %Compatibility Layer for old objective format
- if isstruct(cst{i,6})
- cst{i,6} = arrayfun(@matRad_DoseOptimizationFunction.convertOldOptimizationStruct,cst{i,6},'UniformOutput',false);
- end
- for j = 1:numel(cst{i,6})
-
- obj = cst{i,6}{j};
-
- %In case it is a default saved struct, convert to object
- %Also intrinsically checks that we have a valid optimization
- %objective or constraint function in the end
- if ~isa(obj,'matRad_DoseOptimizationFunction')
- try
- obj = matRad_DoseOptimizationFunction.createInstanceFromStruct(obj);
- catch
- matRad_cfg.dispError('cst{%d,6}{%d} is not a valid Objective/constraint! Remove or Replace and try again!',i,j);
- end
- end
-
- obj = obj.setDoseParameters(obj.getDoseParameters()/pln.numOfFractions);
-
- cst{i,6}{j} = obj;
- end
-end
-
-% resizing cst to dose cube resolution
-cst = matRad_resizeCstToGrid(cst,dij.ctGrid.x,dij.ctGrid.y,dij.ctGrid.z,...
- dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z);
-
-% find target indices and described dose(s) for weight vector
-% initialization
-V = [];
-doseTarget = [];
-ixTarget = [];
-
-for i = 1:size(cst,1)
- if isequal(cst{i,3},'TARGET') && ~isempty(cst{i,6})
- V = [V;cst{i,4}{1}];
-
- %Iterate through objectives/constraints
- fDoses = [];
- for fObjCell = cst{i,6}
- dParams = fObjCell{1}.getDoseParameters();
- %Don't care for Inf constraints
- dParams = dParams(isfinite(dParams));
- %Add do dose list
- fDoses = [fDoses dParams];
- end
-
-
- doseTarget = [doseTarget fDoses];
- ixTarget = [ixTarget i*ones(1,length(fDoses))];
- end
-end
-[doseTarget,i] = max(doseTarget);
-ixTarget = ixTarget(i);
-wOnes = ones(dij.totalNumOfBixels,1);
-
-% modified settings for photon dao
-if pln.propOpt.runDAO && strcmp(pln.radiationMode,'photons')
-% options.ipopt.max_iter = 50;
-% options.ipopt.acceptable_obj_change_tol = 7e-3; % (Acc6), Solved To Acceptable Level if (Acc1),...,(Acc6) fullfiled
-
-end
-% calculate initial beam intensities wInit
-if strcmp(pln.propOpt.bioOptimization,'const_RBExD') && strcmp(pln.radiationMode,'protons')
-
- % check if a constant RBE is defined - if not use 1.1
- if ~isfield(dij,'RBE')
- dij.RBE = 1.1;
- end
- bixelWeight = (doseTarget)/(dij.RBE * mean(dij.physicalDose{1}(V,:)*wOnes));
- wInit = wOnes * bixelWeight;
-
-elseif (strcmp(pln.propOpt.bioOptimization,'LEMIV_effect') || strcmp(pln.propOpt.bioOptimization,'LEMIV_RBExD')) ...
- && strcmp(pln.radiationMode,'carbon')
-
- % retrieve photon LQM parameter
- [ax,bx] = matRad_getPhotonLQMParameters(cst,dij.doseGrid.numOfVoxels,1);
-
- if ~isequal(dij.ax(dij.ax~=0),ax(dij.ax~=0)) || ...
- ~isequal(dij.bx(dij.bx~=0),bx(dij.bx~=0))
- matRad_cfg.dispError('Inconsistent biological parameter - please recalculate dose influence matrix!\n');
- end
-
- for i = 1:size(cst,1)
-
- for j = 1:size(cst{i,6},2)
- % check if prescribed doses are in a valid domain
- if any(cst{i,6}{j}.getDoseParameters() > 5) && isequal(cst{i,3},'TARGET')
- matRad_cfg.dispError('Reference dose > 10 Gy[RBE] for target. Biological optimization outside the valid domain of the base data. Reduce dose prescription or use more fractions.\n');
- end
-
- end
- end
-
- dij.ixDose = dij.bx~=0;
-
- if isequal(pln.propOpt.bioOptimization,'LEMIV_effect')
-
- effectTarget = cst{ixTarget,5}.alphaX * doseTarget + cst{ixTarget,5}.betaX * doseTarget^2;
- p = (sum(dij.mAlphaDose{1}(V,:)*wOnes)) / (sum((dij.mSqrtBetaDose{1}(V,:) * wOnes).^2));
- q = -(effectTarget * length(V)) / (sum((dij.mSqrtBetaDose{1}(V,:) * wOnes).^2));
- wInit = -(p/2) + sqrt((p^2)/4 -q) * wOnes;
-
- elseif isequal(pln.propOpt.bioOptimization,'LEMIV_RBExD')
-
- %pre-calculations
- dij.gamma = zeros(dij.doseGrid.numOfVoxels,1);
- dij.gamma(dij.ixDose) = dij.ax(dij.ixDose)./(2*dij.bx(dij.ixDose));
-
- % calculate current in target
- CurrEffectTarget = (dij.mAlphaDose{1}(V,:)*wOnes + (dij.mSqrtBetaDose{1}(V,:)*wOnes).^2);
- % ensure a underestimated biological effective dose
- TolEstBio = 1.2;
- % calculate maximal RBE in target
- maxCurrRBE = max(-cst{ixTarget,5}.alphaX + sqrt(cst{ixTarget,5}.alphaX^2 + ...
- 4*cst{ixTarget,5}.betaX.*CurrEffectTarget)./(2*cst{ixTarget,5}.betaX*(dij.physicalDose{1}(V,:)*wOnes)));
- wInit = ((doseTarget)/(TolEstBio*maxCurrRBE*max(dij.physicalDose{1}(V,:)*wOnes)))* wOnes;
- end
-
-else
- bixelWeight = (doseTarget)/(mean(dij.physicalDose{1}(V,:)*wOnes));
- wInit = wOnes * bixelWeight;
- pln.propOpt.bioOptimization = 'none';
-end
-
-% set optimization options
-options.radMod = pln.radiationMode;
-options.bioOpt = pln.propOpt.bioOptimization;
-options.ID = [pln.radiationMode '_' pln.propOpt.bioOptimization];
-options.numOfScenarios = dij.numOfScenarios;
-
-%Select Projection
-
-switch pln.propOpt.bioOptimization
- case 'LEMIV_effect'
- backProjection = matRad_EffectProjection;
- case 'const_RBExD'
- backProjection = matRad_ConstantRBEProjection;
- case 'LEMIV_RBExD'
- backProjection = matRad_VariableRBEProjection;
- case 'none'
- backProjection = matRad_DoseProjection;
- otherwise
- warning(['Did not recognize bioloigcal setting ''' pln.probOpt.bioOptimization '''!\nUsing physical dose optimization!']);
- backProjection = matRad_DoseProjection;
-end
-
-
-%backProjection = matRad_DoseProjection();
-
-optiProb = matRad_OptimizationProblem(backProjection);
-
-%optimizer = matRad_OptimizerIPOPT;
-
-if ~isfield(pln.propOpt,'optimizer')
- pln.propOpt.optimizer = 'IPOPT';
-end
-
-switch pln.propOpt.optimizer
- case 'IPOPT'
- optimizer = matRad_OptimizerIPOPT;
- case 'fmincon'
- optimizer = matRad_OptimizerFmincon;
- otherwise
- warning(['Optimizer ''' pln.propOpt.optimizer ''' not known! Fallback to IPOPT!']);
- optimizer = matRad_OptimizerIPOPT;
-end
-
-if ~optimizer.IsAvailable()
- matRad_cfg.dispError(['Optimizer ''' pln.propOpt.optimizer ''' not available!']);
-end
-
-optimizer = optimizer.optimize(wInit,optiProb,dij,cst);
-
-wOpt = optimizer.wResult;
-info = optimizer.resultInfo;
-
-resultGUI = matRad_calcCubes(wOpt,dij);
-resultGUI.wUnsequenced = wOpt;
-resultGUI.usedOptimizer = optimizer;
-resultGUI.info = info;
-
-% unblock mex files
-clear mex
diff --git a/matRad_generateStf.m b/matRad_generateStf.m
deleted file mode 100644
index f128f0db0..000000000
--- a/matRad_generateStf.m
+++ /dev/null
@@ -1,506 +0,0 @@
-function stf = matRad_generateStf(ct,cst,pln,visMode)
-% matRad steering information generation
-%
-% call
-% stf = matRad_generateStf(ct,cst,pln,visMode)
-%
-% input
-% ct: ct cube
-% cst: matRad cst struct
-% pln: matRad plan meta information struct
-% visMode: toggle on/off different visualizations by setting this value to 1,2,3 (optional)
-%
-% output
-% stf: matRad steering information struct
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-matRad_cfg = MatRad_Config.instance();
-
-matRad_cfg.dispInfo('matRad: Generating stf struct... ');
-
-if nargin < 4
- visMode = 0;
-end
-
-if numel(pln.propStf.gantryAngles) ~= numel(pln.propStf.couchAngles)
- matRad_cfg.dispError('Inconsistent number of gantry and couch angles.');
-end
-
-if ~isnumeric(pln.propStf.bixelWidth) || pln.propStf.bixelWidth < 0 || ~isfinite(pln.propStf.bixelWidth)
- matRad_cfg.dispError('bixel width (spot distance) needs to be a real number [mm] larger than zero.');
-end
-
-% find all target voxels from cst cell array
-V = [];
-for i=1:size(cst,1)
- if isequal(cst{i,3},'TARGET') && ~isempty(cst{i,6})
- V = [V;vertcat(cst{i,4}{:})];
- end
-end
-
-% Remove double voxels
-V = unique(V);
-% generate voi cube for targets
-voiTarget = zeros(ct.cubeDim);
-voiTarget(V) = 1;
-
-% add margin
-addmarginBool = matRad_cfg.propStf.defaultAddMargin;
-if isfield(pln,'propStf') && isfield(pln.propStf,'addMargin')
- addmarginBool = pln.propStf.addMargin;
-end
-
-if addmarginBool
- voiTarget = matRad_addMargin(voiTarget,cst,ct.resolution,ct.resolution,true);
- V = find(voiTarget>0);
-end
-
-% throw error message if no target is found
-if isempty(V)
- matRad_cfg.dispError('Could not find target.');
-end
-
-% Convert linear indices to 3D voxel coordinates
-[coordsY_vox, coordsX_vox, coordsZ_vox] = ind2sub(ct.cubeDim,V);
-
-% prepare structures necessary for particles
-fileName = [pln.radiationMode '_' pln.machine];
-try
- load([fileparts(mfilename('fullpath')) filesep 'basedata' filesep fileName]);
- SAD = machine.meta.SAD;
-catch
- matRad_cfg.dispError('Could not find the following machine file: %s',fileName);
-end
-
-if strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon')
-
- availableEnergies = [machine.data.energy];
- availablePeakPos = [machine.data.peakPos] + [machine.data.offset];
-
- if sum(availablePeakPos<0)>0
- matRad_cfg.dispError('at least one available peak position is negative - inconsistent machine file')
- end
- %clear machine;
-end
-
-% calculate rED or rSP from HU
-ct = matRad_calcWaterEqD(ct, pln);
-
-% take only voxels inside patient
-V = [cst{:,4}];
-V = unique(vertcat(V{:}));
-
-% ignore densities outside of contours
-eraseCtDensMask = ones(prod(ct.cubeDim),1);
-eraseCtDensMask(V) = 0;
-for i = 1:ct.numOfCtScen
- ct.cube{i}(eraseCtDensMask == 1) = 0;
-end
-
-% Define steering file like struct. Prellocating for speed.
-stf = struct;
-
-% loop over all angles
-for i = 1:length(pln.propStf.gantryAngles)
-
- % Correct for iso center position. Whit this correction Isocenter is
- % (0,0,0) [mm]
- coordsX = coordsX_vox*ct.resolution.x - pln.propStf.isoCenter(i,1);
- coordsY = coordsY_vox*ct.resolution.y - pln.propStf.isoCenter(i,2);
- coordsZ = coordsZ_vox*ct.resolution.z - pln.propStf.isoCenter(i,3);
-
- % Save meta information for treatment plan
- stf(i).gantryAngle = pln.propStf.gantryAngles(i);
- stf(i).couchAngle = pln.propStf.couchAngles(i);
- stf(i).bixelWidth = pln.propStf.bixelWidth;
- stf(i).radiationMode = pln.radiationMode;
- stf(i).SAD = SAD;
- stf(i).isoCenter = pln.propStf.isoCenter(i,:);
-
- % Get the (active) rotation matrix. We perform a passive/system
- % rotation with row vector coordinates, which would introduce two
- % inversions / transpositions of the matrix, thus no changes to the
- % rotation matrix are necessary
- rotMat_system_T = matRad_getRotationMatrix(pln.propStf.gantryAngles(i),pln.propStf.couchAngles(i));
-
- rot_coords = [coordsX coordsY coordsZ]*rotMat_system_T;
-
- % project x and z coordinates to isocenter
- coordsAtIsoCenterPlane(:,1) = (rot_coords(:,1)*SAD)./(SAD + rot_coords(:,2));
- coordsAtIsoCenterPlane(:,2) = (rot_coords(:,3)*SAD)./(SAD + rot_coords(:,2));
-
- % Take unique rows values for beamlets positions. Calculate position of
- % central ray for every bixel
- rayPos = unique(pln.propStf.bixelWidth*round([ coordsAtIsoCenterPlane(:,1) ...
- zeros(size(coordsAtIsoCenterPlane,1),1) ...
- coordsAtIsoCenterPlane(:,2)]/pln.propStf.bixelWidth),'rows');
-
- % pad ray position array if resolution of target voxel grid not sufficient
- maxCtResolution = max([ct.resolution.x ct.resolution.y ct.resolution.z]);
- if pln.propStf.bixelWidth < maxCtResolution
- origRayPos = rayPos;
- for j = -floor(maxCtResolution/pln.propStf.bixelWidth):floor(maxCtResolution/pln.propStf.bixelWidth)
- for k = -floor(maxCtResolution/pln.propStf.bixelWidth):floor(maxCtResolution/pln.propStf.bixelWidth)
- if abs(j)+abs(k)==0
- continue;
- end
- rayPos = [rayPos; origRayPos(:,1)+j*pln.propStf.bixelWidth origRayPos(:,2) origRayPos(:,3)+k*pln.propStf.bixelWidth];
- end
- end
- end
-
- % remove spaces within rows of bixels for DAO
- if pln.propOpt.runDAO
- % create single x,y,z vectors
- x = rayPos(:,1);
- y = rayPos(:,2);
- z = rayPos(:,3);
- uniZ = unique(z);
- for j = 1:numel(uniZ)
- x_loc = x(z == uniZ(j));
- x_min = min(x_loc);
- x_max = max(x_loc);
- x = [x; [x_min:pln.propStf.bixelWidth:x_max]'];
- y = [y; zeros((x_max-x_min)/pln.propStf.bixelWidth+1,1)];
- z = [z; uniZ(j)*ones((x_max-x_min)/pln.propStf.bixelWidth+1,1)];
- end
-
- rayPos = [x,y,z];
- end
-
- % remove double rays
- rayPos = unique(rayPos,'rows');
-
- % Save the number of rays
- stf(i).numOfRays = size(rayPos,1);
-
- % Save ray and target position in beam eye's view (bev)
- for j = 1:stf(i).numOfRays
- stf(i).ray(j).rayPos_bev = rayPos(j,:);
- stf(i).ray(j).targetPoint_bev = [2*stf(i).ray(j).rayPos_bev(1) ...
- SAD ...
- 2*stf(i).ray(j).rayPos_bev(3)];
- end
-
- % source position in bev
- stf(i).sourcePoint_bev = [0 -SAD 0];
-
- % get (active) rotation matrix
- % transpose matrix because we are working with row vectors
- rotMat_vectors_T = transpose(matRad_getRotationMatrix(pln.propStf.gantryAngles(i),pln.propStf.couchAngles(i)));
-
-
- stf(i).sourcePoint = stf(i).sourcePoint_bev*rotMat_vectors_T;
-
- % Save ray and target position in lps system.
- for j = 1:stf(i).numOfRays
- stf(i).ray(j).rayPos = stf(i).ray(j).rayPos_bev*rotMat_vectors_T;
- stf(i).ray(j).targetPoint = stf(i).ray(j).targetPoint_bev*rotMat_vectors_T;
- if strcmp(pln.radiationMode,'photons')
- stf(i).ray(j).beamletCornersAtIso = [rayPos(j,:) + [+stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];...
- rayPos(j,:) + [-stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];...
- rayPos(j,:) + [-stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2];...
- rayPos(j,:) + [+stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2]]*rotMat_vectors_T;
- stf(i).ray(j).rayCorners_SCD = (repmat([0, machine.meta.SCD - SAD, 0],4,1)+ (machine.meta.SCD/SAD) * ...
- [rayPos(j,:) + [+stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];...
- rayPos(j,:) + [-stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];...
- rayPos(j,:) + [-stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2];...
- rayPos(j,:) + [+stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2]])*rotMat_vectors_T;
- end
- end
-
- % loop over all rays to determine meta information for each ray
- stf(i).numOfBixelsPerRay = ones(1,stf(i).numOfRays);
-
- for j = stf(i).numOfRays:-1:1
-
- % ray tracing necessary to determine depth of the target
- [~,l,rho,~,~] = matRad_siddonRayTracer(stf(i).isoCenter, ...
- ct.resolution, ...
- stf(i).sourcePoint, ...
- stf(i).ray(j).targetPoint, ...
- [{ct.cube{1}} {voiTarget}]);
-
- % find appropriate energies for particles
- if strcmp(stf(i).radiationMode,'protons') || strcmp(stf(i).radiationMode,'carbon')
-
- % target hit
- if sum(rho{2}) > 0
-
- % compute radiological depths
- % http://www.ncbi.nlm.nih.gov/pubmed/4000088, eq 14
- radDepths = cumsum(l .* rho{1});
-
- % find target entry & exit
- diff_voi = diff([rho{2}]);
- targetEntry = radDepths(diff_voi == 1);
- targetExit = radDepths(diff_voi == -1);
-
- if numel(targetEntry) ~= numel(targetExit)
- matRad_cfg.dispError('Inconsistency during ray tracing. Please check correct assignment and overlap priorities of structure types OAR & TARGET.');
- end
-
- stf(i).ray(j).energy = [];
-
- % Save energies in stf struct
- for k = 1:numel(targetEntry)
- stf(i).ray(j).energy = [stf(i).ray(j).energy availableEnergies(availablePeakPos>=targetEntry(k)&availablePeakPos<=targetExit(k))];
- end
-
- % book keeping & calculate focus index
- stf(i).numOfBixelsPerRay(j) = numel([stf(i).ray(j).energy]);
- currentMinimumFWHM = matRad_interp1(machine.meta.LUT_bxWidthminFWHM(1,:)',...
- machine.meta.LUT_bxWidthminFWHM(2,:)',...
- pln.propStf.bixelWidth);
- focusIx = ones(stf(i).numOfBixelsPerRay(j),1);
- [~, vEnergyIx] = min(abs(bsxfun(@minus,[machine.data.energy]',...
- repmat(stf(i).ray(j).energy,length([machine.data]),1))));
-
- % get for each spot the focus index
- for k = 1:stf(i).numOfBixelsPerRay(j)
- focusIx(k) = find(machine.data(vEnergyIx(k)).initFocus.SisFWHMAtIso > currentMinimumFWHM,1,'first');
- end
-
- stf(i).ray(j).focusIx = focusIx';
-
- else % target not hit
- stf(i).ray(j) = [];
- stf(i).numOfBixelsPerRay(j) = [];
- end
-
- elseif strcmp(stf(i).radiationMode,'photons')
-
- % book keeping for photons
- stf(i).ray(j).energy = machine.data.energy;
-
- else
- matRad_cfg.dispError('Error generating stf struct: invalid radiation modality.');
- end
-
- end
-
- % store total number of rays for beam-i
- stf(i).numOfRays = size(stf(i).ray,2);
-
- % post processing for particle remove energy slices
- if strcmp(stf(i).radiationMode,'protons') || strcmp(stf(i).radiationMode,'carbon')
-
- % get minimum energy per field
- minEnergy = min([stf(i).ray.energy]);
- maxEnergy = max([stf(i).ray.energy]);
-
- % get corresponding peak position
- availableEnergies = [machine.data.energy];
- minPeakPos = machine.data(minEnergy == availableEnergies).peakPos;
- maxPeakPos = machine.data(maxEnergy == availableEnergies).peakPos;
-
- % find set of energyies with adequate spacing
- if ~isfield(pln.propStf, 'longitudinalSpotSpacing')
- longitudinalSpotSpacing = matRad_cfg.propStf.defaultLongitudinalSpotSpacing;
- else
- longitudinalSpotSpacing = pln.propStf.longitudinalSpotSpacing;
- end
-
- stf(i).longitudinalSpotSpacing = longitudinalSpotSpacing;
-
- tolerance = longitudinalSpotSpacing/10;
- availablePeakPos = [machine.data.peakPos];
-
- useEnergyBool = availablePeakPos >= minPeakPos & availablePeakPos <= maxPeakPos;
-
- ixCurr = find(useEnergyBool,1,'first');
- ixRun = ixCurr + 1;
- ixEnd = find(useEnergyBool,1,'last');
-
- while ixRun <= ixEnd
- if abs(availablePeakPos(ixRun)-availablePeakPos(ixCurr)) < ...
- longitudinalSpotSpacing - tolerance
- useEnergyBool(ixRun) = 0;
- else
- ixCurr = ixRun;
- end
- ixRun = ixRun + 1;
- end
-
- for j = stf(i).numOfRays:-1:1
- for k = stf(i).numOfBixelsPerRay(j):-1:1
- maskEnergy = stf(i).ray(j).energy(k) == availableEnergies;
- if ~useEnergyBool(maskEnergy)
- stf(i).ray(j).energy(k) = [];
- stf(i).ray(j).focusIx(k) = [];
- stf(i).numOfBixelsPerRay(j) = stf(i).numOfBixelsPerRay(j) - 1;
- end
- end
- if isempty(stf(i).ray(j).energy)
- stf(i).ray(j) = [];
- stf(i).numOfBixelsPerRay(j) = [];
- stf(i).numOfRays = stf(i).numOfRays - 1;
- end
- end
-
- end
-
- % save total number of bixels
- stf(i).totalNumOfBixels = sum(stf(i).numOfBixelsPerRay);
-
- % Show progress
- matRad_progress(i,length(pln.propStf.gantryAngles));
-
- %% visualization
- if visMode > 0
-
- clf;
- % first subplot: visualization in bev
- subplot(1,2,1)
- hold on
-
- % plot rotated target coordinates
- plot3(rot_coords(:,1),rot_coords(:,2),rot_coords(:,3),'r.')
-
- % surface rendering
- if visMode == 2
-
- % generate a 3D rectangular grid centered at isocenter in
- % voxel coordinates
- [X,Y,Z] = meshgrid((1:ct.cubeDim(2))-stf(i).isoCenter(1)/ct.resolution.x, ...
- (1:ct.cubeDim(1))-stf(i).isoCenter(2)/ct.resolution.y, ...
- (1:ct.cubeDim(3))-stf(i).isoCenter(3)/ct.resolution.z);
-
- % computes surface
- patSurfCube = 0*ct.cube{1};
- idx = [cst{:,4}];
- idx = unique(vertcat(idx{:}));
- patSurfCube(idx) = 1;
-
- [f,v] = isosurface(X,Y,Z,patSurfCube,.5);
-
- % convert isosurface from voxel to [mm]
- v(:,1) = v(:,1)*ct.resolution.x;
- v(:,2) = v(:,2)*ct.resolution.y;
- v(:,3) = v(:,3)*ct.resolution.z;
-
- % rotate surface
- rotated_surface = v*rotMat_system_T;
-
- % surface rendering
- surface = patch('Faces',f,'Vertices',rotated_surface);
- set(surface,'FaceColor',[0 0 1],'EdgeColor','none','FaceAlpha',.4);
- lighting gouraud;
-
- end
-
- % plot projection matrix: coordinates at isocenter
- plot3(rayPos(:,1),rayPos(:,2),rayPos(:,3),'k.');
-
- % Plot matrix border of matrix at isocenter
- for j = 1:stf(i).numOfRays
-
- % Compute border for every bixels
- targetPoint_vox_X_1 = stf(i).ray(j).targetPoint_bev(:,1) + pln.propStf.bixelWidth;
- targetPoint_vox_Y_1 = stf(i).ray(j).targetPoint_bev(:,2);
- targetPoint_vox_Z_1 = stf(i).ray(j).targetPoint_bev(:,3) + pln.propStf.bixelWidth;
-
- targetPoint_vox_X_2 = stf(i).ray(j).targetPoint_bev(:,1) + pln.propStf.bixelWidth;
- targetPoint_vox_Y_2 = stf(i).ray(j).targetPoint_bev(:,2);
- targetPoint_vox_Z_2 = stf(i).ray(j).targetPoint_bev(:,3) - pln.propStf.bixelWidth;
-
- targetPoint_vox_X_3 = stf(i).ray(j).targetPoint_bev(:,1) - pln.propStf.bixelWidth;
- targetPoint_vox_Y_3 = stf(i).ray(j).targetPoint_bev(:,2);
- targetPoint_vox_Z_3 = stf(i).ray(j).targetPoint_bev(:,3) - pln.propStf.bixelWidth;
-
- targetPoint_vox_X_4 = stf(i).ray(j).targetPoint_bev(:,1) - pln.propStf.bixelWidth;
- targetPoint_vox_Y_4 = stf(i).ray(j).targetPoint_bev(:,2);
- targetPoint_vox_Z_4 = stf(i).ray(j).targetPoint_bev(:,3) + pln.propStf.bixelWidth;
-
- % plot
- plot3([stf(i).sourcePoint_bev(1) targetPoint_vox_X_1],[stf(i).sourcePoint_bev(2) targetPoint_vox_Y_1],[stf(i).sourcePoint_bev(3) targetPoint_vox_Z_1],'g')
- plot3([stf(i).sourcePoint_bev(1) targetPoint_vox_X_2],[stf(i).sourcePoint_bev(2) targetPoint_vox_Y_2],[stf(i).sourcePoint_bev(3) targetPoint_vox_Z_2],'g')
- plot3([stf(i).sourcePoint_bev(1) targetPoint_vox_X_3],[stf(i).sourcePoint_bev(2) targetPoint_vox_Y_3],[stf(i).sourcePoint_bev(3) targetPoint_vox_Z_3],'g')
- plot3([stf(i).sourcePoint_bev(1) targetPoint_vox_X_4],[stf(i).sourcePoint_bev(2) targetPoint_vox_Y_4],[stf(i).sourcePoint_bev(3) targetPoint_vox_Z_4],'g')
-
- end
-
- % Plot properties
- daspect([1 1 1]);
- view(0,-90);
- xlabel 'X [mm]'
- ylabel 'Y [mm]'
- zlabel 'Z [mm]'
- title ('Beam''s eye view')
- axis([-300 300 -300 300 -300 300]);
-
- % second subplot: visualization in lps coordinate system
- subplot(1,2,2)
-
- % Plot target coordinates whitout any rotation
- plot3(coordsX,coordsY,coordsZ,'r.')
- hold on;
-
- % Rotated projection matrix at isocenter
- isocenter_plane_coor = rayPos*rotMat_vectors_T;
-
- % Plot isocenter plane
- plot3(isocenter_plane_coor(:,1),isocenter_plane_coor(:,2),isocenter_plane_coor(:,3),'y.');
-
- % Plot rotated bixels border.
- for j = 1:stf(i).numOfRays
- % Generate rotated projection target points.
- targetPoint_vox_1_rotated = [stf(i).ray(j).targetPoint_bev(:,1) + pln.propStf.bixelWidth,stf(i).ray(j).targetPoint_bev(:,2),stf(i).ray(j).targetPoint_bev(:,3) + pln.propStf.bixelWidth]*rotMat_vectors_T;
- targetPoint_vox_2_rotated = [stf(i).ray(j).targetPoint_bev(:,1) + pln.propStf.bixelWidth,stf(i).ray(j).targetPoint_bev(:,2),stf(i).ray(j).targetPoint_bev(:,3) - pln.propStf.bixelWidth]*rotMat_vectors_T;
- targetPoint_vox_3_rotated = [stf(i).ray(j).targetPoint_bev(:,1) - pln.propStf.bixelWidth,stf(i).ray(j).targetPoint_bev(:,2),stf(i).ray(j).targetPoint_bev(:,3) - pln.propStf.bixelWidth]*rotMat_vectors_T;
- targetPoint_vox_4_rotated = [stf(i).ray(j).targetPoint_bev(:,1) - pln.propStf.bixelWidth,stf(i).ray(j).targetPoint_bev(:,2),stf(i).ray(j).targetPoint_bev(:,3) + pln.propStf.bixelWidth]*rotMat_vectors_T;
-
- % Plot rotated target points.
- plot3([stf(i).sourcePoint(1) targetPoint_vox_1_rotated(:,1)],[stf(i).sourcePoint(2) targetPoint_vox_1_rotated(:,2)],[stf(i).sourcePoint(3) targetPoint_vox_1_rotated(:,3)],'g')
- plot3([stf(i).sourcePoint(1) targetPoint_vox_2_rotated(:,1)],[stf(i).sourcePoint(2) targetPoint_vox_2_rotated(:,2)],[stf(i).sourcePoint(3) targetPoint_vox_2_rotated(:,3)],'g')
- plot3([stf(i).sourcePoint(1) targetPoint_vox_3_rotated(:,1)],[stf(i).sourcePoint(2) targetPoint_vox_3_rotated(:,2)],[stf(i).sourcePoint(3) targetPoint_vox_3_rotated(:,3)],'g')
- plot3([stf(i).sourcePoint(1) targetPoint_vox_4_rotated(:,1)],[stf(i).sourcePoint(2) targetPoint_vox_4_rotated(:,2)],[stf(i).sourcePoint(3) targetPoint_vox_4_rotated(:,3)],'g')
- end
-
- % surface rendering
- if visMode == 2
- surface = patch('Faces',f,'Vertices',v);
- set(surface,'FaceColor',[0 0 1],'EdgeColor','none','FaceAlpha',.4);
- lighting gouraud;
- end
-
- % labels etc.
- daspect([1 1 1]);
- view(0,-90);
- xlabel 'X [mm]'
- ylabel 'Y [mm]'
- zlabel 'Z [mm]'
- title 'lps coordinate system'
- axis([-300 300 -300 300 -300 300]);
- %pause(1);
- end
-
- % include rangeshifter data if not yet available
- if strcmp(pln.radiationMode, 'protons') || strcmp(pln.radiationMode, 'carbon')
- for j = 1:stf(i).numOfRays
- for k = 1:numel(stf(i).ray(j).energy)
- stf(i).ray(j).rangeShifter(k).ID = 0;
- stf(i).ray(j).rangeShifter(k).eqThickness = 0;
- stf(i).ray(j).rangeShifter(k).sourceRashiDistance = 0;
- end
- end
- end
-
-end
-
-end
diff --git a/matRad_getPhotonLQMParameters.m b/matRad_getPhotonLQMParameters.m
deleted file mode 100644
index df32788c6..000000000
--- a/matRad_getPhotonLQMParameters.m
+++ /dev/null
@@ -1,56 +0,0 @@
-function [ax,bx] = matRad_getPhotonLQMParameters(cst,numVoxel,ctScen,VdoseGrid)
-% matRad function to receive the photon LQM reference parameter
-%
-% call
-% [ax,bx] = matRad_getPhotonLQMParameters(cst,numVoxel,ctScen,VdoseGrid)
-%
-% input
-% cst: matRad cst struct
-% numVoxel: number of voxels of the dose cube
-% ctScen: CT scenarios for alpha_x and beta_x should be calculated
-% VdoseGrid: optional linear index vector that allows to specify subindices
-% for which ax and bx will be computed
-%
-% output
-% ax: vector containing for each linear voxel index alpha_x
-% bx: vector containing for each linear voxel index beta_x
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2018 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-ax = zeros(numVoxel,ctScen);
-bx = zeros(numVoxel,ctScen);
-
-for i = 1:size(cst,1)
- if isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET')
- for ctScen = 1:numel(cst{i,4})
-
- if exist('VdoseGrid','var')
- isInVdoseGrid = ismember(VdoseGrid,cst{i,4}{ctScen});
- ax(VdoseGrid(isInVdoseGrid)) = cst{i,5}.alphaX;
- bx(VdoseGrid(isInVdoseGrid)) = cst{i,5}.betaX;
- else
- ax(cst{i,4}{ctScen},ctScen) = cst{i,5}.alphaX;
- bx(cst{i,4}{ctScen},ctScen) = cst{i,5}.betaX;
- end
-
- end
- end
-end
-
-
-
-
diff --git a/matRad_rc.m b/matRad_rc.m
index 52f2a07a6..8a5b98106 100644
--- a/matRad_rc.m
+++ b/matRad_rc.m
@@ -1,3 +1,4 @@
+function matRad_cfg = matRad_rc(clearWindow)
% matRad rc script
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -6,26 +7,66 @@
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% set search path
+if nargin < 1
+ clearWindow = false;
+end
-matRad_cfg = MatRad_Config.instance();
+thisFolder = fileparts(mfilename("fullpath"));
-%clear command window and close all figures
-clc;
-close all;
+sameMatRad = false;
+% Initialize matRad
+try %Check if matRad is already on the path and if so, if it is the same installation
+ tmp_cfg = MatRad_Config.instance();
+ if ~strcmp(tmp_cfg.matRadRoot,thisFolder)
+ tmp_cfg.dispWarning('Called matRad_rc in folder %s but matRad initialized in folder %s!\n Removing old matRad from path and using new installation!',fileparts(mfilename("fullpath")),tmp_cfg.matRadRoot);
+ if ~isdeployed
+ rmpath(genpath(tmp_cfg.matRadRoot));
+ end
+ clear tmp_cfg MatRad_Config;
+ tmp_cfg = MatRad_Config.instance();
+ sameMatRad = false;
+ else
+ sameMatRad = true;
+ end
+catch %If matRad is not on the path, initialize it freshly and add the sourcefolder containing MatRad_Config to the path
+ matRadRoot = thisFolder;
+ matRadSrcRoot = fullfile(thisFolder,'matRad');
+ if ~isdeployed
+ addpath(matRadRoot,matRadSrcRoot);
+ end
+ tmp_cfg = MatRad_Config.instance();
+ sameMatRad = false;
+end
% clear workspace and command prompt, close all figures
-[env,envver] = matRad_getEnvironment();
+if clearWindow
+ clc;
+ close all;
+end
+
+% Version Display
vString = matRad_version();
-matRad_cfg.dispInfo('You are running matRad %s with %s %s\n',vString,env,envver);
-clear env envver vString;
+if ~sameMatRad || clearWindow
+ %Version Info
+ versionStr = sprintf('You are running matRad %s with %s %s',vString,tmp_cfg.env,tmp_cfg.envVersion);
+ infoStr = matRad_info();
+
+ message = [versionStr newline infoStr newline];
+ tmp_cfg.dispInfo(message);
+end
+%Return MatRad_Config instance
+if nargout > 0
+ matRad_cfg = tmp_cfg;
+else
+ assignin('base','matRad_cfg',tmp_cfg);
+end
\ No newline at end of file
diff --git a/matRad_runTests.m b/matRad_runTests.m
new file mode 100644
index 000000000..efcd8bc46
--- /dev/null
+++ b/matRad_runTests.m
@@ -0,0 +1,87 @@
+function result = matRad_runTests(folder,withCoverage)
+%% matRad_runTests.m
+% This function runs the test suite for the matRad package.
+%
+% Usage:
+% - Run the script to execute all the tests in the test suite.
+%
+% Notes:
+% - Make sure to have MOxUnit installed and added to the path.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+back = cd(fileparts(mfilename("fullpath")));
+matRad_cfg = matRad_rc;
+
+if nargin < 2
+ withCoverage = false;
+end
+
+if withCoverage && matRad_cfg.isOctave
+ matRad_cfg.dispWarning('Coverage collection not possible with Octave. Turning off!');
+ withCoverage = false;
+end
+
+%add MOxUnit submodule if not yet on path
+if exist('moxunit_runtests','file') == 2
+ matRad_cfg.dispInfo('MOxUnit is already on the path and this version will be used for testing: ');
+else
+ matRad_cfg.dispInfo('MOxUnit submodule will be added to the path and used for testing: ');
+ rootFolder = cd(fullfile(matRad_cfg.matRadRoot,'submodules','MOxUnit','MOxUnit'));
+ moxunit_set_path;
+ cd(rootFolder);
+end
+matRad_cfg.dispInfo('%s\n',fileparts(which("moxunit_runtests")));
+
+%add MOcov if coverage will be recorded
+if withCoverage
+ if exist('mocov','file') == 2
+ matRad_cfg.dispInfo('MOcov is already on the path and this version will be used for coverage collection: ');
+ else
+ matRad_cfg.dispInfo('MOcov submodule will be added to the path and used for coverage collection: ');
+ addpath(fullfile(matRad_cfg.matRadRoot,'submodules','MOcov','MOcov'));
+ end
+ matRad_cfg.dispInfo('%s\n',fileparts(which("mocov.m")));
+end
+
+
+matRad_cfg.dispInfo('Setting default properties for testing and starting test suite!\n');
+matRad_cfg.setDefaultPropertiesForTesting();
+matRad_cfg.logLevel = 1;
+
+addpath(fullfile(matRad_cfg.matRadRoot,'test'));
+addpath(fullfile(matRad_cfg.matRadRoot,'test','testData'));
+if nargin < 1
+ folder = 'test';
+end
+
+%add test folder to path
+addpath(genpath(fullfile(matRad_cfg.matRadRoot,folder)));
+
+if withCoverage
+ result = moxunit_runtests(folder,'-recursive','-junit_xml_file','testresults.xml',...
+ '-with_coverage','-cover','matRad',...
+ '-cover_xml_file','coverage.xml','-cover_json_file','coverage.json',...
+ '-cover_text_output','cmdline',...
+ '-cover_method','profile');
+else
+ result = moxunit_runtests(folder,'-recursive','-junit_xml_file','testresults.xml');
+end
+
+
+matRad_cfg.setDefaultProperties();
+matRad_cfg.logLevel = 3;
+cd(back);
+clear back;
+matRad_cfg.dispInfo('Restored default properties and returned to original directory!\n');
\ No newline at end of file
diff --git a/matRad_showDVH.m b/matRad_showDVH.m
deleted file mode 100644
index 312949d16..000000000
--- a/matRad_showDVH.m
+++ /dev/null
@@ -1,104 +0,0 @@
-function matRad_showDVH(dvh,cst,pln,lineStyleIndicator)
-% matRad dvh visualizaion
-%
-% call
-% matRad_showDVH(dvh,cst)
-% matRad_showDVH(dvh,cst,pln)
-% matRad_showDVH(dvh,cst,lineStyleIndicator)
-% matRad_showDVH(dvh,cst,pln,lineStyleIndicator)
-%
-% input
-% dvh: result struct from fluence optimization/sequencing
-% cst: matRad cst struct
-% pln: (now optional) matRad pln struct,
-% standard uses Dose [Gy]
-% lineStyleIndicator: (optional) integer (1,2,3,4) to indicate the current linestyle
-% (hint: use different lineStyles to overlay
-% different dvhs)
-%
-% output
-% graphical display of DVH
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-if ~exist('lineStyleIndicator','var') || isempty(lineStyleIndicator)
- lineStyleIndicator = 1;
-end
-
-% create new figure and set default line style indicator if not explictly
-% specified
-hold on;
-
-%reduce cst
-visibleIx = cellfun(@(c) c.Visible == 1,cst(:,5));
-cstNames = cst(visibleIx,2);
-cstInfo = cst(visibleIx,5);
-dvh = dvh(visibleIx);
-
-numOfVois = numel(cstNames);
-
-%% print the dvh
-
-%try to get colors from cst
-try
- colorMx = cellfun(@(c) c.visibleColor,cstInfo,'UniformOutput',false);
- colorMx = cell2mat(colorMx);
-catch
- colorMx = colorcube;
- colorMx = colorMx(1:floor(64/numOfVois):64,:);
-end
-
-lineStyles = {'-',':','--','-.'};
-
-maxDVHvol = 0;
-maxDVHdose = 0;
-
-for i = 1:numOfVois
- % cut off at the first zero value where there is no more signal
- % behind
- ix = max([1 find(dvh(i).volumePoints>0,1,'last')]);
- currDvh = [dvh(i).doseGrid(1:ix);dvh(i).volumePoints(1:ix)];
-
- plot(currDvh(1,:),currDvh(2,:),'LineWidth',4,'Color',colorMx(i,:), ...
- 'LineStyle',lineStyles{lineStyleIndicator},'DisplayName',cstNames{i})
-
- maxDVHvol = max(maxDVHvol,max(currDvh(2,:)));
- maxDVHdose = max(maxDVHdose,max(currDvh(1,:)));
-end
-
-fontSizeValue = 14;
-myLegend = legend('show','location','NorthEast');
-set(myLegend,'FontSize',10,'Interpreter','none');
-legend boxoff
-
-ylim([0 1.1*maxDVHvol]);
-xlim([0 1.2*maxDVHdose]);
-
-grid on,grid minor
-box(gca,'on');
-set(gca,'LineWidth',1.5,'FontSize',fontSizeValue);
-ylabel('Volume [%]','FontSize',fontSizeValue)
-
-if exist('pln','var') && ~isempty(pln)
- if strcmp(pln.propOpt.bioOptimization,'none')
- xlabel('Dose [Gy]','FontSize',fontSizeValue);
- else
- xlabel('RBE x Dose [Gy(RBE)]','FontSize',fontSizeValue);
- end
-else
- xlabel('Dose [Gy]','FontSize',fontSizeValue);
-end
diff --git a/matRad_showQualityIndicators.m b/matRad_showQualityIndicators.m
deleted file mode 100644
index f1495ae30..000000000
--- a/matRad_showQualityIndicators.m
+++ /dev/null
@@ -1,62 +0,0 @@
-function matRad_showQualityIndicators(qi)
-% matRad display of quality indicators as table
-%
-% call
-% matRad_showQualityIndicators(qi)
-%
-% input
-% qi: result struct from matRad_calcQualityIndicators
-%
-% output
-% graphical display of quality indicators in table form
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-matRad_cfg = MatRad_Config.instance();
-
-[env, vStr] = matRad_getEnvironment();
-
-% Create the column and row names in cell arrays
-rnames = {qi.name};
-qi = rmfield(qi,'name');
-cnames = fieldnames(qi);
-for i = 1:numel(cnames)
- ix = find(cnames{i}(4:end) == '_');
- if ~isempty(ix)
- cnames{i}(ix+3) = '.';
- end
-end
-
-%To avoid parse error in octave, replace empty qi values with '-'
-qi = (squeeze(struct2cell(qi)))';
-qiEmpty = cellfun(@isempty,qi);
-qi(qiEmpty) = {'-'};
-
-%since uitable is only available in newer octave versions, we try and catch
-try
- % Create the uitable
- table = uitable(gcf,'Data',qi,...
- 'ColumnName',cnames,...
- 'RowName',rnames,'ColumnWidth',{70});
-
- % Layout
- pos = get(gca,'position');
- set(table,'units','normalized','position',pos)
- axis off
-catch ME
- matRad_cfg.dispWarning('The uitable function is not implemented in %s v%s.',env,vStr);
-end
diff --git a/ompMC/matRad_compileOmpMCInterface.m b/ompMC/matRad_compileOmpMCInterface.m
deleted file mode 100644
index 8afb00cb5..000000000
--- a/ompMC/matRad_compileOmpMCInterface.m
+++ /dev/null
@@ -1,102 +0,0 @@
-function matRad_compileOmpMCInterface(dest,omcFolder)
-% Compiles the ompMC interface (integrated as submodule)
-%
-% call
-% matRad_compileOmpMCInterface()
-% matRad_compileOmpMCInterface(dest)
-% matRad_compileOmpMCInterface(dest,sourceFolder)
-%
-% input:
-% dest: (optional) destination for mex file. Default: location
-% of this file
-% sourceFolder: (optional) path to ompMC . Default assumes its checked
-% out in the submodules folder of matRad
-%
-% References
-%
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2020 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-matRad_cfg = MatRad_Config.instance();
-
-env = matRad_getEnvironment();
-
-if nargin < 1
- dest = fileparts(mfilename('fullpath'));
-end
-
-if nargin < 2
- omcFolder = [matRad_cfg.matRadRoot filesep 'submodules' filesep 'ompMC'];
-end
-
-sourceFolder = [omcFolder filesep 'src'];
-interfaceFolder = [omcFolder filesep 'ucodes' filesep 'omc_matrad'];
-
-mainFile = [interfaceFolder filesep 'omc_matrad.c'];
-
-addFiles = {'ompmc.c','omc_utilities.c','omc_random.c'};
-addFiles = cellfun(@(f) fullfile(sourceFolder,f),addFiles,'UniformOutput',false);
-
-addFiles = strjoin(addFiles,' ');
-
-if exist ('OCTAVE_VERSION','builtin')
- ccName = evalc('mkoctfile -p CC');
-else
- myCCompiler = mex.getCompilerConfigurations('C','Selected');
- ccName = myCCompiler.ShortName;
-end
-
-%These settings have only been tested for MSVC and g++. You may need to adapt for other compilers
-if ~isempty(strfind(ccName,'MSVC')) %Not use contains(...) because of octave
- flags{1,1} = 'COMPFLAGS';
- flags{1,2} = '/openmp';
- flags{2,1} = 'OPTIMFLAGS';
- flags{2,2} = '/O2';
-else
- flags{1,1} = 'CFLAGS';
- flags{1,2} = '-std=gnu99 -fopenmp -O3';
- flags{2,1} = 'LDFLAGS';
- flags{2,2} = '-fopenmp';
-
-end
-
-includestring = ['-I' sourceFolder];
-
-flagstring = '';
-
-%For Octave, the flags will be set in the environment, while they
-%will be parsed as string arguments in MATLAB
-for flag = 1:size(flags,1)
- if strcmp(env,'OCTAVE')
- preFlagContent = evalc(['mkoctfile -p ' flags{flag,1}]);
- if ~isempty(preFlagContent)
- preFlagContent = preFlagContent(1:end-1); %Strip newline
- end
- newContent = [preFlagContent ' ' flags{flag,2}];
- setenv(flags{flag,1},newContent);
- matRad_cfg.dispDebug('Set compiler flag %s to %s\n',flags{flag,1},newContent);
- else
- flagstring = [flagstring flags{flag,1} '="$' flags{flag,1} ' ' flags{flag,2} '" '];
- end
-end
-
-mexCall = ['mex -largeArrayDims ' flagstring ' ' includestring ' ' mainFile ' ' addFiles];
-matRad_cfg.dispDebug('Compiler call: %s\n',mexCall);
-
-currDir = pwd;
-cd(dest);
-eval(mexCall);
-cd(currDir);
-end
diff --git a/optimization/+DoseObjectives/matRad_MeanDose.m b/optimization/+DoseObjectives/matRad_MeanDose.m
deleted file mode 100644
index 43a1207d9..000000000
--- a/optimization/+DoseObjectives/matRad_MeanDose.m
+++ /dev/null
@@ -1,75 +0,0 @@
-classdef matRad_MeanDose < DoseObjectives.matRad_DoseObjective
-% matRad_MeanDose Implements a penalized MeanDose objective
-% See matRad_DoseObjective for interface description
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2020 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
- properties (Constant)
- name = 'Mean Dose';
- parameterNames = {'d^{ref}'};
- parameterTypes = {'dose'};
- %parameterNames = {};
- %parameterIsDose = [];
- end
-
- properties
- parameters = {0};
- penalty = 1;
- end
-
- methods
- function obj = matRad_MeanDose(penalty,dMeanRef)
-
- % if we have a struct in first argument
- if nargin == 1 && isstruct(penalty)
- inputStruct = penalty;
- initFromStruct = true;
- else
- initFromStruct = false;
- inputStruct = [];
- end
-
- %Call Superclass Constructor (for struct initialization)
- obj@DoseObjectives.matRad_DoseObjective(inputStruct);
-
- if ~initFromStruct
- if nargin == 2 && isscalar(dMeanRef)
- obj.parameters{1} = dMeanRef;
- end
-
- if nargin >= 1 && isscalar(penalty)
- obj.penalty = penalty;
- end
- end
-
- end
-
- %% Calculates the Objective Function value
- function fDose = computeDoseObjectiveFunction(obj,dose)
- %fDose = obj.penalty * abs(mean(dose(:)) - obj.parameters{1});
- fDose = obj.penalty * (mean(dose(:)) - obj.parameters{1})^2;
- end
-
- %% Calculates the Objective Function gradient
- function fDoseGrad = computeDoseObjectiveGradient(obj,dose)
- %fDoseGrad = (obj.penalty/numel(dose))*sign(dose(:)-obj.parameters{1});
- fDoseGrad = obj.penalty*2*(mean(dose(:))-obj.parameters{1}) * ones(size(dose(:)))/numel(dose);
- end
- end
-
-end
-
diff --git a/optimization/@matRad_OptimizationProblem/matRad_OptimizationProblem.m b/optimization/@matRad_OptimizationProblem/matRad_OptimizationProblem.m
deleted file mode 100644
index 6f3608fa5..000000000
--- a/optimization/@matRad_OptimizationProblem/matRad_OptimizationProblem.m
+++ /dev/null
@@ -1,60 +0,0 @@
-classdef matRad_OptimizationProblem < handle
- %matRad_OptimizationProblem Main Class for fluence optimization problems
- % Describes a standard fluence optimization problem by providing the
- % implementation of the objective & constraint function/gradient wrappers
- % and managing the mapping and backprojection of the respective dose-
- % related quantity
- %
- % References
- % -
- %
- % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %
- % Copyright 2020 the matRad development team.
- %
- % This file is part of the matRad project. It is subject to the license
- % terms in the LICENSE file found in the top-level directory of this
- % distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
- % of the matRad project, including this file, may be copied, modified,
- % propagated, or distributed except according to the terms contained in the
- % LICENSE file.
- %
- % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
- properties
- BP %matRad_BackProjection object for mapping & backprojection
- bioOpt = '';
- end
-
- methods
- function obj = matRad_OptimizationProblem(backProjection)
- obj.BP = backProjection;
- end
-
- %Objective function declaration
- fVal = matRad_objectiveFunction(optiProb,w,dij,cst)
-
- %Objective gradient declaration
- fGrad = matRad_objectiveGradient(optiProb,w,dij,cst)
-
- %Constraint function declaration
- cVal = matRad_constraintFunctions(optiProb,w,dij,cst)
-
- %Constraint Jacobian declaration
- cJacob = matRad_constraintJacobian(optiProb,w,dij,cst)
-
- %Jacobian Structure
- jacobStruct = matRad_getJacobianStructure(optiProb,w,dij,cst)
-
- [cl,cu] = matRad_getConstraintBounds(optiProb,cst)
-
- function lb = lowerBounds(optiProb,w)
- lb = zeros(size(w));
- end
-
- function ub = upperBounds(optiProb,w)
- ub = Inf * ones(size(w));
- end
- end
-end
-
diff --git a/optimization/@matRad_OptimizationProblem/matRad_constraintFunctions.m b/optimization/@matRad_OptimizationProblem/matRad_constraintFunctions.m
deleted file mode 100644
index f31bbd857..000000000
--- a/optimization/@matRad_OptimizationProblem/matRad_constraintFunctions.m
+++ /dev/null
@@ -1,93 +0,0 @@
-function c = matRad_constraintFunctions(optiProb,w,dij,cst)
-% matRad IPOPT callback: constraint function for inverse planning
-% supporting max dose constraint, min dose constraint, min mean dose constraint,
-% max mean dose constraint, min EUD constraint, max EUD constraint,
-% max DVH constraint, min DVH constraint
-%
-% call
-% c = matRad_constraintFunctions(optiProb,w,dij,cst)
-%
-% input
-% optiProb: option struct defining the type of optimization
-% w: bixel weight vector
-% dij: dose influence matrix
-% cst: matRad cst struct
-%
-% output
-% c: value of constraints
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2016 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-% get current dose / effect / RBExDose vector
-%d = matRad_backProjection(w,dij,optiProb);
-optiProb.BP = optiProb.BP.compute(dij,w);
-d = optiProb.BP.GetResult();
-
-% Initializes constraints
-c = [];
-
-% compute objective function for every VOI.
-for i = 1:size(cst,1)
-
- % Only take OAR or target VOI.
- if ~isempty(cst{i,4}{1}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )
-
- % loop over the number of constraints for the current VOI
- for j = 1:numel(cst{i,6})
-
- obj = cst{i,6}{j};
-
- % only perform computations for constraints
- % if ~isempty(strfind(obj.type,'constraint'))
- if isa(obj,'DoseConstraints.matRad_DoseConstraint')
-
- % if we have effect optimization, temporarily replace doses with effect
- % Maybe we should put some switch into the classes for that
- if (~isequal(obj.name, 'max dose constraint') && ~isequal(obj.name, 'min dose constraint') &&...
- ~isequal(obj.name, 'max mean dose constraint') && ~isequal(obj.name, 'min mean dose constraint') && ...
- ~isequal(obj.name, 'min EUD constraint') && ~isequal(obj.name, 'max EUD constraint')) && ...
- (isa(optiProb.BP,'matRad_EffectProjection') && ~isa(optiProb.BP,'matRad_VariableRBEProjection'))
-
- doses = obj.getDoseParameters();
-
- effect = cst{i,5}.alphaX*doses + cst{i,5}.betaX*doses.^2;
-
- obj = obj.setDoseParameters(effect);
- end
-
- % if conventional opt: just add constraints of nominal dose
- %if strcmp(cst{i,6}(j).robustness,'none')
-
- d_i = d{1}(cst{i,4}{1});
-
- %c = [c; matRad_constFunc(d_i,cst{i,6}(j),d_ref)];
- c = [c; obj.computeDoseConstraintFunction(d_i)];
-
- %else
-
- %error('Invalid robustness setting.');
-
- %end % if we are in the nominal sceario or rob opt
-
- end % if we are a constraint
-
- end % over all defined constraints & objectives
-
- end % if structure not empty and oar or target
-
-end % over all structures
diff --git a/optimization/@matRad_OptimizationProblem/matRad_constraintJacobian.m b/optimization/@matRad_OptimizationProblem/matRad_constraintJacobian.m
deleted file mode 100644
index d0ee2dd22..000000000
--- a/optimization/@matRad_OptimizationProblem/matRad_constraintJacobian.m
+++ /dev/null
@@ -1,203 +0,0 @@
-function jacob = matRad_constraintJacobian(optiProb,w,dij,cst)
-% matRad IPOPT callback: jacobian function for inverse planning
-% supporting max dose constraint, min dose constraint, min mean dose constraint,
-% max mean dose constraint, min EUD constraint, max EUD constraint, max DVH
-% constraint, min DVH constraint
-%
-% call
-% jacob = matRad_jacobFunc(optiProb,w,dij,cst)
-%
-% input
-% optiProb: option struct defining the type of optimization
-% w: bixel weight vector
-% dij: dose influence matrix
-% cst: matRad cst struct
-%
-% output
-% jacob: jacobian of constraint function
-%
-% References
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2016 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% get current dose / effect / RBExDose vector
-%d = matRad_backProjection(w,dij,optiProb);
-%d = optiProb.matRad_backProjection(w,dij);
-optiProb.BP = optiProb.BP.compute(dij,w);
-d = optiProb.BP.GetResult();
-
-% initialize jacobian (only single scenario supported in optimization)
-jacob = sparse([]);
-
-% initialize projection matrices and id containers
-DoseProjection{1} = sparse([]);
-mAlphaDoseProjection{1} = sparse([]);
-mSqrtBetaDoseProjection{1} = sparse([]);
-voxelID = [];
-constraintID = [];
-scenID2 = [];
-
-% compute objective function for every VOI.
-for i = 1:size(cst,1)
-
- % Only take OAR or target VOI.
- if ~isempty(cst{i,4}{1}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )
-
-
- % loop over the number of constraints for the current VOI
- for j = 1:numel(cst{i,6})
-
- obj = cst{i,6}{j}; %Get the Optimization Object
-
- % only perform computations for constraints
- %if ~isempty(strfind(obj.type,'constraint'))
- if isa(obj,'DoseConstraints.matRad_DoseConstraint')
-
- % compute reference
- if (~isequal(obj.name, 'max dose constraint') && ~isequal(obj.name, 'min dose constraint') &&...
- ~isequal(obj.name, 'max mean dose constraint') && ~isequal(obj.name, 'min mean dose constraint') && ...
- ~isequal(obj.name, 'min EUD constraint') && ~isequal(obj.name, 'max EUD constraint')) && ...
- (isa(optiProb.BP,'matRad_EffectProjection') && ~isa(optiProb.BP,'matRad_VariableRBEProjection'))
-
- doses = obj.getDoseParameters();
-
- effect = cst{i,5}.alphaX*doses + cst{i,5}.betaX*doses.^2;
-
- obj = obj.setDoseParameters(effect);
- end
-
- % if conventional opt: just add constraints of nominal dose
- %if strcmp(cst{i,6}{j}.robustness,'none')
-
- d_i = d{1}(cst{i,4}{1});
-
- %jacobVec = matRad_jacobFunc(d_i,cst{i,6}{j},d_ref);
- jacobSub = obj.computeDoseConstraintJacobian(d_i);
-
- nConst = size(jacobSub,2);
-
- %tic;
-
- %Iterate through columns of the sub-jacobian
- %TODO: Maybe this could all be function of the projection
- %Objects???
- if isa(optiProb.BP,'matRad_DoseProjection') && ~isempty(jacobSub) || isa(optiProb.BP,'matRad_ConstantRBEProjection')
-
- startIx = size(DoseProjection{1},2) + 1;
- %First append the Projection matrix with sparse zeros
- DoseProjection{1} = [DoseProjection{1},sparse(dij.doseGrid.numOfVoxels,nConst)];
-
- %Now directly write the jacobian in there
- DoseProjection{1}(cst{i,4}{1},startIx:end) = jacobSub;
-
- elseif isa(optiProb.BP,'matRad_EffectProjection') && ~isempty(jacobSub)
-
- if isa(optiProb.BP,'matRad_VariableRBEProjection')
- scaledEffect = (dij.gamma(cst{i,4}{1}) + d_i);
- jacobSub = jacobSub./(2*dij.bx(cst{i,4}{1}) .* scaledEffect);
- end
-
- startIx = size(mAlphaDoseProjection{1},2) + 1;
-
- %First append the alphaDose matrix with sparse
- %zeros then insert
- mAlphaDoseProjection{1} = [mAlphaDoseProjection{1},sparse(dij.doseGrid.numOfVoxels,nConst)];
- mAlphaDoseProjection{1}(cst{i,4}{1},startIx:end) = jacobSub;
-
- %The betadose has a different structure due to the
- %quadratic transformation, but in principle the
- %same as above
- mSqrtBetaDoseProjection{1} = [mSqrtBetaDoseProjection{1}, sparse(repmat(cst{i,4}{1},nConst,1),repmat(1:numel(cst{i,4}{1}),1,nConst),2*reshape(jacobSub',[],1),dij.doseGrid.numOfVoxels,nConst*numel(cst{i,4}{1}))];
-
- if isempty(constraintID)
- newID = 1;
- else
- newID = constraintID(end)+1;
- end
-
- voxelID = [voxelID;repmat(cst{i,4}{1},nConst,1)]; %Keep track of voxels for organizing the sqrt(beta)Dose projection later
- constraintID = [constraintID, ...
- reshape(ones(numel(cst{i,4}{1}),1)*[newID:newID+nConst-1],[1 nConst*numel(cst{i,4}{1})])]; %Keep track of constraints for organizing the sqrt(beta)Dose projection later
-
- end
-
-
- %Old implementation with for loop
- %{
- for c = 1:size(jacobSub,2)
- jacobVec = jacobSub(:,c);
-
- if isa(optiProb.BP,'matRad_DoseProjection') && ~isempty(jacobVec) || isa(optiProb.BP,'matRad_ConstantRBEProjection')
-
- DoseProjection{1} = [DoseProjection{1},sparse(cst{i,4}{1},1,jacobVec,dij.doseGrid.numOfVoxels,1)];
-
- elseif isa(optiProb.BP,'matRad_EffectProjection') && ~isempty(jacobVec)
-
- if isa(optiProb.BP,'matRad_VariableRBEProjection')
- scaledEffect = (dij.gamma(cst{i,4}{1}) + d_i);
- jacobVec = jacobVec./(2*dij.bx(cst{i,4}{1}).*scaledEffect);
- end
-
- mAlphaDoseProjection{1} = [mAlphaDoseProjection{1},sparse(cst{i,4}{1},1,jacobVec,dij.doseGrid.numOfVoxels,1)];
- mSqrtBetaDoseProjection{1} = [mSqrtBetaDoseProjection{1},...
- sparse(cst{i,4}{1},1:numel(cst{i,4}{1}),2*jacobVec,dij.doseGrid.numOfVoxels,numel(cst{i,4}{1}))];
-
- voxelID = [voxelID ;cst{i,4}{1}]; %list of voxels relevant for constraints to enable faster computations
-
- if isempty(constraintID)
- lastID = 0;
- else
- lastID = constraintID(end);
- end
- constraintID = [constraintID, repmat(1 + lastID,1,numel(cst{i,4}{1}))]; %Maps constraints to voxels
-
- end
- end
- %}
- end
-
- end
-
- end
-
-end
-
-% enter if statement also for protons using a constant RBE
-if isa(optiProb.BP,'matRad_DoseProjection')
-
- if ~isempty(DoseProjection{1})
- jacob = DoseProjection{1}' * dij.physicalDose{1};
- end
-
-elseif isa(optiProb.BP,'matRad_ConstantRBEProjection')
-
- if ~isempty(DoseProjection{1})
- jacob = DoseProjection{1}' * dij.RBE * dij.physicalDose{1};
- end
-
-elseif isa(optiProb.BP,'matRad_EffectProjection')
-
- if ~isempty(mSqrtBetaDoseProjection{1}) && ~isempty(mAlphaDoseProjection{1})
- mSqrtBetaDoseProjection{1} = mSqrtBetaDoseProjection{1}' * dij.mSqrtBetaDose{1} * w;
- mSqrtBetaDoseProjection{1} = sparse(voxelID,constraintID,mSqrtBetaDoseProjection{1},...
- size(mAlphaDoseProjection{1},1),size(mAlphaDoseProjection{1},2));
-
- jacob = mAlphaDoseProjection{1}' * dij.mAlphaDose{1} +...
- mSqrtBetaDoseProjection{1}' * dij.mSqrtBetaDose{1};
-
- end
-end
-end
diff --git a/optimization/@matRad_OptimizationProblem/matRad_objectiveFunction.m b/optimization/@matRad_OptimizationProblem/matRad_objectiveFunction.m
deleted file mode 100644
index a8ef54158..000000000
--- a/optimization/@matRad_OptimizationProblem/matRad_objectiveFunction.m
+++ /dev/null
@@ -1,82 +0,0 @@
-function f = matRad_objectiveFunction(optiProb,w,dij,cst)
-% matRad IPOPT objective function wrapper
-%
-% call
-% f = matRad_objectiveFuncWrapper(optiProb,w,dij,cst)
-%
-% input
-% optiProb: matRad optimization problem
-% w: beamlet/ pencil beam weight vector
-% dij: matRad dose influence struct
-% cst: matRad cst struct
-%
-% output
-% f: objective function value
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2016 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% get current dose / effect / RBExDose vector
-%d = optiProb.matRad_backProjection(w,dij);
-optiProb.BP = optiProb.BP.compute(dij,w);
-d = optiProb.BP.GetResult();
-
-
-% Initialize f
-f = 0;
-
-% compute objectiveective function for every VOI.
-for i = 1:size(cst,1)
-
- % Only take OAR or target VOI.
- if ~isempty(cst{i,4}{1}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )
-
- % loop over the number of constraints for the current VOI
- for j = 1:numel(cst{i,6})
-
- objective = cst{i,6}{j};
-
- % only perform gradient computations for objectiveectives
- %if isempty(strfind(objective.type,'constraint'))
- if isa(objective,'DoseObjectives.matRad_DoseObjective')
-
- % if we have effect optimization, temporarily replace doses with effect
- if (~isequal(objective.name, 'Mean Dose') && ~isequal(objective.name, 'EUD')) &&...
- (isa(optiProb.BP,'matRad_EffectProjection') && ~isa(optiProb.BP,'matRad_VariableRBEProjection'))
-
- doses = objective.getDoseParameters();
-
- effect = cst{i,5}.alphaX*doses + cst{i,5}.betaX*doses.^2;
-
- objective = objective.setDoseParameters(effect);
- end
-
- % if conventional opt: just sum objectiveectives of nominal dose
- %if strcmp(cst{i,6}{j}.robustness,'none')
-
- d_i = d{1}(cst{i,4}{1});
-
- f = f + objective.computeDoseObjectiveFunction(d_i);
-
- %end
-
- end
-
- end
-
- end
-
-end
diff --git a/optimization/@matRad_OptimizationProblem/matRad_objectiveGradient.m b/optimization/@matRad_OptimizationProblem/matRad_objectiveGradient.m
deleted file mode 100644
index da4d95a1b..000000000
--- a/optimization/@matRad_OptimizationProblem/matRad_objectiveGradient.m
+++ /dev/null
@@ -1,85 +0,0 @@
-function weightGradient = matRad_objectiveGradient(optiProb,w,dij,cst)
-% matRad IPOPT callback: gradient function for inverse planning
-% supporting mean dose objectives, EUD objectives, squared overdosage,
-% squared underdosage, squared deviation and DVH objectives
-%
-% call
-% g = matRad_gradFuncWrapper(optiProb,w,dij,cst)
-%
-% input
-% optiProb: option struct defining the type of optimization
-% w: bixel weight vector
-% dij: dose influence matrix
-% cst: matRad cst struct
-%
-% output
-% g: gradient of objective function
-%
-% References
-% [1] http://www.sciencedirect.com/science/article/pii/S0958394701000577
-% [2] http://www.sciencedirect.com/science/article/pii/S0360301601025858
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2016 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% get current dose / effect / RBExDose vector
-%d = matRad_backProjection(w,dij,optiProb);
-optiProb.BP = optiProb.BP.compute(dij,w);
-d = optiProb.BP.GetResult();
-
-% Initializes dose gradient
-doseGradient{1} = zeros(dij.doseGrid.numOfVoxels,1);
-
-% compute objective function for every VOI.
-for i = 1:size(cst,1)
-
- % Only take OAR or target VOI.
- if ~isempty(cst{i,4}{1}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )
-
- % loop over the number of constraints and objectives for the current VOI
- for j = 1:numel(cst{i,6})
-
- %Get current optimization function
- objective = cst{i,6}{j};
-
- % only perform gradient computations for objectives
- if isa(objective,'DoseObjectives.matRad_DoseObjective')
- % if we have effect optimization, temporarily replace doses with effect
- if (~isequal(objective.name, 'Mean Dose') && ~isequal(objective.name, 'EUD')) &&...
- (isa(optiProb.BP,'matRad_EffectProjection') && ~isa(optiProb.BP,'matRad_VariableRBEProjection'))
-
- doses = objective.getDoseParameters();
-
- effect = cst{i,5}.alphaX*doses + cst{i,5}.betaX*doses.^2;
-
- objective = objective.setDoseParameters(effect);
- end
-
- %dose in VOI
- d_i = d{1}(cst{i,4}{1});
-
- %add to dose gradient
- doseGradient{1}(cst{i,4}{1}) = doseGradient{1}(cst{i,4}{1}) + objective.computeDoseObjectiveGradient(d_i);
- end
- end
- end
-end
-
-%project to weight gradient
-optiProb.BP = optiProb.BP.computeGradient(dij,doseGradient,w);
-g = optiProb.BP.GetGradient();
-weightGradient = g{1};
-
-end
diff --git a/optimization/projections/matRad_BackProjection.m b/optimization/projections/matRad_BackProjection.m
deleted file mode 100644
index 7ce56b5eb..000000000
--- a/optimization/projections/matRad_BackProjection.m
+++ /dev/null
@@ -1,83 +0,0 @@
-classdef matRad_BackProjection
-% matRad_BackProjection superclass for all backprojection algorithms
-% used within matRad optimzation processes
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
- properties (Access = protected)
- wCache
- wGradCache %different cache for optimal performance (if multiple evaluations of objective but not gradient are required)
- d
- wGrad
- end
-
- properties
- dij %reference to matRad dij struct (to enable local changes)
- end
-
-
- methods
- function obj = matRad_BackProjection()
- obj.wCache = [];
- obj.wGradCache = [];
- obj.d = [];
- obj.wGrad = [];
- end
-
- function obj = compute(obj,dij,w)
- if ~isequal(obj.wCache,w)
- obj.d = obj.computeResult(dij,w);
- obj.wCache = w;
- end
- end
-
- function obj = computeGradient(obj,dij,doseGrad,w)
- if ~isequal(obj.wGradCache,w)
- obj.wGrad = obj.projectGradient(dij,doseGrad,w);
- obj.wGradCache = w;
- end
- end
-
- function d = GetResult(obj)
- d = obj.d;
- end
-
- function wGrad = GetGradient(obj)
- wGrad = obj.wGrad;
- end
-
- function d = computeResult(obj,dij,w)
- d = cell(size(dij.physicalDose));
- d = arrayfun(@(scen) computeSingleScenario(obj,dij,scen,w),ones(size(dij.physicalDose)),'UniformOutput',false);
- end
-
- function wGrad = projectGradient(obj,dij,doseGrad,w)
- wGrad = cell(size(dij.physicalDose));
- wGrad = arrayfun(@(scen) projectSingleScenarioGradient(obj,dij,doseGrad,scen,w),ones(size(dij.physicalDose)),'UniformOutput',false);
- end
- end
-
- %These should be abstract methods, however Octave can't parse them. As soon
- %as Octave is able to do this, they should be made abstract again
- methods %(Abstract)
- function d = computeSingleScenario(obj,dij,scen,w)
- error('Function needs to be implemented');
- end
-
- function wGrad = projectSingleScenarioGradient(obj,dij,doseGrad,scen,w)
- error('Function needs to be implemented');
- end
- end
-end
-
diff --git a/optimization/projections/matRad_EffectProjection.m b/optimization/projections/matRad_EffectProjection.m
deleted file mode 100644
index 329280329..000000000
--- a/optimization/projections/matRad_EffectProjection.m
+++ /dev/null
@@ -1,46 +0,0 @@
-classdef matRad_EffectProjection < matRad_BackProjection
-% matRad_EffectProjection class for effect-based optimization
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
- methods
- function obj = matRad_EffectProjection()
- end
- end
-
- methods
- function effect = computeSingleScenario(~,dij,scen,w)
- if isempty(dij.mAlphaDose{scen}) || isempty(dij.mSqrtBetaDose{scen})
- effect = [];
- matRad_cfg = MatRad_Config.instance();
- matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
- else
- effect = dij.mAlphaDose{scen}*w + (dij.mSqrtBetaDose{scen}*w).^2;
- end
- end
-
- function wGrad = projectSingleScenarioGradient(~,dij,doseGrad,scen,w)
- if isempty(dij.mAlphaDose{scen}) || isempty(dij.mSqrtBetaDose{scen})
- wGrad = [];
- matRad_cfg = MatRad_Config.instance();
- matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
- else
- vBias = (doseGrad{scen}' * dij.mAlphaDose{scen})';
- quadTerm = dij.mSqrtBetaDose{scen} * w;
- mPsi = (2*(doseGrad{scen}.*quadTerm)' * dij.mSqrtBetaDose{scen})';
- wGrad = vBias + mPsi;
- end
- end
- end
-end
diff --git a/optimization/projections/matRad_VariableRBEProjection.m b/optimization/projections/matRad_VariableRBEProjection.m
deleted file mode 100644
index 4926dc8bb..000000000
--- a/optimization/projections/matRad_VariableRBEProjection.m
+++ /dev/null
@@ -1,51 +0,0 @@
-classdef matRad_VariableRBEProjection < matRad_EffectProjection
-% matRad_VariableRBEProjection class for RBE-weighted dose optimization
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2019 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
- methods
- function obj = matRad_VariableRBEProjection()
- end
-
- function RBExD = computeSingleScenario(obj,dij,scen,w)
- effect = computeSingleScenario@matRad_EffectProjection(obj,dij,scen,w); %First compute effect
- RBExD = zeros(dij.doseGrid.numOfVoxels,1);
- RBExD(dij.ixDose) = sqrt((effect(dij.ixDose)./dij.bx(dij.ixDose))+(dij.gamma(dij.ixDose).^2)) - dij.gamma(dij.ixDose);
- end
-
- function wGrad = projectSingleScenarioGradient(obj,dij,doseGrad,scen,w)
- if isempty(dij.mAlphaDose{scen}) || isempty(dij.mSqrtBetaDose{scen})
- wGrad = [];
- matRad_cfg = MatRad_Config.instance();
- matRad_cfg.dispWarning('Empty scenario in optimization detected! This should not happen...\n');
- else
- %While the dose cache should be up to date here, we ask for
- %a computation (will skip if weights are equal to cache)
- obj = obj.compute(dij,w);
-
- %Scaling vor variable RBExD
- scaledEffect = obj.d{scen} + dij.gamma;
- doseGradTmp = zeros(dij.doseGrid.numOfVoxels,1);
- doseGradTmp(dij.ixDose) = doseGrad{scen}(dij.ixDose) ./ (2*dij.bx(dij.ixDose).*scaledEffect(dij.ixDose));
-
- %Now modify the effect computation
- vBias = (doseGradTmp' * dij.mAlphaDose{scen})';
- quadTerm = dij.mSqrtBetaDose{scen} * w;
- mPsi = (2*(doseGrad{scen}.*quadTerm)' * dij.mSqrtBetaDose{scen})';
- wGrad = vBias + mPsi;
- end
- end
- end
-end
-
diff --git a/plotting/matRad_computeAllVoiSurfaces.m b/plotting/matRad_computeAllVoiSurfaces.m
deleted file mode 100644
index 07cf13276..000000000
--- a/plotting/matRad_computeAllVoiSurfaces.m
+++ /dev/null
@@ -1,75 +0,0 @@
-function cst = matRad_computeAllVoiSurfaces(ct,cst)
-% matRad function that computes all VOI surfaces
-%
-% call
-% cst = matRad_computeAllVoiSurfaces(ct,cst)
-%
-% input
-% ct matRad ct struct
-% cst matRad cst struct
-%
-% output
-% cst the new cst with the column containing the precomputed surface
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-disp('Computing 3D Surfaces...');
-
- % initialize waitbar
- figureWait = waitbar(0,'Computing 3D Surfaces...');
- % prevent closure of waitbar and show busy state
- set(figureWait,'pointer','watch');
-
- xCoord = ct.resolution.x * double(1:ct.cubeDim(2));
- yCoord = ct.resolution.y * double(1:ct.cubeDim(1));
- zCoord = ct.resolution.z * double(1:ct.cubeDim(3));
-
- [xMesh,yMesh,zMesh] = meshgrid(xCoord,yCoord,zCoord);
-
- numVois = size(cst,1);
-
-
-
- for s = 1:numVois
- mask = zeros(ct.cubeDim); % create zero cube with same dimeonsions like dose cube
- mask(cst{s,4}{1}) = 1;
-
- %Smooth the VOI
- v = smooth3(mask,'gaussian',[5 5 5],2);
- isoSurface = isosurface(xMesh,yMesh,zMesh,v,0.5);
-
- %reduce the complexity
- isoSurface = reducepatch(isoSurface,0.05);
-
- %compute isonormals
- isoNormals = isonormals(xMesh,yMesh,zMesh,v,isoSurface.vertices);
-
- cst{s,8}{1} = isoSurface;
- cst{s,8}{2} = isoNormals;
- matRad_progress(s,numVois);
- waitbar(s/numVois,figureWait);
- end
- try
- % wait 0.1s for closing all waitbars
- allWaitBarFigures = findall(0,'type','figure','tag','TMWWaitbar');
- delete(allWaitBarFigures);
- pause(0.1);
- catch
-
- end
-end
-
diff --git a/plotting/matRad_computeVoiContours.m b/plotting/matRad_computeVoiContours.m
deleted file mode 100644
index d2e2fce0a..000000000
--- a/plotting/matRad_computeVoiContours.m
+++ /dev/null
@@ -1,54 +0,0 @@
-function cst = matRad_computeVoiContours(ct,cst)
-% matRad function that computes all VOI contours
-%
-% call
-% cst = matRad_computeVoiContours(ct,cst)
-%
-% input
-% ct matRad ct struct
-% cst matRad cst struct
-%
-% output
-% cst the new cst with the column containing the precomputed contours
-%
-% References
-% -
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2015 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-matRad_cfg = MatRad_Config.instance();
-matRad_cfg.dispInfo('Precomputing Contours for Display... ');
-
-mask = zeros(ct.cubeDim); % create zero cube with same dimeonsions like dose cube
-for s = 1:size(cst,1)
- matRad_progress(s,size(cst,1));
- cst{s,7} = cell(max(ct.cubeDim(:)),3);
- mask(:) = 0;
- mask(cst{s,4}{1}) = 1;
- for slice = 1:ct.cubeDim(1)
- if any(any(mask(slice,:,:) > 0))
- cst{s,7}{slice,1} = contourc(squeeze(mask(slice,:,:)),.5*[1 1]);
- end
- end
- for slice = 1:ct.cubeDim(2)
- if any(any(mask(:,slice,:) > 0))
- cst{s,7}{slice,2} = contourc(squeeze(mask(:,slice,:)),.5*[1 1]);
- end
- end
- for slice = 1:ct.cubeDim(3)
- if any(any(mask(:,:,slice) > 0))
- cst{s,7}{slice,3} = contourc(squeeze(mask(:,:,slice)),.5*[1 1]);
- end
- end
-end
diff --git a/submodules/MOcov b/submodules/MOcov
new file mode 160000
index 000000000..995abc8ef
--- /dev/null
+++ b/submodules/MOcov
@@ -0,0 +1 @@
+Subproject commit 995abc8ef4766dcbc6318804bd01573901cad718
diff --git a/submodules/MOxUnit b/submodules/MOxUnit
new file mode 160000
index 000000000..db6c87fe7
--- /dev/null
+++ b/submodules/MOxUnit
@@ -0,0 +1 @@
+Subproject commit db6c87fe7379d5a5d727feed760c38f3c533b339
diff --git a/submodules/latexTable b/submodules/latexTable
new file mode 160000
index 000000000..e4236fbb2
--- /dev/null
+++ b/submodules/latexTable
@@ -0,0 +1 @@
+Subproject commit e4236fbb20f6f9b8bbe5b363072d98fa648cc5fc
diff --git a/submodules/matlab2tikz b/submodules/matlab2tikz
new file mode 160000
index 000000000..816f87548
--- /dev/null
+++ b/submodules/matlab2tikz
@@ -0,0 +1 @@
+Subproject commit 816f8754804cd45d8b41b3adf3ff9709a29cf173
diff --git a/test/IOfuncs/test_io_mha.m b/test/IOfuncs/test_io_mha.m
new file mode 100644
index 000000000..60d3aae0e
--- /dev/null
+++ b/test/IOfuncs/test_io_mha.m
@@ -0,0 +1,72 @@
+function test_suite = test_io_mha
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_mhd_basic_writeReadCube
+ load TG119.mat
+
+ tmpDir = helper_temporaryFolder('matRad_test_mha');
+ tmpFilePath = fullfile(tmpDir,'TG119.mhd');
+
+ datatype = 'double';
+
+ metadata = struct;
+ metadata.resolution = ct.resolution;
+
+ %Check the write
+ matRad_writeCube(tmpFilePath,ct.cubeHU{1},datatype,metadata);
+ assertEqual(exist(tmpFilePath,'file'),2);
+
+ %check the read
+ [readCube,readMeta] = matRad_readCube(tmpFilePath);
+
+ assertElementsAlmostEqual(ct.cubeHU{1},readCube);
+ assertEqual(readMeta.resolution,[ct.resolution.x ct.resolution.y ct.resolution.z]);
+ assertEqual(readMeta.cubeDim,ct.cubeDim);
+
+function test_mha_basic_writeReadCube
+ load TG119.mat
+
+ tmpDir = helper_temporaryFolder('matRad_test_mha');
+ tmpFilePath = fullfile(tmpDir,'TG119.mha');
+
+ datatype = 'double';
+
+ metadata = struct;
+ metadata.resolution = ct.resolution;
+
+ %Check the write
+ matRad_writeCube(tmpFilePath,ct.cubeHU{1},datatype,metadata);
+ assertEqual(exist(tmpFilePath,'file'),2);
+
+ %check the read
+ [readCube,readMeta] = matRad_readCube(tmpFilePath);
+
+ assertElementsAlmostEqual(ct.cubeHU{1},readCube);
+ assertEqual(readMeta.resolution,[ct.resolution.x ct.resolution.y ct.resolution.z]);
+ assertEqual(readMeta.cubeDim,ct.cubeDim);
+
diff --git a/test/IOfuncs/test_io_niftii.m b/test/IOfuncs/test_io_niftii.m
new file mode 100644
index 000000000..292df4256
--- /dev/null
+++ b/test/IOfuncs/test_io_niftii.m
@@ -0,0 +1,84 @@
+function test_suite = test_io_niftii
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_niftii_basic_writeReadCube
+ if moxunit_util_platform_is_octave
+ moxunit_throw_test_skipped_exception('niftiread not available for Octave!');
+ end
+
+ load TG119.mat
+
+ tmpDir = helper_temporaryFolder('matRad_test_nii');
+ tmpFilePath = fullfile(tmpDir,'TG119.nii');
+
+ datatype = 'double';
+
+ metadata = struct;
+ metadata.resolution = ct.resolution;
+ metadata.compress = false;
+
+ %Check the write
+ matRad_writeCube(tmpFilePath,ct.cubeHU{1},datatype,metadata);
+ assertEqual(exist(tmpFilePath,'file'),2);
+
+ %check the read
+ [readCube,readMeta] = matRad_readCube(tmpFilePath);
+
+ assertElementsAlmostEqual(ct.cubeHU{1},readCube);
+ assertEqual(readMeta.resolution,[ct.resolution.x ct.resolution.y ct.resolution.z]);
+ assertEqual(readMeta.cubeDim,ct.cubeDim);
+
+function test_niftii_compressed_writeReadCube
+ if moxunit_util_platform_is_octave
+ moxunit_throw_test_skipped_exception('niftiread not available for Octave!');
+ end
+
+ load TG119.mat
+
+ tmpDir = helper_temporaryFolder('matRad_test_nii');
+ tmpFilePath = fullfile(tmpDir,'TG119_compressed.nii');
+
+ datatype = 'double';
+
+ metadata = struct;
+ metadata.resolution = ct.resolution;
+ metadata.compress = true;
+
+ %Check the write
+ matRad_writeCube(tmpFilePath,ct.cubeHU{1},datatype,metadata);
+
+ assertEqual(exist([tmpFilePath '.gz'],'file'),2);
+
+ %check the read
+ [readCube,readMeta] = matRad_readCube([tmpFilePath '.gz']);
+
+ assertElementsAlmostEqual(ct.cubeHU{1},readCube);
+ assertEqual(readMeta.resolution,[ct.resolution.x ct.resolution.y ct.resolution.z]);
+ assertEqual(readMeta.cubeDim,ct.cubeDim);
+
+
diff --git a/test/IOfuncs/test_io_nrrd.m b/test/IOfuncs/test_io_nrrd.m
new file mode 100644
index 000000000..1f61aa8ad
--- /dev/null
+++ b/test/IOfuncs/test_io_nrrd.m
@@ -0,0 +1,51 @@
+function test_suite = test_io_nrrd
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_nrrd_basic_writeReadCube
+ load TG119.mat
+
+ tmpDir = helper_temporaryFolder('matRad_test_nrrd');
+ tmpFilePath = fullfile(tmpDir,'TG119.nrrd');
+
+ datatype = 'double';
+
+ metadata = struct;
+ metadata.resolution = ct.resolution;
+
+ %Check the write
+ matRad_writeCube(tmpFilePath,ct.cubeHU{1},datatype,metadata);
+ assertEqual(exist(tmpFilePath,'file'),2);
+
+ %check the read
+ [readCube,readMeta] = matRad_readCube(tmpFilePath);
+
+ assertElementsAlmostEqual(ct.cubeHU{1},readCube);
+ assertEqual(readMeta.resolution,[ct.resolution.x ct.resolution.y ct.resolution.z]);
+ assertEqual(readMeta.cubeDim,ct.cubeDim);
+
+
diff --git a/test/IOfuncs/test_io_vtk.m b/test/IOfuncs/test_io_vtk.m
new file mode 100644
index 000000000..f99bed54e
--- /dev/null
+++ b/test/IOfuncs/test_io_vtk.m
@@ -0,0 +1,44 @@
+function test_suite = test_io_vtk
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_vtk_basic_write
+ load TG119.mat
+
+ tmpDir = helper_temporaryFolder('matRad_test_vtk');
+ tmpFilePath = fullfile(tmpDir,'TG119.vtk');
+
+ datatype = 'double';
+
+ metadata = struct;
+ metadata.resolution = ct.resolution;
+
+ %Check the write
+ matRad_writeCube(tmpFilePath,ct.cubeHU{1},datatype,metadata);
+ assertEqual(exist(tmpFilePath,'file'),2);
+
+
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 000000000..ee4089bdc
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,58 @@
+## matRad Tests
+matRad tests run with [MOxUnit](https://github.com/MOxUnit/MOxUnit). Coverage is generated with [MOcov](https://github.com/MOcov/MOcov).
+
+matRad runs through (most of the) examples (each one counting as one test) on top of defined unitTests using MOxUnits Syntax.
+Multiple Matlab versions as well as Octave are tested, all running on Ubuntu.
+
+### Testing Locally
+1. As MOxUnit and MOcov are integrated as *submodules* in the submodules/ folder of the repository, make sure your submodules are initialized and up to date. Many git tools do that automatically on cloning a repository, but you can do this manually by running `git submodule update --init` in the repository root.
+
+ If you didn't clone the repository and are working with the downloaded source, you can do the following:
+ 1. Clone MOxUnit into a *separate* folder (not within or in subfolders of the matRad root dir).
+ 2. Navigate into the MOxUnit code folder (within the MOxUnit) and call `moxunit_set_path`.
+ 3. Navigate back into the matRad root folder.
+2. Run tests on matRads test folder by calling `matRad_runTests(folder,withCoverage)` from the root folder. The arguments `folder` and `withCoverage` are optional. If `folder` is not given, it will default to `test`. A specific set of tests can be run, then, for example, by calling `matRad_runTests('test/tools')` to run tests in the `test/tools` folder.
+
+### Writing Tests
+Here's how a basic Test File can look like, based on [test_version.m](tools/test_version.m)
+```matlab
+function test_suite = test_version
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_matrad_version
+ assertTrue(ischar(matRad_version()));
+ [str,full] = matRad_version();
+ assertTrue(isstruct(full));
+ assertTrue(ischar(str));
+
+function test_matrad_environment
+ env = matRad_getEnvironment();
+ assertTrue(ischar(env));
+ [~,verstr] = matRad_getEnvironment();
+ assertTrue(ischar(verstr));
+```
diff --git a/test/autoExampleTest/test_examples.m b/test/autoExampleTest/test_examples.m
new file mode 100644
index 000000000..378ef7b56
--- /dev/null
+++ b/test/autoExampleTest/test_examples.m
@@ -0,0 +1,106 @@
+function test_suite = test_examples
+
+matRad_cfg = MatRad_Config.instance();
+
+% supressing the inherent Ocatave warnings for division by zero
+if matRad_cfg.isOctave
+ warning('off','Octave:divide-by-zero');
+end
+
+% Define Scripts relative to root folder
+
+exampleScripts = {'examples/matRad_example1_phantom.m',...
+ 'examples/matRad_example2_photons.m',...
+ 'examples/matRad_example3_photonsDAO.m',...
+ 'examples/matRad_example4_photonsMC.m',...
+ 'examples/matRad_example5_protons.m',...
+ 'examples/matRad_example6_protonsNoise.m',...
+ 'examples/matRad_example7_carbon.m',...
+ 'examples/matRad_example8_protonsRobust.m',...
+ 'examples/matRad_example9_4DDoseCalcMinimal.m',...
+ 'examples/matRad_example10_4DphotonRobust.m',...
+ 'examples/matRad_example11_helium.m',...
+ 'examples/matRad_example12_simpleParticleMonteCarlo.m',...
+ 'examples/matRad_example15_brachy.m',...
+ 'matRad.m',...
+ };
+
+%exampleScripts = {'matRad.m'}; %Uncomment to fast-test the example testing workflow
+
+exampleScripts = cellfun(@(script) fullfile(matRad_cfg.matRadRoot,script),exampleScripts,'UniformOutput',false);
+
+
+testing_prefix = 'tmptest_';
+
+% Some parameters to reduce computational overhead during testing
+unitTestBixelWidth = 20;
+unitTestSpotSpacing = matRad_cfg.defaults.propStf.longitudinalSpotSpacing;
+unitTestResolution = matRad_cfg.defaults.propDoseCalc.doseGrid.resolution;
+
+%% Copy and manipulate all scripts
+[folders,names,exts] = cellfun(@fileparts,exampleScripts,'UniformOutput',false);
+
+%Create temporary example test folder
+tmpExampleTestFolder = helper_temporaryFolder('exampleTest',true);
+addpath(tmpExampleTestFolder);
+newFolders = cell(size(folders));
+[newFolders{:}] = deal(tmpExampleTestFolder);
+
+%Copy scripts
+testScriptNames = strcat(testing_prefix,names);
+testScriptFiles = strcat(testScriptNames,exts);
+testScripts = cellfun(@fullfile,newFolders,testScriptFiles,'UniformOutput',false);
+
+status = cellfun(@copyfile,exampleScripts,testScripts);
+
+matRad_unitTestTextManipulation(testScriptFiles,'pln.propStf.bixelWidth',['pln.propStf.bixelWidth = ' num2str(unitTestBixelWidth) ';'],tmpExampleTestFolder);
+matRad_unitTestTextManipulation(testScriptFiles,'pln.propStf.longitudinalSpotSpacing',['pln.propStf.longitudinalSpotSpacing = ' num2str(unitTestSpotSpacing) ';'],tmpExampleTestFolder);
+matRad_unitTestTextManipulation(testScriptFiles,'pln.propDoseCalc.doseGrid.resolution.x',['pln.propDoseCalc.doseGrid.resolution.x = ' num2str(unitTestResolution.x) ';'],tmpExampleTestFolder);
+matRad_unitTestTextManipulation(testScriptFiles,'pln.propDoseCalc.doseGrid.resolution.y',['pln.propDoseCalc.doseGrid.resolution.y = ' num2str(unitTestResolution.y) ';'],tmpExampleTestFolder);
+matRad_unitTestTextManipulation(testScriptFiles,'pln.propDoseCalc.doseGrid.resolution.z',['pln.propDoseCalc.doseGrid.resolution.z = ' num2str(unitTestResolution.z) ';'],tmpExampleTestFolder);
+matRad_unitTestTextManipulation(testScriptFiles,'display(','%%%%%%%%%%%%%%% REMOVED DISPLAY FOR TESTING %%%%%%%%%%%%%%',tmpExampleTestFolder);
+
+%initTestSuite;
+%We need to manually set up the test_suite to bypass the automatic function
+%assignment
+test_suite=MOxUnitTestSuite();
+
+for testIx = 1:length(testScriptNames)
+ currScriptName = testScriptNames{testIx};
+ testfun = @() runSingleExampleTest(currScriptName,testScripts{testIx}); %Test is evaluated in the base workspace and clears new variables after that
+
+ test_case=MOxUnitFunctionHandleTestCase(...
+ names{testIx},...
+ mfilename, testfun);
+ test_suite=addTest(test_suite, test_case);
+ %test_functions{testIx,1} = testfun;
+end
+
+%initTestSuite;
+%We need to manually set up the test_suite
+
+function runSingleExampleTest(exampleName,path)
+%We use a kind of fishy way to run the test in the base workspace
+%First we record the variables we have in the base workspace
+baseVars = evalin('base','who');
+
+%Example is evaluated in the base workspace
+%addpath(fileparts(path));
+try
+ evalin('base',exampleName);
+
+ %Clean up of the base workspace by cleaning all new variables
+ afterTestVars = evalin('base','who');
+ newVars = setdiff(afterTestVars,baseVars);
+ evalin('base',['clear ' strjoin(newVars)]);
+ close all;
+catch ME
+ %Also clean up the base workspace by cleaning all new variables
+ afterTestVars = evalin('base','who');
+ newVars = setdiff(afterTestVars,baseVars);
+ evalin('base',['clear ' strjoin(newVars)]);
+ close all;
+
+ %Now rethrow
+ rethrow(ME);
+end
diff --git a/test/bioModel/test_biologicalModel.m b/test/bioModel/test_biologicalModel.m
new file mode 100644
index 000000000..509a478cd
--- /dev/null
+++ b/test/bioModel/test_biologicalModel.m
@@ -0,0 +1,129 @@
+function test_suite = test_biologicalModel
+
+test_functions = localfunctions();
+
+initTestSuite;
+
+function test_bioModelConstructor
+ if moxunit_util_platform_is_octave()
+ assertExceptionThrown(@() matRad_BiologicalModel());
+ assertExceptionThrown(@() matRad_BiologicalModel('photons'));
+ else
+ assertExceptionThrown(@() matRad_BiologicalModel(),'MATLAB:minrhs');
+ assertExceptionThrown(@() matRad_BiologicalModel('photons'),'MATLAB:minrhs');
+ end
+
+
+function test_setBiologicalModel
+ bioModel = matRad_BiologicalModel('photons','RBExD','LEM');
+ assertTrue(strcmp(bioModel.model, 'none')); %default photon model
+ assertTrue(strcmp(bioModel.quantityOpt, 'physicalDose'));
+ bioModel = matRad_BiologicalModel('protons','RBExD','LEM');
+ assertTrue(strcmp(bioModel.model, 'constRBE')); %default proton model
+ bioModel = matRad_BiologicalModel('carbon','effect','MCN');
+ assertTrue(strcmp(bioModel.model, 'LEM')); %default carbon model
+ assertTrue(strcmp(bioModel.quantityOpt, 'effect'));
+ bioModel = matRad_BiologicalModel('protons','BED','LEM');
+ assertTrue(strcmp(bioModel.model, 'MCN')); %default proton model for BED
+ assertTrue(strcmp(bioModel.quantityOpt, 'BED'));
+
+
+function test_calcLQParameter
+ bioModel = matRad_BiologicalModel('photons','RBExD','LEM');
+ if moxunit_util_platform_is_octave()
+ assertExceptionThrown(@() bioModel.calcLQParameter());
+ assertExceptionThrown(@() bioModel.calcLQParameterForKernel());
+ else
+ assertExceptionThrown(@() bioModel.calcLQParameter(),'MATLAB:minrhs');
+ assertExceptionThrown(@() bioModel.calcLQParameterForKernel(),'MATLAB:minrhs');
+ end
+
+
+function test_calcLQParameterForKernel_protonsMCN
+ bioModel = matRad_BiologicalModel('protons','RBExD','MCN');
+ kernels.LET = 0.9810;
+
+ bixel.energyIx = 11;
+ load protons_Generic.mat;
+ bixel.baseData = machine.data(11);
+ bixel.radDepths = 0;
+ bixel.vAlphaX = 0.5;
+ bixel.vBetaX = 0.05;
+ bixel.vTissueIndex = 1;
+
+ test_bixelAlpha = 0.5170;
+ test_bixelBeta = 0.0593;
+
+ [bixelAlpha,bixelBeta] = calcLQParameterForKernel(bioModel,bixel,kernels);
+ assertTrue(isnumeric(bixelBeta));
+ assertTrue(isnumeric(bixelAlpha));
+ assertElementsAlmostEqual(bixelAlpha,test_bixelAlpha,'absolute', 1e-4);
+ assertElementsAlmostEqual(bixelBeta, test_bixelBeta,'absolute', 1e-4);
+
+
+function test_calcLQParameterForKernel_protonsWED
+ bioModel = matRad_BiologicalModel('protons','RBExD','WED');
+ kernels.LET = 0.9810;
+
+ bixel.energyIx = 11;
+ load protons_Generic.mat;
+ bixel.baseData = machine.data(11);
+ bixel.radDepths = 0;
+ bixel.vAlphaX = 0.5;
+ bixel.vBetaX = 0.05;
+ bixel.vTissueIndex = 1;
+
+ test_bixelAlpha = 0.5213;
+ test_bixelBeta = 0.0500;
+
+ [bixelAlpha,bixelBeta] = calcLQParameterForKernel(bioModel,bixel,kernels);
+
+ assertTrue(isnumeric(bixelBeta));
+ assertTrue(isnumeric(bixelAlpha));
+ assertElementsAlmostEqual(bixelAlpha,test_bixelAlpha,'absolute', 1e-4);
+ assertElementsAlmostEqual(bixelBeta, test_bixelBeta,'absolute', 1e-4);
+
+function test_calcLQParameterForKernel_heliumHEL
+ bioModel = matRad_BiologicalModel('helium','RBExD','HEL');
+ kernels.LET = 4.1914;
+
+ bixel.energyIx = 11;
+ load helium_Generic.mat;
+ bixel.baseData = machine.data(11);
+ bixel.radDepths = 0.05;
+ bixel.vAlphaX = 0.5;
+ bixel.vBetaX = 0.05;
+ bixel.vTissueIndex = 1;
+
+ test_bixelAlpha = 0.5190;
+ test_bixelBeta = 0.0500;
+
+ [bixelAlpha,bixelBeta] = calcLQParameterForKernel(bioModel,bixel,kernels);
+ assertTrue(isnumeric(bixelBeta));
+ assertTrue(isnumeric(bixelAlpha));
+ assertElementsAlmostEqual(bixelAlpha,test_bixelAlpha,'absolute', 1e-4);
+ assertElementsAlmostEqual(bixelBeta, test_bixelBeta,'absolute', 1e-4);
+
+function test_calcLQParameterForKernel_carbonLEM
+ bioModel = matRad_BiologicalModel('carbon','RBExD','LEM');
+ kernels.LET = 34.968;
+ kernels.alpha = [0.2978 0.7053];
+ kernels.beta = [0.0412 0.0443];
+
+ bixel.energyIx = 4;
+ load carbon_Generic.mat;
+ bixel.baseData = machine.data(4);
+ bixel.radDepths = 0;
+ bixel.vAlphaX = 0.5;
+ bixel.vBetaX = 0.05;
+ bixel.vTissueIndex = 1;
+
+ test_bixelAlpha = 0.2978;
+ test_bixelBeta = 0.0412;
+
+ [bixelAlpha,bixelBeta] = calcLQParameterForKernel(bioModel,bixel,kernels);
+ assertTrue(isnumeric(bixelBeta));
+ assertTrue(isnumeric(bixelAlpha));
+ assertElementsAlmostEqual(bixelAlpha,test_bixelAlpha,'absolute', 1e-4);
+ assertElementsAlmostEqual(bixelBeta, test_bixelBeta,'absolute', 1e-4);
+
diff --git a/test/brachy/data/calcDoseSimpleTestStruct.mat b/test/brachy/data/calcDoseSimpleTestStruct.mat
new file mode 100644
index 000000000..4a0b01169
Binary files /dev/null and b/test/brachy/data/calcDoseSimpleTestStruct.mat differ
diff --git a/test/brachy/data/examplePlnAndStf.mat b/test/brachy/data/examplePlnAndStf.mat
new file mode 100644
index 000000000..c5818df0a
Binary files /dev/null and b/test/brachy/data/examplePlnAndStf.mat differ
diff --git a/test/brachy/data/referenceDoseCalculation.mat b/test/brachy/data/referenceDoseCalculation.mat
new file mode 100644
index 000000000..843d140bd
Binary files /dev/null and b/test/brachy/data/referenceDoseCalculation.mat differ
diff --git a/test/brachy/matRad_gammaPassRate2D.m b/test/brachy/matRad_gammaPassRate2D.m
new file mode 100644
index 000000000..8f21ad6f9
--- /dev/null
+++ b/test/brachy/matRad_gammaPassRate2D.m
@@ -0,0 +1,77 @@
+function [gammaPassRate,gammaValues] =...
+ matRad_gammaPassRate2D(doseMes,doseCalc, grids, DTA , doseDiff, frac )
+
+% matRad_gammaPassRate2D compares two dose distributions in terms of gamma
+% analysis.
+%
+% call
+% [gammaPassRate,gammaValues] =...
+% matRad_gammaPassRate2D(doseMes,doseCalc, grids, DTA , doseDiff, frac )
+%
+% input
+% doseMes: Measured dose (reference), 3D dose distribution grid
+% doseCalc: Calculated dose (to evaluate, -"-)
+% grids: struct
+% grids.x: 3D meshgrid with x coords
+% grids.y: 3D meshgrid with y coords
+% grids.z: 3D meshgrid with z coords
+% DTA: Distance To Agreement in mm (criterion parameter)
+% doseDiff: doseDiff in % (criterion parameter)
+% frac: fraction of random dose points evaluated
+% (this is used to safe time while get a statistical result)
+%
+% output
+% 3D grid with gamma factors
+%
+% reference
+% see D. A. Low, W. B. Harms, S. Mutic, and J. A. Purdy:
+% „A technique for the quantitativeevaluation of dose distributions“,
+% Medical Physics, vol. 25, no. 5, pp. 656–661, 1998.
+% DOI:https://doi.org/10.1118/1.598
+
+
+% define and explain quantities...
+% d: distance to agreement [mm]
+d = DTA;
+
+% D: dose distance as fraction from measured dose
+D = doseDiff/100;
+
+% preallocation for grid of all gamma values
+gammaValues = zeros(size(doseCalc));
+
+% calculate gamma accordance for all measurement points at positions rm
+% with array indey rmIndex:
+
+% create random fraction
+indices = 1:numel(doseMes);
+randFrac = rand(size(indices)) < frac;
+usedIndices = indices(randFrac);
+run = 1;
+
+%run for loop
+for rmIndex = usedIndices
+ % rm: r measurement vector
+ rm = [grids.x(rmIndex), grids.y(rmIndex), grids.z(rmIndex)];
+
+ Gamma = sqrt(((grids.x-rm(1)).^2 + (grids.y-rm(2)).^2 +...
+ (grids.z-rm(3)).^2)/d^2 + (doseMes(rmIndex) - doseCalc).^2/D^2);
+
+ if exist('OCTAVE_VERSION', 'builtin') == 5
+ % Octave environment
+ gamma = min(Gamma(:));
+ else
+ % MATLAB environment
+ gamma = min(Gamma,[],'all');
+
+ end
+
+ gammaValues(rmIndex) = gamma;
+
+ run = run+1;
+end
+
+gammaPassRate = nnz(gammaValues(usedIndices)<1)/numel(usedIndices);
+
+
+end
diff --git a/test/brachy/test_calcBrachyDose.m b/test/brachy/test_calcBrachyDose.m
new file mode 100644
index 000000000..fc307660f
--- /dev/null
+++ b/test/brachy/test_calcBrachyDose.m
@@ -0,0 +1,95 @@
+function test_suite = test_calcBrachyDose
+
+test_functions =localfunctions();
+
+initTestSuite;
+
+% test if dij struct has required shape
+function test_rightOutput()
+ engine = DoseEngines.matRad_TG43BrachyEngine;
+ load PROSTATE.mat ct cst;
+ load examplePlnAndStf.mat pln stf;
+ pln.propDoseCalc.durationImplanted = Inf;
+
+
+ dij = engine.setCalcDose(ct,cst,stf);
+ assertTrue(isfield(dij,'doseGrid'));
+ assertTrue(isfield(dij,'physicalDose'));
+ assertTrue(iscell(dij.physicalDose));
+
+ clear ct cst pln;
+function test_getDistMtx
+ % check calculated distance matrix against values calculated
+ % on a spreadsheet (same folder as test) for 3x3 test point mtx
+ seedPoints.x = [2,1,2];
+ seedPoints.y = [2,3,1];
+ seedPoints.z = [1,5,1];
+ dosePoints.x = [0,1,1];
+ dosePoints.y = [0,2,3];
+ dosePoints.z = [0,1,2];
+ verifyDistanceMatrix = [3,5.9,2.4;1,4.1,1.4;1.7,3,2.45];
+ [funcDistanceMatrix,~] = matRad_getDistanceMatrix(seedPoints,dosePoints);
+
+ assertElementsAlmostEqual(funcDistanceMatrix.dist,verifyDistanceMatrix,'absolute',0.1);
+
+function test_getThetMtx
+ engine = DoseEngines.matRad_TG43BrachyEngine;
+ % check calculated angle matrix against values calculated
+ % on a spreadsheet (same folder as test) for 3x3 test point mtx
+ seedPoints.x = [2,1,2];
+ seedPoints.y = [2,3,1];
+ seedPoints.z = [1,5,1];
+ dosePoints.x = [0,1,1];
+ dosePoints.y = [0,2,3];
+ dosePoints.z = [0,1,2];
+ SeedDirection = sqrt(1/3)*ones(1,3);
+ verifyThetaMatrix = [164.2,151.4,160.5;125.3,134.4,90;70.5,125.3,61.9];
+ [DistanceMatrix,~] = matRad_getDistanceMatrix(seedPoints,dosePoints);
+ [funcThetaMatrix,~] = engine.getThetaMatrix(SeedDirection,DistanceMatrix);
+
+ assertElementsAlmostEqual(funcThetaMatrix,verifyThetaMatrix,'absolute',0.1)
+
+function test_verifyDose1D
+
+ engine = DoseEngines.matRad_TG43BrachyEngine;
+ % verify if the calculated dose fits precalculated TG43.
+ % The criterion is more than 99% gamma pass rate for 3mm and
+ % 3%. For more information on gamma analysis, see help of the
+ % matRad_gammaPassRate3D function
+
+ % as a test, 10% of the points in a 81 x 81 grid are evaluated
+ load referenceDoseCalculation refDos;
+
+ machine = refDos.TG43_1D.basedata;
+ r = refDos.coords.r;
+ grids = refDos.coords.grids;
+
+ doseCal = engine.getDoseRate1D_poly(machine,r);
+ doseRef = refDos.TG43_1D.fullDose;
+
+ [gammaPassRate,~] = ...
+ matRad_gammaPassRate2D(doseRef,doseCal, grids, 3 , 3 , 0.1);
+
+ assertTrue(gammaPassRate > 0.99)
+
+function test_verifyDose2D
+
+ engine = DoseEngines.matRad_TG43BrachyEngine;
+ load referenceDoseCalculation refDos;
+
+ machine = refDos.TG43_2D.basedata;
+ grids = refDos.coords.grids;
+ r = refDos.coords.r;
+ theta = refDos.coords.theta;
+
+ doseCal = engine.getDoseRate2D_poly(machine,r,theta);
+ doseRef = refDos.TG43_2D.fullDose;
+
+ [gammaPassRate,~] = ...
+ matRad_gammaPassRate2D(doseRef,doseCal, grids, 3 , 3 , 0.1);
+
+ assertTrue(gammaPassRate > 0.99)
+
+
+
+
diff --git a/test/core/test_cubeIndex2worldCoords.m b/test/core/test_cubeIndex2worldCoords.m
new file mode 100644
index 000000000..0d8ce6c49
--- /dev/null
+++ b/test/core/test_cubeIndex2worldCoords.m
@@ -0,0 +1,72 @@
+function test_suite = test_cubeIndex2worldCoords
+
+test_functions = localfunctions();
+
+initTestSuite;
+
+function test_empty_cubeIndex2worldCoords
+ ct = helper_getTestCt();
+ assertExceptionThrown(@()matRad_cubeIndex2worldCoords());
+ assertExceptionThrown(@()matRad_cubeIndex2worldCoords([],ct));
+ assertExceptionThrown(@()matRad_cubeIndex2worldCoords([100 100 100],ct)); %Out of bounds
+
+%with ct.dicom
+function test_Dicom_cubeIndex2worldCoords
+ ct = helper_getTestCt();
+ ct.dicomInfo.ImagePositionPatient = [-10 -10 -10];
+ index = [31 32 33] ;
+ expected = [83 80 86];
+ assertEqual(matRad_cubeIndex2worldCoords(index , ct), expected);
+ assertEqual(matRad_cubeIndex2worldCoords(sub2ind(ct.cubeDim,index(1),index(2),index(3)) , ct), expected);
+
+ %Multiple indices
+ index = [31 32 33; 33 31 32];
+ expected = [83 80 86; 80 86 83];
+ assertEqual(matRad_cubeIndex2worldCoords(index , ct), expected);
+ assertEqual(matRad_cubeIndex2worldCoords(sub2ind(ct.cubeDim,index(:,1),index(:,2),index(:,3)) , ct), expected);
+
+ %Wrong dimension error
+ assertExceptionThrown(@()matRad_cubeIndex2worldCoords([1 1 1 1],ct));
+ assertExceptionThrown(@()matRad_cubeIndex2worldCoords([1 1],ct));
+
+
+% without ct.dicomInfo
+function test_noDicom_cubeIndex2worldCoords
+ ct = helper_getTestCt();
+
+ index = [31 32 33];
+ expected = [3 0 6];
+ assertEqual(matRad_cubeIndex2worldCoords(index , ct), expected);
+ assertEqual(matRad_cubeIndex2worldCoords(sub2ind(ct.cubeDim,index(1),index(2),index(3)) , ct), expected);
+
+ index = [1 2 3; 3 2 1];
+ expected = [-87 -90 -84; -87 -84 -90];
+
+ assertEqual(matRad_cubeIndex2worldCoords(index , ct), expected);
+ assertEqual(matRad_cubeIndex2worldCoords(sub2ind(ct.cubeDim,index(:,1),index(:,2),index(:,3)) , ct), expected);
+
+% without ct.dicomInfo
+function test_grid_cubeIndex2worldCoords
+ ct = helper_getTestCt();
+
+ ct.dimensions = ct.cubeDim;
+ ct = rmfield(ct,'cubeDim');
+
+ index = [31 32 33];
+ expected = [3 0 6];
+ assertEqual(matRad_cubeIndex2worldCoords(index , ct), expected);
+ assertEqual(matRad_cubeIndex2worldCoords(sub2ind(ct.dimensions,index(1),index(2),index(3)) , ct), expected);
+
+ index = [1 2 3; 3 2 1];
+ expected = [-87 -90 -84; -87 -84 -90];
+
+ assertEqual(matRad_cubeIndex2worldCoords(index , ct), expected);
+ assertEqual(matRad_cubeIndex2worldCoords(sub2ind(ct.dimensions,index(:,1),index(:,2),index(:,3)) , ct), expected);
+
+
+
+function ct = helper_getTestCt()
+ ct.resolution.x = 3;
+ ct.resolution.y = 3;
+ ct.resolution.z = 3;
+ ct.cubeDim = [60 60 60 ];
diff --git a/test/core/test_generateStf.m b/test/core/test_generateStf.m
new file mode 100644
index 000000000..033628b4c
--- /dev/null
+++ b/test/core/test_generateStf.m
@@ -0,0 +1,88 @@
+function test_suite = test_generateStf
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_generateStf_photons
+ load TG119.mat;
+ pln = helper_basicPln('photons',ct,cst);
+
+ stf = matRad_generateStf(ct,cst,pln);
+ assertTrue(isstruct(stf));
+ assertEqual(numel(stf),numel(pln.propStf.gantryAngles));
+
+function test_generateStf_protons
+ load TG119.mat;
+ pln = helper_basicPln('protons',ct,cst);
+
+ stf = matRad_generateStf(ct,cst,pln);
+ assertTrue(isstruct(stf));
+ assertEqual(numel(stf),numel(pln.propStf.gantryAngles));
+
+function test_generateStf_helium
+ load TG119.mat;
+ pln = helper_basicPln('helium',ct,cst);
+
+ stf = matRad_generateStf(ct,cst,pln);
+ assertTrue(isstruct(stf));
+ assertEqual(numel(stf),numel(pln.propStf.gantryAngles));
+
+function test_generateStf_carbon
+ load TG119.mat;
+ pln = helper_basicPln('carbon',ct,cst);
+
+ stf = matRad_generateStf(ct,cst,pln);
+ assertTrue(isstruct(stf));
+ assertEqual(numel(stf),numel(pln.propStf.gantryAngles));
+
+function test_generateStf_noTargetObjectives
+ load TG119.mat;
+ [cst{:,6}] = deal([]);
+ pln = helper_basicPln('photons',ct,cst);
+
+ stf = matRad_generateStf(ct,cst,pln);
+ assertTrue(isstruct(stf));
+ assertEqual(numel(stf),numel(pln.propStf.gantryAngles));
+
+function test_generateStfSingleBixel
+ load TG119.mat;
+ pln = helper_basicPln('photons',ct,cst);
+ stf = matRad_generateSingleBixelStf(ct,cst,pln);
+ assertTrue(isstruct(stf));
+
+ pln = helper_basicPln('protons',ct,cst);
+ stf = matRad_generateSingleBixelStf(ct,cst,pln);
+ assertTrue(isstruct(stf));
+
+function pln = helper_basicPln(modality,ct,cst)
+ pln.radiationMode = modality;
+ pln.numOfFractions = 30;
+ pln.machine = 'Generic';
+ pln.multScen = matRad_NominalScenario(ct);
+ pln.propStf.gantryAngles = 0;
+ pln.propStf.couchAngles = 0;
+ pln.propStf.bixelWidth = 5;
+ pln.propStf.isoCenter = matRad_getIsoCenter(cst,ct);
diff --git a/test/core/test_getWorldAxes.m b/test/core/test_getWorldAxes.m
new file mode 100644
index 000000000..71c7edba3
--- /dev/null
+++ b/test/core/test_getWorldAxes.m
@@ -0,0 +1,23 @@
+function test_suite = test_getWorldAxes
+
+test_functions = localfunctions();
+
+initTestSuite;
+
+function test_basic_getWorldAxes
+ assertExceptionThrown(@()matRad_getWorldAxes());
+
+ ct = get_testCtHelper();
+ expectedX = [-5 -4 -3 -2 -1 0 1 2 3 4];
+ expectedY = [-10 -8 -6 -4 -2 0 2 4 6 8];
+ expectedZ = [-15 -12 -9 -6 -3 0 3 6 9 12];
+ resultCt = matRad_getWorldAxes(ct);
+ assertEqual(resultCt.x,expectedX);
+ assertEqual(resultCt.y,expectedY);
+ assertEqual(resultCt.z,expectedZ);
+
+function ct = get_testCtHelper()
+ ct.resolution.x = 1;
+ ct.resolution.y = 2;
+ ct.resolution.z = 3;
+ ct.cubeDim = [10 10 10 ];
\ No newline at end of file
diff --git a/test/core/test_world2cubeCoords.m b/test/core/test_world2cubeCoords.m
new file mode 100644
index 000000000..70baf9cc0
--- /dev/null
+++ b/test/core/test_world2cubeCoords.m
@@ -0,0 +1,31 @@
+function test_suite = test_world2cubeCoords
+
+test_functions = localfunctions();
+
+initTestSuite;
+
+
+function test_empty_world2cubeCoords
+ ct = helper_getTestCt();
+ assertExceptionThrown(@()matRad_world2cubeCoords());
+ assertExceptionThrown(@()matRad_world2cubeCoords([],ct));
+
+
+function test_basic_world2cubeCoords
+ ct = helper_getTestCt();
+ expected = [3 36 45];
+ assertEqual(matRad_world2cubeCoords([-30 3 12], ct),expected);
+% out of bounds
+ assertExceptionThrown(@()matRad_world2cubeCoords([-30,4,45],ct,false));
+
+
+function ct = helper_getTestCt()
+ ct.x = -30:3:27;
+ ct.y = -30:3:27;
+ ct.z = -30:3:27;
+
+ ct.resolution.x = 3;
+ ct.resolution.y = 3;
+ ct.resolution.z = 3;
+
+ ct.cubeDim = [20 20 20 ];
\ No newline at end of file
diff --git a/test/core/test_world2cubeIndex.m b/test/core/test_world2cubeIndex.m
new file mode 100644
index 000000000..e76c9633d
--- /dev/null
+++ b/test/core/test_world2cubeIndex.m
@@ -0,0 +1,36 @@
+function test_suite = test_world2cubeIndex
+
+test_functions = localfunctions();
+
+initTestSuite;
+
+
+function test_empty_world2cubeIndex
+ ct = helper_getTestCt();
+ assertExceptionThrown(@()matRad_world2cubeIndex());
+ assertExceptionThrown(@()matRad_world2cubeIndex([],ct));
+
+function test_basic_world2cubeIndex
+ ct = helper_getTestCt();
+ wCoord = [-30 3 12];
+ expected = [12 1 15];
+ assertEqual(matRad_world2cubeIndex(wCoord, ct),expected);
+
+ wCoord = [-30 3 12; 12 -30 3];
+ expected = [12 1 15; 1 15 12];
+ assertEqual(matRad_world2cubeIndex(wCoord, ct),expected);
+
+% out of bounds
+ assertExceptionThrown(@()matRad_world2cubeIndex([-30,4,35],ct));
+
+
+function ct = helper_getTestCt()
+ ct.x = -30:3:27;
+ ct.y = -30:3:27;
+ ct.z = -30:3:27;
+
+ ct.resolution.x = 3;
+ ct.resolution.y = 3;
+ ct.resolution.z = 3;
+
+ ct.cubeDim = [20 20 20 ];
\ No newline at end of file
diff --git a/test/dicom/test_dicomIO.m b/test/dicom/test_dicomIO.m
new file mode 100644
index 000000000..724f99ed0
--- /dev/null
+++ b/test/dicom/test_dicomIO.m
@@ -0,0 +1,88 @@
+function test_suite = test_dicomIO
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_DicomImporter_emptyfolder
+ path = helper_temporaryFolder('dicomIOtest');
+ assertExceptionThrown(@() matRad_DicomImporter(path));
+
+function test_DicomExporter
+ testpatient = load('photons_testData.mat');
+ path = helper_temporaryFolder('dicomIOtest');
+
+ dummyResultGUI = struct('physicalDose',rand(testpatient.ct.cubeDim),'w',ones(sum([testpatient.stf.totalNumOfBixels]),1));
+ for i = 1:numel(testpatient.stf)
+ dummyResultGUI.(['physicalDose_beam' num2str(i)]) = rand(testpatient.ct.cubeDim);
+ end
+ dummyResultGUI.wUnsequenced = dummyResultGUI.w;
+
+ h = matRad_DicomExporter(testpatient.ct,testpatient.cst,testpatient.pln,testpatient.stf,dummyResultGUI);
+ assertTrue(isa(h,'matRad_DicomExporter'));
+ assertTrue(isa(h,'handle'));
+
+ h.dicomDir = path;
+ h.matRad_exportDicom();
+
+ dircontents = dir([path filesep '*.dcm']);
+ assertTrue(numel(dircontents) > 0)
+
+
+function test_DicomImporter_construct_with_path_and_file_load
+ path = helper_temporaryFolder('dicomIOtest',false);
+ h = matRad_DicomImporter(path);
+ assertTrue(isequal(h.patDir, path));
+ assertTrue(~isempty(h.allfiles));
+
+ NumberOfCtFiles = numel(nonzeros(strcmp(h.allfiles(:,2),'CT')));
+ NumberOfRtssFiles = numel(nonzeros(strcmpi(h.allfiles(:,2),'rtstruct')));
+ NumberOfRtPlanFiles = numel(nonzeros(strcmpi(h.allfiles(:,2),'rtplan')));
+ NumberOfRtDoseFiles = numel(nonzeros(strcmpi(h.allfiles(:,2),'rtdose')));
+
+ assertTrue(isequal(NumberOfCtFiles, size(h.importFiles.ct, 1)));
+ assertTrue(isequal(NumberOfRtssFiles, size(h.importFiles.rtss, 1)));
+ assertTrue(isequal(NumberOfRtPlanFiles, size(h.importFiles.rtplan,1)));
+ assertTrue(isequal(NumberOfRtDoseFiles, size(h.importFiles.rtdose,1)));
+
+ resBool = true;
+ if isempty(h.importFiles.resx) || isempty(h.importFiles.resy) || isempty(h.importFiles.resz)
+ resBool = false;
+ end
+ assertTrue(resBool);
+
+function test_DicomImporter_Import
+ path = helper_temporaryFolder('dicomIOtest',false);
+ h = matRad_DicomImporter(path);
+
+ h.matRad_importDicom();
+
+
+
+
+
+
+
+
diff --git a/test/doseCalc/test_Analytical.m b/test/doseCalc/test_Analytical.m
new file mode 100644
index 000000000..a04f53fdb
--- /dev/null
+++ b/test/doseCalc/test_Analytical.m
@@ -0,0 +1,69 @@
+function test_suite = test_Analytical
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_getAnalyticalEngineFromPln
+ % Single gaussian lateral model
+ testData.pln = struct('radiationMode','protons','machine','Generic');
+ testData.pln.propDoseCalc.engine = 'AnalyticalPB';
+ engine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.getEngineFromPln(testData.pln);
+ assertTrue(isa(engine,'DoseEngines.matRad_ParticleAnalyticalBortfeldEngine'));
+
+ % Double Gaussian lateral model
+ % If you don't have my clusterDose basedata you cannot try this :P
+ %{
+ testData.pln = struct('radiationMode','protons','machine','Generic_clusterDose');
+ testData.pln.propDoseCalc.engine = 'AnalyticalPB';
+ engine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.getEngineFromPln(testData.pln);
+ assertTrue(isa(engine,'DoseEngines.matRad_ParticleAnalyticalBortfeldEngine'));
+ %}
+
+function test_loadMachineForAnalytical
+ possibleRadModes = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.possibleRadiationModes;
+ for i = 1:numel(possibleRadModes)
+ machine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.loadMachine(possibleRadModes{i},'Generic');
+ assertTrue(isstruct(machine));
+ assertTrue(isfield(machine, 'meta'));
+ assertTrue(isfield(machine.meta, 'radiationMode'));
+ assertTrue(strcmp(machine.meta.radiationMode, 'protons'));
+ end
+
+function test_calcDoseAnalytical
+ testData = load('protons_testData.mat');
+ assertTrue(DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.isAvailable(testData.pln))
+ testData.pln.propDoseCalc.engine = 'AnalyticalPB';
+ resultGUI = matRad_calcDoseForward(testData.ct, testData.cst, testData.stf, testData.pln, ones(sum([testData.stf(:).totalNumOfBixels]),1));
+
+ assertTrue(isfield(resultGUI, 'physicalDose'));
+ assertTrue(isfield(resultGUI, 'w'));
+ assertTrue(isequal(testData.ct.cubeDim, size(resultGUI.physicalDose)));
+
+function test_nonSupportedSettings
+ % Radiation mode other than protons not implemented
+ testData = load('carbon_testData.mat');
+ testData.pln.propDoseCalc.engine = 'AnalyticalPB';
+ assertFalse(DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.isAvailable(testData.pln));
+
+ % Biological models, LET, other lateral models not implemented
+ testData = load('protons_testData.mat');
+ testData.pln.propDoseCalc.engine = 'AnalyticalPB';
+ testData.pln.propDoseCalc.calcLET = true;
+ testData.pln.propDoseCalc.calcBioDose = true;
+ testData.pln.propDoseCalc.lateralModel = 'double';
+ engine = DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.getEngineFromPln(testData.pln);
+ assertTrue(isa(engine,'DoseEngines.matRad_ParticleAnalyticalBortfeldEngine'));
+
+ resultGUI = matRad_calcDoseForward(testData.ct, testData.cst, testData.stf, testData.pln, ones(sum([testData.stf(:).totalNumOfBixels]),1));
+ assertTrue(~engine.calcLET)
+ %assertTrue(~engine.calcBioDose) % Access protected property
+
+ % Invalid machine without radiation mode field
+ testData.pln.machine = 'Empty';
+ testData.pln.propDoseCalc.engine = 'AnalyticalPB';
+ assertExceptionThrown(@() DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.isAvailable(testData.pln));
+ assertFalse(DoseEngines.matRad_ParticleAnalyticalBortfeldEngine.isAvailable(testData.pln,[]));
+
+
+
diff --git a/test/doseCalc/test_BEDprojection.m b/test/doseCalc/test_BEDprojection.m
new file mode 100644
index 000000000..f469bf1ee
--- /dev/null
+++ b/test/doseCalc/test_BEDprojection.m
@@ -0,0 +1,73 @@
+function test_suite = test_BEDprojection
+
+test_functions = localfunctions();
+
+initTestSuite;
+
+function test_BEDprojectionConstructor
+bed = matRad_BEDProjection;
+assertTrue(isobject(bed));
+assertTrue(isa(bed, 'matRad_BEDProjection'));
+
+%test compute single scenario BED projection
+function test_BED_computeSingleScenario
+% ct of 3x3 with center voxel as target ,
+% single pencil beam
+a = sparse(zeros(9,1));
+a(5) = 2;
+dij.physicalDose{1} = a;
+a(5) = 1;
+dij.mAlphaDose{1} = a;
+a(5) = sqrt(0.05)*2;
+dij.mSqrtBetaDose{1} = a;
+dij.ax{1} = 0.5*ones(numel(a),1);
+dij.bx{1} = 0.05*ones(numel(a),1);
+dij.doseGrid.numOfVoxels = 9;
+dij.ixDose{1} = 5;
+w = 1;
+scen = 1;
+obj = matRad_BEDProjection;
+BED = computeSingleScenario(obj,dij,scen,w);
+a = zeros(dij.doseGrid.numOfVoxels,1);
+a(dij.ixDose{scen}) = 2.4;
+expectedResult = a;
+
+assertElementsAlmostEqual(BED,expectedResult,'absolute',1e-4);
+
+
+
+function test_BED_computeSingleScenarioGradient
+% ct of 3x3 with center voxel as target ,
+% single pencil beam
+a = sparse(zeros(9,1));
+a(5) = 2;
+dij.physicalDose{1} = a;
+a(5) = 1;
+dij.mAlphaDose{1} = a;
+a(5) = sqrt(0.05)*2;
+dij.mSqrtBetaDose{1} = a;
+dij.ax{1} = 0.5*ones(numel(a),1);
+dij.bx{1} = 0.05*ones(numel(a),1);
+dij.doseGrid.numOfVoxels = 9;
+dij.ixDose{1} = 5;
+w = 1;
+scen = 1;
+obj = matRad_BEDProjection;
+a = zeros(dij.doseGrid.numOfVoxels,1);
+a(dij.ixDose{scen}) = 1;
+doseGrad{1} = a ;
+wGrad = projectSingleScenarioGradient(obj,dij,doseGrad,scen,w);
+
+expectedResult = 2.8;
+
+assertElementsAlmostEqual(wGrad,expectedResult,'absolute',1e-4);
+
+
+function test_BED_setBiologicalDosePrescription
+opti = DoseObjectives.matRad_SquaredOverdosing(1,2);
+alphaX = 0.5;
+betaX = 0.05;
+opti = matRad_BEDProjection.setBiologicalDosePrescriptions(opti,alphaX,betaX);
+assertEqual(opti.parameters{1},2.4 );
+
+
diff --git a/test/doseCalc/test_TopasMCEngine.m b/test/doseCalc/test_TopasMCEngine.m
new file mode 100644
index 000000000..c90d65508
--- /dev/null
+++ b/test/doseCalc/test_TopasMCEngine.m
@@ -0,0 +1,131 @@
+function test_suite = test_TopasMCEngine
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_loadMachine
+ radModes = DoseEngines.matRad_TopasMCEngine.possibleRadiationModes;
+ for i = 1:numel(radModes)
+ machine = DoseEngines.matRad_TopasMCEngine.loadMachine(radModes{i},'Generic');
+ assertTrue(isstruct(machine));
+ end
+ assertExceptionThrown(@() DoseEngines.matRad_TopasMCEngine.loadMachine('grbl','grbl'),'matRad:Error')
+
+function test_getEngineFromPlnByName
+ radModes = DoseEngines.matRad_TopasMCEngine.possibleRadiationModes;
+ for i = 1:numel(radModes)
+ plnDummy = struct('radiationMode',radModes{i},'machine','Generic','propDoseCalc',struct('engine','TOPAS'));
+ engine = DoseEngines.matRad_TopasMCEngine.getEngineFromPln(plnDummy);
+ assertTrue(isa(engine,'DoseEngines.matRad_TopasMCEngine'));
+ end
+
+function test_TopasMCdoseCalcBasic
+% test if all the necessary output files are written vor a couple of cases.
+% i am not using the default number of histories for testing her, insted 1e6.
+% Because the files are just writen and not simulated so we dont care about simulation time.
+% To few histories may result in wierd behavior in the topas interface, i.e if a beam
+% recieves no histories because there are not enough to be distributed accros the spots,
+% it causes an error
+radModes = DoseEngines.matRad_TopasMCEngine.possibleRadiationModes;
+matRad_cfg = MatRad_Config.instance();
+
+if moxunit_util_platform_is_octave
+ confirm_recursive_rmdir(false,'local');
+end
+
+for i = 1:numel(radModes)
+ if ~strcmp(radModes{i},'photons')
+ load([radModes{i} '_testData.mat']);
+ pln.propDoseCalc.engine = 'TOPAS';
+ pln.propDoseCalc.externalCalculation = 'write';
+ pln.propDoseCalc.numHistoriesDirect = 1e6;
+ resultGUI = matRad_calcDoseForward(ct,cst,stf,pln, ones(1,sum([stf(:).totalNumOfBixels])));
+
+ elseif strcmp(radModes{i},'photons')
+ load([radModes{i} '_testData.mat']);
+ pln.propOpt.runSequencing = 1;
+ pln.propOpt.runDAO = 1;
+ dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+ resultGUI = matRad_calcCubes(ones(dij.totalNumOfBixels,1),dij);
+ resultGUI.wUnsequenced = ones(dij.totalNumOfBixels,1);
+ resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,5);
+ [pln,stf] = matRad_aperture2collimation(pln,stf,resultGUI.sequencing,resultGUI.apertureInfo);
+ pln.propDoseCalc.engine = 'TOPAS';
+ pln.propDoseCalc.externalCalculation = 'write';
+ pln.propDoseCalc.beamProfile = 'phasespace';
+ pln.propDoseCalc.numHistoriesDirect = 1e6;
+ resultGUI = matRad_calcDoseForward(ct,cst,stf,pln, resultGUI.w );
+ end
+ folderName = [matRad_cfg.primaryUserFolder filesep 'TOPAS' filesep];
+ folderName = [folderName stf(1).radiationMode,'_',stf(1).machine,'_',datestr(now, 'dd-mm-yy')];
+ %check of outputfolder exists
+ assertTrue(isfolder(folderName));
+ %check if file in folder existi
+ assertTrue(isfile([folderName filesep 'matRad_cube.dat']));
+ assertTrue(isfile([folderName filesep 'matRad_cube.txt']));
+ assertTrue(isfile([folderName filesep 'MCparam.mat']));
+ for j = 1:pln.propStf.numOfBeams
+ assertTrue(isfile([folderName filesep 'beamSetup_matRad_plan_field' num2str(j) '.txt']));
+ assertTrue(isfile([folderName filesep 'matRad_plan_field' num2str(j) '_run1.txt']));
+ end
+ rmdir(folderName,'s'); %clean up
+end
+
+function test_TopasMCdoseCalcMultRuns
+numOfRuns = 5;
+radModes = DoseEngines.matRad_TopasMCEngine.possibleRadiationModes;
+matRad_cfg = MatRad_Config.instance();
+
+if moxunit_util_platform_is_octave
+ confirm_recursive_rmdir(false,'local');
+end
+
+for i = 1:numel(radModes)
+ if ~strcmp(radModes{i},'photons')
+ load([radModes{i} '_testData.mat']);
+ pln.propDoseCalc.engine = 'TOPAS';
+ pln.propDoseCalc.externalCalculation = 'write';
+ pln.propDoseCalc.numHistoriesDirect = 1e6;
+ pln.propDoseCalc.numOfRuns = numOfRuns;
+ resultGUI = matRad_calcDoseForward(ct,cst,stf,pln, ones(1,sum([stf(:).totalNumOfBixels])));
+
+ elseif strcmp(radModes{i},'photons')
+ load([radModes{i} '_testData.mat']);
+ pln.propOpt.runSequencing = 1;
+ pln.propOpt.runDAO = 1;
+ dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+ resultGUI = matRad_calcCubes(ones(dij.totalNumOfBixels,1),dij);
+ resultGUI.wUnsequenced = ones(dij.totalNumOfBixels,1);
+ resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,5);
+ [pln,stf] = matRad_aperture2collimation(pln,stf,resultGUI.sequencing,resultGUI.apertureInfo);
+ pln.propDoseCalc.engine = 'TOPAS';
+ pln.propDoseCalc.externalCalculation = 'write';
+ pln.propDoseCalc.beamProfile = 'phasespace';
+ pln.propDoseCalc.numHistoriesDirect = 1e6;
+ pln.propDoseCalc.numOfRuns = numOfRuns;
+ resultGUI = matRad_calcDoseForward(ct,cst,stf,pln, resultGUI.w );
+ end
+ folderName = [matRad_cfg.primaryUserFolder filesep 'TOPAS' filesep];
+ folderName = [folderName stf(1).radiationMode,'_',stf(1).machine,'_',datestr(now, 'dd-mm-yy')];
+ %check of outputfolder exists
+ assertTrue(isfolder(folderName));
+ %check if file in folder existi
+ assertTrue(isfile([folderName filesep 'matRad_cube.dat']));
+ assertTrue(isfile([folderName filesep 'matRad_cube.txt']));
+ assertTrue(isfile([folderName filesep 'MCparam.mat']));
+ for j = 1:pln.propStf.numOfBeams
+ assertTrue(isfile([folderName filesep 'beamSetup_matRad_plan_field' num2str(j) '.txt']));
+ for k = 1:numOfRuns
+ assertTrue(isfile([folderName filesep 'matRad_plan_field' num2str(j) '_run' num2str(k) '.txt']));
+ end
+ end
+
+ rmdir(folderName,'s'); %clean up
+end
+
+
+
+
+
+
diff --git a/test/doseCalc/test_baseEngine.m b/test/doseCalc/test_baseEngine.m
new file mode 100644
index 000000000..6280f3949
--- /dev/null
+++ b/test/doseCalc/test_baseEngine.m
@@ -0,0 +1,68 @@
+function test_suite = test_baseEngine
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_doseEngineBaseAbstract
+ if moxunit_util_platform_is_octave()
+ assertExceptionThrown(@() DoseEngines.matRad_DoseEngineBase(),'');
+ else
+ assertExceptionThrown(@() DoseEngines.matRad_DoseEngineBase(),'MATLAB:class:abstract');
+ end
+
+function test_abstractIsAvailable
+ assertExceptionThrown(@() DoseEngines.matRad_DoseEngineBase.isAvailable,'matRad:Error');
+
+function test_getAvailableEngines
+ avail = DoseEngines.matRad_DoseEngineBase.getAvailableEngines();
+ assertTrue(~isempty(avail));
+ assertTrue(isstruct(avail));
+ assertTrue(iscolumn(avail));
+
+ photonDummyPln = struct('radiationMode','photons','machine','Generic');
+ availPhotons = DoseEngines.matRad_DoseEngineBase.getAvailableEngines(photonDummyPln);
+ assertTrue(~isempty(avail));
+ assertTrue(isstruct(avail));
+ assertTrue(iscolumn(avail));
+ assertTrue(numel(availPhotons) < numel(avail));
+
+function test_loadMachine
+ machine = DoseEngines.matRad_DoseEngineBase.loadMachine('photons','Generic');
+ assertTrue(isstruct(machine));
+
+ if moxunit_util_platform_is_octave()
+ assertExceptionThrown(@() DoseEngines.matRad_DoseEngineBase.loadMachine());
+ assertExceptionThrown(@() DoseEngines.matRad_DoseEngineBase.loadMachine('photons'));
+ else
+ assertExceptionThrown(@() DoseEngines.matRad_DoseEngineBase.loadMachine(),'MATLAB:minrhs');
+ assertExceptionThrown(@() DoseEngines.matRad_DoseEngineBase.loadMachine('photons'),'MATLAB:minrhs');
+ end
+ assertExceptionThrown(@() DoseEngines.matRad_DoseEngineBase.loadMachine('grbl','grbl'),'matRad:Error');
+
+function test_getEngineFromPlnDefaults
+ photonDummyPln = struct('radiationMode','photons','machine','Generic');
+ engine = DoseEngines.matRad_DoseEngineBase.getEngineFromPln(photonDummyPln);
+ assertTrue(isa(engine,'DoseEngines.matRad_PhotonPencilBeamSVDEngine'));
+
+ protonDummyPln = struct('radiationMode','protons','machine','Generic');
+ engine = DoseEngines.matRad_DoseEngineBase.getEngineFromPln(protonDummyPln);
+ assertTrue(isa(engine,'DoseEngines.matRad_ParticleHongPencilBeamEngine'));
+
+ carbonDummyPln = struct('radiationMode','carbon','machine','Generic');
+ engine = DoseEngines.matRad_DoseEngineBase.getEngineFromPln(carbonDummyPln);
+ assertTrue(isa(engine,'DoseEngines.matRad_ParticleHongPencilBeamEngine'));
+
+ heliumDummyPln = struct('radiationMode','helium','machine','Generic');
+ engine = DoseEngines.matRad_DoseEngineBase.getEngineFromPln(heliumDummyPln);
+ assertTrue(isa(engine,'DoseEngines.matRad_ParticleHongPencilBeamEngine'));
+
+function test_getEngineFromPlnByName
+ protonDummyPln = struct('radiationMode','protons','machine','Generic','propDoseCalc',struct('engine','MCsquare'));
+ engine = DoseEngines.matRad_DoseEngineBase.getEngineFromPln(protonDummyPln);
+ assertTrue(isa(engine,'DoseEngines.matRad_ParticleMCsquareEngine'));
+
+ %Wrong name
+ photonDummyPln = struct('radiationMode','photons','machine','Generic','propDoseCalc',struct('engine','MCsquare'));
+ engine = DoseEngines.matRad_DoseEngineBase.getEngineFromPln(photonDummyPln);
+ assertTrue(isa(engine,'DoseEngines.matRad_PhotonPencilBeamSVDEngine'));
diff --git a/test/doseConstraints/test_DoseConstraintFromObjective.m b/test/doseConstraints/test_DoseConstraintFromObjective.m
new file mode 100644
index 000000000..84493d233
--- /dev/null
+++ b/test/doseConstraints/test_DoseConstraintFromObjective.m
@@ -0,0 +1,56 @@
+function test_suite = test_DoseConstraintFromObjective
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_matRad_DoseConstraintFromObjective
+ objective = DoseObjectives.matRad_SquaredOverdosing(100,30);
+ obj = DoseConstraints.matRad_DoseConstraintFromObjective(objective); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_DoseConstraintFromObjective'));
+ assertEqual(obj.parameters{1},1e-5);
+ assertEqual(obj.parameters{2},1e-3);
+
+
+ obj = DoseConstraints.matRad_DoseConstraintFromObjective(objective,1e-3,1e-3);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_DoseConstraintFromObjective'));
+
+function test_matRad_DoseConstraintFromObjective_bounds
+ objective = DoseObjectives.matRad_SquaredOverdosing(100,30);
+ obj = DoseConstraints.matRad_DoseConstraintFromObjective(objective,1e-3,1e-3);
+
+ cu = upperBounds(obj,2);
+ cl = lowerBounds(obj,2);
+ assertEqual(cu,0.002);
+ assertEqual(cl,0);
+
+function test_matRad_DoseConstraintFromObjective_computeConstrFunc
+ objective = DoseObjectives.matRad_SquaredOverdosing(100,30);
+ obj = DoseConstraints.matRad_DoseConstraintFromObjective(objective,1e-3,1e-3);
+
+ dose = [25 28 30 23 35]';
+ cDose = computeDoseConstraintFunction(obj,dose);
+ cDose_expected = 5;
+ assertElementsAlmostEqual(cDose,cDose_expected,'absolute',1e-4);
+
+function test_matRad_DoseConstraintFromObjective_computeJacob
+ objective = DoseObjectives.matRad_SquaredOverdosing(100,30);
+ obj = DoseConstraints.matRad_DoseConstraintFromObjective(objective,1e-3,1e-3);
+
+ dose = [25 28 30 23 35]';
+ cDoseJacob = computeDoseConstraintJacobian(obj,dose);
+ cDoseJacob_expected = [0 0 0 0 2]';
+ assertElementsAlmostEqual(cDoseJacob,cDoseJacob_expected,'absolute',1e-4);
+
+function test_matRad_DoseConstraintFromObjective_getSet
+ objective = DoseObjectives.matRad_SquaredOverdosing(100,30);
+ obj = DoseConstraints.matRad_DoseConstraintFromObjective(objective,1e-3,1e-3);
+
+ doseParams = getDoseParameters(obj);
+ assertEqual(doseParams,30);
+
+ obj = setDoseParameters(obj,60);
+ assertEqual(obj.objective.parameters{1},60);
+
diff --git a/test/doseConstraints/test_doseConstraintsMinMaxDose.m b/test/doseConstraints/test_doseConstraintsMinMaxDose.m
new file mode 100644
index 000000000..ec241081e
--- /dev/null
+++ b/test/doseConstraints/test_doseConstraintsMinMaxDose.m
@@ -0,0 +1,151 @@
+function test_suite = test_doseConstraintsMinMaxDose
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_DoseConstraints_matRad_MinMaxDose
+
+ obj = DoseConstraints.matRad_MinMaxDose(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_MinMaxDose'));
+ assertEqual(obj.parameters{1},0);
+ assertEqual(obj.parameters{2},30);
+ assertEqual(obj.parameters{3},1);
+
+
+ obj = DoseConstraints.matRad_MinMaxDose(10,30,'approx');
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_MinMaxDose'));
+
+function test_DoseConstraints_matRad_MinMaxDose_upperBounds
+ obj = DoseConstraints.matRad_MinMaxDose(-5,Inf,'approx');
+ assertTrue(isempty(upperBounds(obj,2)));
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,Inf,'approx');
+ assertTrue(isinf(upperBounds(obj,2)));
+
+ obj = DoseConstraints.matRad_MinMaxDose(-5,70,'approx');
+ assertEqual(upperBounds(obj,2),70);
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'approx');
+ assertEqual(upperBounds(obj,2),[Inf , 70]');
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'voxelwise');
+ assertEqual(upperBounds(obj,2),[70 70]');
+
+
+function test_DoseConstraints_matRad_MinMaxDose_lowerBounds
+ obj = DoseConstraints.matRad_MinMaxDose(-5,Inf,'approx');
+ assertTrue(isempty(lowerBounds(obj,2)));
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,Inf,'approx');
+ assertEqual(lowerBounds(obj,2),50);
+
+ obj = DoseConstraints.matRad_MinMaxDose(-5,70,'approx');
+ assertEqual(lowerBounds(obj,2),0);
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'approx');
+ assertEqual(lowerBounds(obj,2),[50, 0]');
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'voxelwise');
+ assertEqual(lowerBounds(obj,2),[50 50]');
+
+
+function test_DoseConstraints_MinMaxDose_getDoseConstrntJacbnStrct
+ obj = DoseConstraints.matRad_MinMaxDose(-5,Inf,'approx');
+ assertTrue(isempty(getDoseConstraintJacobianStructure(obj,2)));
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'approx');
+ assertEqual(size(getDoseConstraintJacobianStructure(obj,2)),[2,2]);
+
+ obj = DoseConstraints.matRad_MinMaxDose(-5,70,'approx');
+ assertEqual(size(getDoseConstraintJacobianStructure(obj,2)),[2,1]);
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'voxelwise');
+ assertEqual(size(getDoseConstraintJacobianStructure(obj,2)),[2,2]);
+
+
+
+function test_DoseConstraints_MinMaxDose_computeConstrFunction
+
+ obj = DoseConstraints.matRad_MinMaxDose(-5,Inf ,'approx');
+ dose = [55,58,60,62,65]';
+ test_cDose = computeDoseConstraintFunction(obj,dose);
+ assertTrue(isempty(test_cDose));
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'approx');
+
+ dose = [55,58,60,62,65]';
+ test_cDose = computeDoseConstraintFunction(obj,dose);
+ expected_cDose = [55; 65];
+
+ assertElementsAlmostEqual(test_cDose,expected_cDose,'absolute', 1e-4);
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,Inf,'approx');
+
+ dose = [55,58,60,62,65]';
+ test_cDose = computeDoseConstraintFunction(obj,dose);
+ expected_cDose = [55];
+
+ assertElementsAlmostEqual(test_cDose,expected_cDose,'absolute', 1e-4);
+
+ obj = DoseConstraints.matRad_MinMaxDose(-5 ,70,'approx');
+
+ dose = [55,58,60,62,65]';
+ test_cDose = computeDoseConstraintFunction(obj,dose);
+ expected_cDose = [65];
+
+ assertElementsAlmostEqual(test_cDose,expected_cDose,'absolute', 1e-4);
+
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'voxelwise');
+
+ dose = [55,58,60,62,65]';
+ test_cDose = computeDoseConstraintFunction(obj,dose);
+ expected_cDose = [55,58,60,62,65]';
+
+ assertElementsAlmostEqual(test_cDose,expected_cDose,'absolute', 1e-4);
+
+
+function test_DoseConstraints_MinMaxDose_computeDoseConstraintJacobian
+
+ obj = DoseConstraints.matRad_MinMaxDose(-5,Inf ,'approx');
+ dose = [55,58,60,62,65]';
+ test_Jaco = computeDoseConstraintJacobian(obj,dose);
+ assertTrue(isempty(test_Jaco));
+
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'approx');
+ dose = [55,58,60,62,65]';
+
+ test_Jaco = computeDoseConstraintJacobian(obj,dose);
+ expected_Jaco = [ 1 0;
+ 0 0;
+ 0 0;
+ 0 0;
+ 0 1 ];
+ assertElementsAlmostEqual(test_Jaco,expected_Jaco,'absolute', 1e-4);
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,Inf,'approx');
+ dose = [55,58,60,62,65]';
+
+ test_Jaco = computeDoseConstraintJacobian(obj,dose);
+ expected_Jaco = [ 1 0 0 0 0]';
+ assertElementsAlmostEqual(test_Jaco,expected_Jaco,'absolute', 1e-4);
+
+ obj = DoseConstraints.matRad_MinMaxDose(-5 ,70,'approx');
+ dose = [55,58,60,62,65]';
+
+ test_Jaco = computeDoseConstraintJacobian(obj,dose);
+ expected_Jaco = [ 0 0 0 0 1 ]';
+ assertElementsAlmostEqual(test_Jaco,expected_Jaco,'absolute', 1e-4);
+
+ obj = DoseConstraints.matRad_MinMaxDose(50,70,'voxelwise');
+ dose = [55,58,60,62,65]';
+
+ test_Jaco = computeDoseConstraintJacobian(obj,dose);
+ expected_Jaco = speye(5);
+ assertElementsAlmostEqual(test_Jaco,expected_Jaco,'absolute', 1e-4);
+
+
diff --git a/test/doseConstraints/test_doseConstraints_MinMaxDVHDose.m b/test/doseConstraints/test_doseConstraints_MinMaxDVHDose.m
new file mode 100644
index 000000000..95c29d8fc
--- /dev/null
+++ b/test/doseConstraints/test_doseConstraints_MinMaxDVHDose.m
@@ -0,0 +1,40 @@
+function test_suite = test_doseConstraints_MinMaxDVHDose
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_DoseConstraints_matRad_MinMaxDVHDose
+ obj = DoseConstraints.matRad_MinMaxDVH(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_MinMaxDVH'));
+ assertEqual(obj.parameters{1},30);
+ assertEqual(obj.parameters{2},0);
+ assertEqual(obj.parameters{3},100);
+
+ obj = DoseConstraints.matRad_MinMaxDVH(10,30);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_MinMaxDVH'));
+
+function test_DoseConstraints_matRad_DVHDose_Bounds
+ obj = DoseConstraints.matRad_MinMaxDVH(30,0,100);
+ cu = upperBounds(obj,2);
+ cl = lowerBounds(obj,2);
+ assertEqual(cu,1);
+ assertEqual(cl,0);
+
+
+function test_DoseConstraints_matRad_DVHDose_computeDoseConstr
+ obj = DoseConstraints.matRad_MinMaxDVH(60,0,100);
+ dose = [55 58 60 61 65]';
+ cDose = computeDoseConstraintFunction(obj,dose);
+ expected_cDose = 0.6;
+ assertEqual(cDose,expected_cDose);
+
+function test_DoseConstraints_matRad_DVHDose_computeJacobian
+ obj = DoseConstraints.matRad_MinMaxDVH(60,0,100);
+ dose = [55 58 60 61 65]';
+ cDoseJacob = computeDoseConstraintJacobian(obj,dose);
+ expected_Jacob =[0.0018; 0.0218; 0.0460; 0.0375; 0.0018];
+ assertElementsAlmostEqual(cDoseJacob,expected_Jacob,'absolute',1e-4);
+
\ No newline at end of file
diff --git a/test/doseConstraints/test_doseConstraints_MinMaxEUD.m b/test/doseConstraints/test_doseConstraints_MinMaxEUD.m
new file mode 100644
index 000000000..8e07b71a2
--- /dev/null
+++ b/test/doseConstraints/test_doseConstraints_MinMaxEUD.m
@@ -0,0 +1,38 @@
+function test_suite = test_doseConstraints_MinMaxEUD
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_DoseConstraints_matRad_MinMaxEUD
+ obj = DoseConstraints.matRad_MinMaxEUD(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_MinMaxEUD'));
+ assertEqual(obj.parameters{1},5);
+ assertEqual(obj.parameters{2},0);
+ assertEqual(obj.parameters{3},30);
+
+ obj = DoseConstraints.matRad_MinMaxEUD(2,0,30);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_MinMaxEUD'));
+
+function test_DoseConstraints_matRad_EUDDose_Bounds
+ obj = DoseConstraints.matRad_MinMaxEUD(5,0,30);
+ cu = upperBounds(obj,2);
+ cl = lowerBounds(obj,2);
+ assertEqual(cu,30);
+ assertEqual(cl,0);
+
+function test_DoseConstraints_matRad_EUDDose_computeDoseConstr
+ obj = DoseConstraints.matRad_MinMaxEUD(5,0,30);
+ dose = [55 58 60 62 65]';
+ cDose = computeDoseConstraintFunction(obj,dose);
+ expected_cDose = 60.3829;
+ assertElementsAlmostEqual(cDose,expected_cDose,'absolute',1e-4);
+
+function test_DoseConstraints_matRad_EUDDose_computeJacobian
+ obj = DoseConstraints.matRad_MinMaxEUD(5,0,60);
+ dose = [55 58 60 61 65]';
+ cDoseJacob = computeDoseConstraintJacobian(obj,dose);
+ expected_Jacob =[0.1397; 0.1727; 0.1978; 0.2113; 0.2724];
+ assertElementsAlmostEqual(cDoseJacob,expected_Jacob,'absolute',1e-4);
diff --git a/test/doseConstraints/test_doseConstraints_MinMaxMeanDose.m b/test/doseConstraints/test_doseConstraints_MinMaxMeanDose.m
new file mode 100644
index 000000000..5209784c2
--- /dev/null
+++ b/test/doseConstraints/test_doseConstraints_MinMaxMeanDose.m
@@ -0,0 +1,36 @@
+function test_suite = test_doseConstraints_MinMaxMeanDose
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_DoseConstraints_matRad_MinMaxMeanDose
+
+ obj = DoseConstraints.matRad_MinMaxMeanDose(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_MinMaxMeanDose'));
+ assertEqual(obj.parameters{1},0);
+ assertEqual(obj.parameters{2},30);
+
+ obj = DoseConstraints.matRad_MinMaxMeanDose(10,30);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseConstraints.matRad_MinMaxMeanDose'));
+
+
+function test_DoseConstraints_matRad_MeanDose_Bounds
+ obj = DoseConstraints.matRad_MinMaxMeanDose(10,30);
+ cu = upperBounds(obj,2);
+ cl = lowerBounds(obj,2);
+ assertEqual(cu,30);
+ assertEqual(cl,10);
+
+
+function test_DoseConstraints_matRad_MeanDose_ConstrJacob
+ obj = DoseConstraints.matRad_MinMaxMeanDose(10,30);
+ dose = [55,58,60,62,65]';
+ cDose = computeDoseConstraintFunction(obj,dose);
+ assertEqual(cDose,60);
+
+ cDoseJacob = computeDoseConstraintJacobian(obj,dose);
+ expected_cJaco = [0.2 0.2 0.2 0.2 0.2]';
+ assertEqual(cDoseJacob,expected_cJaco);
\ No newline at end of file
diff --git a/test/doseObjectives/test_doseObejctiveEUD.m b/test/doseObjectives/test_doseObejctiveEUD.m
new file mode 100644
index 000000000..8a14beed8
--- /dev/null
+++ b/test/doseObjectives/test_doseObejctiveEUD.m
@@ -0,0 +1,47 @@
+function test_suite = test_doseObejctiveEUD
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_doseObjective_EUD_construct
+
+ obj = DoseObjectives.matRad_EUD(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_EUD'));
+ assertEqual(obj.parameters{1},0);
+ assertEqual(obj.parameters{2},3.5);
+ assertEqual(obj.penalty,1);
+
+ obj = DoseObjectives.matRad_EUD(100,60,3.5);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_EUD'));
+
+function test_doseObjective_EUD_computeObjFunction
+
+ obj = DoseObjectives.matRad_EUD(100,60,3.5);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 0.0579;
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+
+ assertTrue(isnan(test_fDose));
+
+
+function test_doseObjective_EUD_computeGradient
+
+ obj = DoseObjectives.matRad_EUD(100,10,2);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+ expected_fDose = [ 18.3392 19.3395 20.0064 20.6733 21.6736]';
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ assertExceptionThrown(@()computeDoseObjectiveGradient(obj,dose),'matRad:Error')
+
\ No newline at end of file
diff --git a/test/doseObjectives/test_doseObejctiveMaxDVH.m b/test/doseObjectives/test_doseObejctiveMaxDVH.m
new file mode 100644
index 000000000..342f32385
--- /dev/null
+++ b/test/doseObjectives/test_doseObejctiveMaxDVH.m
@@ -0,0 +1,57 @@
+function test_suite = test_doseObejctiveMaxDVH
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_doseObjective_MaxDVH_construct
+
+ obj = DoseObjectives.matRad_MaxDVH(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_MaxDVH'));
+ assertEqual(obj.parameters{1},30);
+ assertEqual(obj.parameters{2},95);
+ assertEqual(obj.penalty,1);
+
+ obj = DoseObjectives.matRad_MaxDVH(100,30,95);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_MaxDVH'));
+
+function test_doseObjective_MaxDVH_computeObjFunction
+
+ obj = DoseObjectives.matRad_MaxDVH(100,60,50);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 0;
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ obj = DoseObjectives.matRad_MaxDVH(100,50,95);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 5;
+
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+
+ assertTrue(isnan(test_fDose));
+
+
+function test_doseObjective_MaxDVH_computeGradient
+
+ obj = DoseObjectives.matRad_MaxDVH(100,50,95);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+ expected_fDose = [ 2 0 0 0 0]';
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+
+ assertTrue(isnan(test_fDose(4)));
\ No newline at end of file
diff --git a/test/doseObjectives/test_doseObejctiveMeanDose.m b/test/doseObjectives/test_doseObejctiveMeanDose.m
new file mode 100644
index 000000000..3e36658c1
--- /dev/null
+++ b/test/doseObjectives/test_doseObejctiveMeanDose.m
@@ -0,0 +1,69 @@
+function test_suite = test_doseObejctiveMeanDose
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_doseObjective_squaredDeviation_construct
+
+ obj = DoseObjectives.matRad_MeanDose(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_MeanDose'));
+ assertEqual(obj.parameters{1},0);
+ assertEqual(obj.parameters{2},1);
+ assertEqual(obj.penalty,1);
+
+ obj = DoseObjectives.matRad_MeanDose(100,0,'Linear');
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_MeanDose'));
+
+ obj = DoseObjectives.matRad_MeanDose(100,10,4);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_MeanDose'));
+ assertEqual(obj.parameters{1},10);
+ assertEqual(obj.parameters{2},1);
+
+function test_doseObjective_MeanDose_computeObjFunction
+
+ obj = DoseObjectives.matRad_MeanDose(100,10,'Linear');
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 50;
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ obj = DoseObjectives.matRad_MeanDose(100,0,'Quadratic');
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 3600;
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+
+ assertTrue(isnan(test_fDose));
+
+
+function test_doseObjective_MeanDose_computeGradient
+
+ obj = DoseObjectives.matRad_MeanDose(100,0,'Linear');
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+ expected_fDose = [0.2; 0.2; 0.2; 0.2; 0.2];
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ obj = DoseObjectives.matRad_MeanDose(100,0,'Quadratic');
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+ expected_fDose = [24; 24; 24; 24; 24];
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+
+ assertTrue(isnan(test_fDose(4)));
diff --git a/test/doseObjectives/test_doseObejctiveMinDVH.m b/test/doseObjectives/test_doseObejctiveMinDVH.m
new file mode 100644
index 000000000..ab228bcd4
--- /dev/null
+++ b/test/doseObjectives/test_doseObejctiveMinDVH.m
@@ -0,0 +1,57 @@
+function test_suite = test_doseObejctiveMinDVH
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_doseObjective_MinDVH_construct
+
+ obj = DoseObjectives.matRad_MinDVH(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_MinDVH'));
+ assertEqual(obj.parameters{1},60);
+ assertEqual(obj.parameters{2},95);
+ assertEqual(obj.penalty,1);
+
+ obj = DoseObjectives.matRad_MinDVH(100,60,95);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_MinDVH'));
+
+function test_doseObjective_MinDVH_computeObjFunction
+
+ obj = DoseObjectives.matRad_MinDVH(100,60,50);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 0;
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ obj = DoseObjectives.matRad_MinDVH(100,60,95);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 5.8;
+
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+
+ assertTrue(isnan(test_fDose));
+
+
+function test_doseObjective_MinDVH_computeGradient
+
+ obj = DoseObjectives.matRad_MinDVH(100,60);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+ expected_fDose = [ -2.0000 -0.8000 0 0 0]';
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+
+ assertTrue(isnan(test_fDose(4)));
\ No newline at end of file
diff --git a/test/doseObjectives/test_doseObejctiveSquaredDeviation.m b/test/doseObjectives/test_doseObejctiveSquaredDeviation.m
new file mode 100644
index 000000000..23d465997
--- /dev/null
+++ b/test/doseObjectives/test_doseObejctiveSquaredDeviation.m
@@ -0,0 +1,47 @@
+function test_suite = test_doseObejctiveSquaredDeviation
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_doseObjective_squaredDeviation_construct
+
+ obj = DoseObjectives.matRad_SquaredDeviation(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_SquaredDeviation'));
+ assertEqual(obj.parameters{1},60);
+ assertEqual(obj.penalty,1);
+
+ obj = DoseObjectives.matRad_SquaredDeviation(100,60);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_SquaredDeviation'));
+
+function test_doseObjective_SquaredDeviation_computeObjFunction
+
+ obj = DoseObjectives.matRad_SquaredDeviation(100,60);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 11.6000;
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+
+ assertTrue(isnan(test_fDose));
+
+
+function test_doseObjective_SquaredDeviation_computeGradient
+
+ obj = DoseObjectives.matRad_SquaredDeviation(100,60);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+ expected_fDose = [-2.0000; -0.8000; 0; 0.8000; 2.0000];
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+
+ assertTrue(isnan(test_fDose(4)));
\ No newline at end of file
diff --git a/test/doseObjectives/test_doseObejctiveSquaredOverdosing.m b/test/doseObjectives/test_doseObejctiveSquaredOverdosing.m
new file mode 100644
index 000000000..505b2740c
--- /dev/null
+++ b/test/doseObjectives/test_doseObejctiveSquaredOverdosing.m
@@ -0,0 +1,46 @@
+function test_suite = test_doseObejctiveSquaredOverdosing
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_doseObjective_SquaredOverdosing_construct
+ obj = DoseObjectives.matRad_SquaredOverdosing(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_SquaredOverdosing'));
+ assertEqual(obj.parameters{1},30);
+ assertEqual(obj.penalty,1);
+
+ obj = DoseObjectives.matRad_SquaredOverdosing(100,60);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_SquaredOverdosing'));
+
+function test_doseObjective_SquaredOverdosing_computeObjFunction
+
+ obj = DoseObjectives.matRad_SquaredOverdosing(100,60);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 5.8;
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+
+ assertTrue(isnan(test_fDose));
+
+
+function test_doseObjective_SquaredOverdosing_computeGradient
+
+ obj = DoseObjectives.matRad_SquaredOverdosing(100,60);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+ expected_fDose = [0 0 0 0.8000 2.0000]';
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+
+ assertTrue(isnan(test_fDose(4)));
\ No newline at end of file
diff --git a/test/doseObjectives/test_doseObejctiveSquaredUnderdosing.m b/test/doseObjectives/test_doseObejctiveSquaredUnderdosing.m
new file mode 100644
index 000000000..90800e19c
--- /dev/null
+++ b/test/doseObjectives/test_doseObejctiveSquaredUnderdosing.m
@@ -0,0 +1,47 @@
+function test_suite = test_doseObejctiveSquaredUnderdosing
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_doseObjective_SquaredUnderdosing_construct
+
+ obj = DoseObjectives.matRad_SquaredUnderdosing(); %default
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_SquaredUnderdosing'));
+ assertEqual(obj.parameters{1},60);
+ assertEqual(obj.penalty,1);
+
+ obj = DoseObjectives.matRad_SquaredUnderdosing(100,60);
+ assertTrue(isobject(obj));
+ assertTrue(isa(obj, 'DoseObjectives.matRad_SquaredUnderdosing'));
+
+function test_doseObjective_SquaredUnderdosing_computeObjFunction
+
+ obj = DoseObjectives.matRad_SquaredUnderdosing(100,60);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+ expected_fDose = 5.8;
+
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveFunction(obj,dose);
+
+ assertTrue(isnan(test_fDose));
+
+
+function test_doseObjective_SquaredUnderdosing_computeGradient
+
+ obj = DoseObjectives.matRad_SquaredUnderdosing(100,60);
+
+ dose = [55,58,60,62,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+ expected_fDose = [ -2.0000 -0.8000 0 0 0]';
+ assertElementsAlmostEqual(test_fDose,expected_fDose,'absolute', 1e-4);
+
+ dose = [55,58,60,NaN,65]';
+ test_fDose = computeDoseObjectiveGradient(obj,dose);
+
+ assertTrue(isnan(test_fDose(4)));
\ No newline at end of file
diff --git a/test/gui/test_gui_3Dwidget.m b/test/gui/test_gui_3Dwidget.m
new file mode 100644
index 000000000..e0231d7d5
--- /dev/null
+++ b/test/gui/test_gui_3Dwidget.m
@@ -0,0 +1,60 @@
+function test_suite = test_gui_3Dwidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_3DWidget_constructor
+ % Test the constructor of matRad_3DWidget class
+ h = matRad_3DWidget();
+ assertTrue(isa(h, 'matRad_3DWidget'));
+ assertTrue(isa(h, 'matRad_ViewingWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_3DWidget(p);
+ assertTrue(isa(h, 'matRad_3DWidget'));
+ assertTrue(isa(h, 'matRad_ViewingWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_3Dwidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_3DWidget();
+ try
+ assertTrue(isa(h, 'matRad_3DWidget'));
+ assertTrue(isa(h, 'matRad_ViewingWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ catch ME
+ evalin('base','clear ct cst pln');
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_DVHStatsWidget.m b/test/gui/test_gui_DVHStatsWidget.m
new file mode 100644
index 000000000..0d1024de7
--- /dev/null
+++ b/test/gui/test_gui_DVHStatsWidget.m
@@ -0,0 +1,43 @@
+function test_suite = test_gui_DVHStatsWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_DVHStatsWidget_constructor
+ % Test the constructor of matRad_DVHStatsWidget class
+ h = matRad_DVHStatsWidget();
+ assertTrue(isa(h, 'matRad_DVHStatsWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_DVHStatsWidget(p);
+ assertTrue(isa(h, 'matRad_DVHStatsWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_DVHWidget.m b/test/gui/test_gui_DVHWidget.m
new file mode 100644
index 000000000..0e74675fb
--- /dev/null
+++ b/test/gui/test_gui_DVHWidget.m
@@ -0,0 +1,43 @@
+function test_suite = test_gui_DVHWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_DVHWidget_constructor
+ % Test the constructor of matRad_DVHWidget class
+ h = matRad_DVHWidget();
+ assertTrue(isa(h, 'matRad_DVHWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_DVHWidget(p);
+ assertTrue(isa(h, 'matRad_DVHWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_GammaWidget.m b/test/gui/test_gui_GammaWidget.m
new file mode 100644
index 000000000..a6bfdb74c
--- /dev/null
+++ b/test/gui/test_gui_GammaWidget.m
@@ -0,0 +1,43 @@
+function test_suite = test_gui_GammaWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_GammaWidget_constructor
+ % Test the constructor of matRad_GammaWidget class
+ h = matRad_GammaWidget();
+ assertTrue(isa(h, 'matRad_GammaWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_GammaWidget(p);
+ assertTrue(isa(h, 'matRad_GammaWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_OptimizationWidget.m b/test/gui/test_gui_OptimizationWidget.m
new file mode 100644
index 000000000..a3454dc41
--- /dev/null
+++ b/test/gui/test_gui_OptimizationWidget.m
@@ -0,0 +1,57 @@
+function test_suite = test_gui_OptimizationWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_OptimizationWidget_constructor
+ % Test the constructor of matRad_OptimizationWidget class
+ h = matRad_OptimizationWidget();
+ assertTrue(isa(h, 'matRad_OptimizationWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_OptimizationWidget(p);
+ assertTrue(isa(h, 'matRad_OptimizationWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_OptimizationWidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_OptimizationWidget();
+ try
+ assertTrue(isa(h, 'matRad_OptimizationWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ catch ME
+ evalin('base','clear ct cst pln');
+ delete(h);
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+ delete(h);
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_PlanWidget.m b/test/gui/test_gui_PlanWidget.m
new file mode 100644
index 000000000..483aa8b86
--- /dev/null
+++ b/test/gui/test_gui_PlanWidget.m
@@ -0,0 +1,57 @@
+function test_suite = test_gui_PlanWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_PlanWidget_constructor
+ % Test the constructor of matRad_PlanWidget class
+ h = matRad_PlanWidget();
+ assertTrue(isa(h, 'matRad_PlanWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_PlanWidget(p);
+ assertTrue(isa(h, 'matRad_PlanWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_PlanWidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_PlanWidget();
+ try
+ assertTrue(isa(h, 'matRad_PlanWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ catch ME
+ evalin('base','clear ct cst pln');
+ delete(h);
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+ delete(h);
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_StatisticsWidget.m b/test/gui/test_gui_StatisticsWidget.m
new file mode 100644
index 000000000..ea3db6c9c
--- /dev/null
+++ b/test/gui/test_gui_StatisticsWidget.m
@@ -0,0 +1,43 @@
+function test_suite = test_gui_StatisticsWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_StatisticsWidget_constructor
+ % Test the constructor of matRad_StatisticsWidget class
+ h = matRad_StatisticsWidget();
+ assertTrue(isa(h, 'matRad_StatisticsWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_StatisticsWidget(p);
+ assertTrue(isa(h, 'matRad_StatisticsWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_StructureVisibilityWidget.m b/test/gui/test_gui_StructureVisibilityWidget.m
new file mode 100644
index 000000000..8d083193d
--- /dev/null
+++ b/test/gui/test_gui_StructureVisibilityWidget.m
@@ -0,0 +1,57 @@
+function test_suite = test_gui_StructureVisibilityWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_StructureVisibilityWidget_constructor
+ % Test the constructor of matRad_StructureVisibilityWidget class
+ h = matRad_StructureVisibilityWidget();
+ assertTrue(isa(h, 'matRad_StructureVisibilityWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_StructureVisibilityWidget(p);
+ assertTrue(isa(h, 'matRad_StructureVisibilityWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_StructureVisibilityWidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_StructureVisibilityWidget();
+ try
+ assertTrue(isa(h, 'matRad_StructureVisibilityWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ catch ME
+ evalin('base','clear ct cst pln');
+ delete(h);
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+ delete(h);
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_exportDicomWidget.m b/test/gui/test_gui_exportDicomWidget.m
new file mode 100644
index 000000000..c8c9aa3a5
--- /dev/null
+++ b/test/gui/test_gui_exportDicomWidget.m
@@ -0,0 +1,57 @@
+function test_suite = test_gui_exportDicomWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_exportDicomWidget_constructor
+ % Test the constructor of matRad_exportDicomWidget class
+ h = matRad_exportDicomWidget();
+ assertTrue(isa(h, 'matRad_exportDicomWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_exportDicomWidget(p);
+ assertTrue(isa(h, 'matRad_exportDicomWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_exportDicomWidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_exportDicomWidget();
+ try
+ assertTrue(isa(h, 'matRad_exportDicomWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ catch ME
+ evalin('base','clear ct cst pln');
+ delete(h);
+ rethrow(ME);
+ end
+ delete(h);
+ evalin('base','clear ct cst pln');
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_exportWidget.m b/test/gui/test_gui_exportWidget.m
new file mode 100644
index 000000000..fb39d2a47
--- /dev/null
+++ b/test/gui/test_gui_exportWidget.m
@@ -0,0 +1,58 @@
+function test_suite = test_gui_exportWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_exportWidget_constructor
+ % Test the constructor of matRad_exportWidget class
+ h = matRad_exportWidget();
+ assertTrue(isa(h, 'matRad_exportWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_exportWidget(p);
+ assertTrue(isa(h, 'matRad_exportWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_exportWidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_exportWidget();
+ try
+ assertTrue(isa(h, 'matRad_exportWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+
+ catch ME
+ evalin('base','clear ct cst pln');
+ delete(h);
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+ delete(h);
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_importDicomWidget.m b/test/gui/test_gui_importDicomWidget.m
new file mode 100644
index 000000000..24174db1a
--- /dev/null
+++ b/test/gui/test_gui_importDicomWidget.m
@@ -0,0 +1,43 @@
+function test_suite = test_gui_importDicomWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_importDicomWidget_constructor
+ % Test the constructor of matRad_importDicomWidget class
+ h = matRad_importDicomWidget();
+ assertTrue(isa(h, 'matRad_importDicomWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_importDicomWidget(p);
+ assertTrue(isa(h, 'matRad_importDicomWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_importWidget.m b/test/gui/test_gui_importWidget.m
new file mode 100644
index 000000000..0778d4c5f
--- /dev/null
+++ b/test/gui/test_gui_importWidget.m
@@ -0,0 +1,43 @@
+function test_suite = test_gui_importWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_importWidget_constructor
+ % Test the constructor of matRad_importWidget class
+ h = matRad_importWidget();
+ assertTrue(isa(h, 'matRad_importWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_importWidget(p);
+ assertTrue(isa(h, 'matRad_importWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_infoWidget.m b/test/gui/test_gui_infoWidget.m
new file mode 100644
index 000000000..0a203fe8e
--- /dev/null
+++ b/test/gui/test_gui_infoWidget.m
@@ -0,0 +1,45 @@
+function test_suite = test_gui_infoWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_infoWidget_construct
+ h = matRad_InfoWidget();
+ assertTrue(isa(h,'matRad_Widget'));
+ assertTrue(isgraphics(h.handles.figure1,'figure'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_InfoWidget(p);
+ assertTrue(isa(h,'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_infoWidget_aboutButton
+ h = matRad_InfoWidget();
+ callbackFn = get(h.handles.btnAbout,'Callback');
+ callbackFn(h,[]);
+ delete(h);
\ No newline at end of file
diff --git a/test/gui/test_gui_logoWidget.m b/test/gui/test_gui_logoWidget.m
new file mode 100644
index 000000000..39e9d898d
--- /dev/null
+++ b/test/gui/test_gui_logoWidget.m
@@ -0,0 +1,38 @@
+function test_suite = test_gui_logoWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_logoWidget_construct
+ h = matRad_LogoWidget();
+ assertTrue(isa(h,'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_LogoWidget(p);
+ assertTrue(isa(h,'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
\ No newline at end of file
diff --git a/test/gui/test_gui_viewerOptionsWidget.m b/test/gui/test_gui_viewerOptionsWidget.m
new file mode 100644
index 000000000..ab39148cf
--- /dev/null
+++ b/test/gui/test_gui_viewerOptionsWidget.m
@@ -0,0 +1,57 @@
+function test_suite = test_gui_viewerOptionsWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_viewerOptionsWidget_constructor
+ % Test the constructor of matRad_ViewerOptionsWidget class
+ h = matRad_ViewerOptionsWidget();
+ assertTrue(isa(h, 'matRad_ViewerOptionsWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_ViewerOptionsWidget(p);
+ assertTrue(isa(h, 'matRad_ViewerOptionsWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_viewerOptionsWidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_ViewerOptionsWidget();
+ try
+ assertTrue(isa(h, 'matRad_ViewerOptionsWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ catch ME
+ evalin('base','clear ct cst pln');
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+ delete(h);
+
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_viewingWidget.m b/test/gui/test_gui_viewingWidget.m
new file mode 100644
index 000000000..91a33bd72
--- /dev/null
+++ b/test/gui/test_gui_viewingWidget.m
@@ -0,0 +1,56 @@
+function test_suite = test_gui_viewingWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_viewingWidget_constructor
+ % Test the constructor of matRad_ViewingWidget class
+ h = matRad_ViewingWidget();
+ assertTrue(isa(h, 'matRad_ViewingWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_ViewingWidget(p);
+ assertTrue(isa(h, 'matRad_ViewingWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_viewingWidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_ViewingWidget();
+ try
+ assertTrue(isa(h, 'matRad_ViewingWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ catch ME
+ evalin('base','clear ct cst pln');
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+ delete(h);
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_visualizationWidget.m b/test/gui/test_gui_visualizationWidget.m
new file mode 100644
index 000000000..b6680ce84
--- /dev/null
+++ b/test/gui/test_gui_visualizationWidget.m
@@ -0,0 +1,56 @@
+function test_suite = test_gui_visualizationWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_visualizationWidget_constructor
+ % Test the constructor of matRad_VisualizationWidget class
+ h = matRad_VisualizationWidget();
+ assertTrue(isa(h, 'matRad_VisualizationWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_VisualizationWidget(p);
+ assertTrue(isa(h, 'matRad_VisualizationWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_visualizationWidget_constructWithData
+ evalin('base','load TG119.mat');
+ h = matRad_VisualizationWidget();
+ try
+ assertTrue(isa(h, 'matRad_VisualizationWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ catch ME
+ evalin('base','clear ct cst pln');
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+ delete(h);
+
+%TODO: Test Buttons / visibility depending on data
\ No newline at end of file
diff --git a/test/gui/test_gui_workflowWidget.m b/test/gui/test_gui_workflowWidget.m
new file mode 100644
index 000000000..867ea734d
--- /dev/null
+++ b/test/gui/test_gui_workflowWidget.m
@@ -0,0 +1,54 @@
+function test_suite = test_gui_workflowWidget
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_workflowWidget_basicConstruct
+ h = matRad_WorkflowWidget();
+ assertTrue(isa(h,'matRad_Widget'));
+ delete(h);
+
+ p = uipanel();
+ h = matRad_WorkflowWidget(p);
+ assertTrue(isa(h,'matRad_Widget'));
+ delete(h);
+ close(get(p,'Parent'));
+
+function test_workflowWidget_constructWithData
+
+ evalin('base','load TG119.mat');
+ h = matRad_WorkflowWidget();
+ try
+ assertTrue(isa(h, 'matRad_WorkflowWidget'));
+ assertTrue(isa(h, 'matRad_Widget'));
+ catch ME
+ evalin('base','clear ct cst pln');
+ rethrow(ME);
+ end
+ evalin('base','clear ct cst pln');
+ delete(h);
+
+%TODO: Test Buttons / visibility depending on data
diff --git a/test/gui/test_themes.m b/test/gui/test_themes.m
new file mode 100644
index 000000000..3088c522e
--- /dev/null
+++ b/test/gui/test_themes.m
@@ -0,0 +1,106 @@
+function test_suite = test_themes
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function themes = helper_getThemes()
+ themes = {?matRad_ThemeDark, ?matRad_ThemeLight};
+
+% Test constructor
+function test_light_constructor
+ themes = helper_getThemes();
+ for i = 1:length(themes)
+ theme = str2func(themes{i}.Name);
+ theme = theme();
+ assertTrue(isa(theme, 'matRad_Theme'));
+ end
+
+function test_properties_struct
+ themes = helper_getThemes();
+ for i = 1:length(themes)
+ themeC = str2func(themes{i}.Name);
+ themeC = themeC();
+ theme = struct(themeC);
+
+ % Verify theme name is not empty
+ assertFalse(isempty(theme.name));
+ assertTrue(ischar(theme.name));
+ assertTrue(isrow(theme.name));
+ assertEqual(theme.name, themeC.name);
+
+ % Verify background color size and range
+ assertEqual(size(theme.backgroundColor), [1 3]);
+ assertTrue(all(theme.backgroundColor >= 0));
+ assertTrue(all(theme.backgroundColor <= 1));
+ assertEqual(theme.backgroundColor, themeC.backgroundColor);
+
+ % Verify element color size and range
+ assertEqual(size(theme.elementColor), [1 3]);
+ assertTrue(all(theme.elementColor >= 0));
+ assertTrue(all(theme.elementColor <= 1));
+ assertEqual(theme.elementColor, themeC.elementColor);
+
+ % Verify text color size and range
+ assertEqual(size(theme.textColor), [1 3]);
+ assertTrue(all(theme.textColor >= 0));
+ assertTrue(all(theme.textColor <= 1));
+ assertEqual(theme.textColor, themeC.textColor);
+
+ % Verify highlight color size and range
+ assertEqual(size(theme.highlightColor), [1 3]);
+ assertTrue(all(theme.highlightColor >= 0));
+ assertTrue(all(theme.highlightColor <= 1));
+ assertEqual(theme.highlightColor, themeC.highlightColor);
+
+ % Verify font size is a positive scalar
+ assertTrue(isscalar(theme.fontSize));
+ assertTrue(isnumeric(theme.fontSize));
+ assertTrue(theme.fontSize > 0);
+ assertEqual(theme.fontSize, themeC.fontSize);
+
+ % Verify font weight is not empty and either 'normal' or 'bold'
+ assertFalse(isempty(theme.fontWeight));
+ assertTrue(any(strcmp(theme.fontWeight, {'normal', 'bold'})));
+ assertEqual(theme.fontWeight, themeC.fontWeight);
+
+ % Verify font name is not empty
+ assertFalse(isempty(theme.fontName));
+ assertTrue(ischar(theme.fontName));
+ assertTrue(isrow(theme.fontName));
+ assertEqual(theme.fontName, themeC.fontName);
+
+ % Verify author is not empty
+ assertFalse(isempty(theme.author));
+ assertTrue(ischar(theme.author));
+ assertTrue(isrow(theme.author));
+ assertEqual(theme.author, themeC.author);
+
+ % Verify description is not empty
+ assertFalse(isempty(theme.description));
+ assertTrue(ischar(theme.description));
+ assertTrue(isrow(theme.description));
+ assertEqual(theme.description, themeC.description);
+ end
diff --git a/test/helper_assignmentTest.m b/test/helper_assignmentTest.m
new file mode 100644
index 000000000..81e4646f4
--- /dev/null
+++ b/test/helper_assignmentTest.m
@@ -0,0 +1,7 @@
+function helper_assignmentTest(obj,property,value)
+%helper_assignmentTest Assigns property by name & value
+% This function can be used to test property set methods (e.g. for
+% exceptions
+ obj.(property) = value;
+end
+
diff --git a/test/helper_temporaryFolder.m b/test/helper_temporaryFolder.m
new file mode 100644
index 000000000..addfcb263
--- /dev/null
+++ b/test/helper_temporaryFolder.m
@@ -0,0 +1,36 @@
+function [tmpPath,status] = helper_temporaryFolder(folderName,clearIfExists)
+%helper_temporaryFolder Creates a temporary folder for test data in the
+% users temporary systemdirectory.
+
+matRad_cfg = MatRad_Config.instance();
+if matRad_cfg.isOctave
+ confirm_recursive_rmdir(false,"local");
+end
+
+if nargin < 2
+ clearIfExists = true;
+end
+
+tmpPath = fullfile(tempdir(),folderName);
+
+if isfolder(tmpPath) && clearIfExists
+ status = rmdir(tmpPath,'s');
+ if status
+ status = mkdir(tmpPath);
+ else
+ status = double(isfolder(tmpPath));
+ end
+elseif ~isfolder(tmpPath)
+ status = mkdir(tmpPath);
+else
+ status = 1;
+end
+
+if ~status
+ tmpPath = pwd();
+ warning('temporary directory invalid - using wokring directory!');
+end
+
+
+end
+
diff --git a/unitTest/matRad_unitTestTextManipulation.m b/test/matRad_unitTestTextManipulation.m
similarity index 87%
rename from unitTest/matRad_unitTestTextManipulation.m
rename to test/matRad_unitTestTextManipulation.m
index 7911eeeb5..444943b30 100644
--- a/unitTest/matRad_unitTestTextManipulation.m
+++ b/test/matRad_unitTestTextManipulation.m
@@ -27,7 +27,7 @@ function matRad_unitTestTextManipulation(filename, string1, string2, path)
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
@@ -46,8 +46,11 @@ function matRad_unitTestTextManipulation(filename, string1, string2, path)
currFilename = filename{fIx};
- fid=fopen([path currFilename]);
- fo=fopen('tempFile.m','w');
+ currFile = fullfile(path,currFilename);
+ tempFile = fullfile(path,'tempFile.m');
+
+ fid=fopen(currFile);
+ fo=fopen(tempFile,'w');
tline = fgetl(fid);
while ischar(tline)
@@ -65,7 +68,6 @@ function matRad_unitTestTextManipulation(filename, string1, string2, path)
fclose(fo);
- movefile('tempFile.m', [path currFilename], 'f');
-end
+ movefile(tempFile, currFile, 'f');
end
-
+end
\ No newline at end of file
diff --git a/test/optimizers/test_optimizerFmincon.m b/test/optimizers/test_optimizerFmincon.m
new file mode 100644
index 000000000..3e7c70aa3
--- /dev/null
+++ b/test/optimizers/test_optimizerFmincon.m
@@ -0,0 +1,52 @@
+function test_suite = test_optimizerFmincon
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_optimizer_fmincon_construct
+
+ if moxunit_util_platform_is_octave
+ moxunit_throw_test_skipped_exception('fmincon not available for Octave!');
+ end
+
+ if ~license('test', 'optimization_toolbox') || license('checkout', 'optimization_toolbox') == 0
+ moxunit_throw_test_skipped_exception('Optimization Toolbox containing fmincon not available!');
+ end
+
+ opti = matRad_OptimizerFmincon();
+ assertTrue(isobject(opti));
+ assertTrue(isa(opti, 'matRad_OptimizerFmincon'));
+
+function test_optimizer_fmincon_available
+
+ if moxunit_util_platform_is_octave
+ moxunit_throw_test_skipped_exception('fmincon not available for Octave!');
+ end
+
+ if ~license('test', 'optimization_toolbox') || license('checkout', 'optimization_toolbox') == 0
+ moxunit_throw_test_skipped_exception('Optimization Toolbox containing fmincon not available!');
+ end
+
+ opti = matRad_OptimizerFmincon();
+ assertTrue(opti.IsAvailable());
+ assertTrue(matRad_OptimizerFmincon.IsAvailable()); %Check static
+
+function test_optimizer_fmincon_getStatus
+
+ if moxunit_util_platform_is_octave
+ moxunit_throw_test_skipped_exception('fmincon not available for Octave!');
+ end
+
+ if ~license('test', 'optimization_toolbox') || license('checkout', 'optimization_toolbox') == 0
+ moxunit_throw_test_skipped_exception('Optimization Toolbox containing fmincon not available!');
+ end
+
+ opti = matRad_OptimizerFmincon();
+ [statusmsg, statusflag] = opti.GetStatus();
+ assertEqual(statusmsg, 'No Last Optimizer Status Available!');
+ assertEqual(statusflag, -1);
+
+ % TODO: test other status
+
+% TODO: test optimize function
\ No newline at end of file
diff --git a/test/optimizers/test_optimizerIPOPT.m b/test/optimizers/test_optimizerIPOPT.m
new file mode 100644
index 000000000..a956d526d
--- /dev/null
+++ b/test/optimizers/test_optimizerIPOPT.m
@@ -0,0 +1,28 @@
+function test_suite = test_optimizerIPOPT
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_optimizer_ipopt_construct
+
+ opti = matRad_OptimizerIPOPT();
+ assertTrue(isobject(opti));
+ assertTrue(isa(opti, 'matRad_OptimizerIPOPT'));
+
+function test_optimizer_ipopt_available
+
+ opti = matRad_OptimizerIPOPT();
+ assertTrue(opti.IsAvailable());
+ assertTrue(matRad_OptimizerIPOPT.IsAvailable()); %Check static
+
+function test_optimizer_ipopt_getStatus
+
+ opti = matRad_OptimizerIPOPT();
+ [statusmsg, statusflag] = opti.GetStatus();
+ assertEqual(statusmsg, 'No Last IPOPT Status Available!');
+ assertEqual(statusflag, -1);
+
+ % TODO: test other status
+
+% TODO: test optimize function
\ No newline at end of file
diff --git a/test/plotting/test_computeIsoDoseContours.m b/test/plotting/test_computeIsoDoseContours.m
new file mode 100644
index 000000000..3cf7bd7c0
--- /dev/null
+++ b/test/plotting/test_computeIsoDoseContours.m
@@ -0,0 +1,25 @@
+function test_suite = test_computeIsoDoseContours
+ %The output should always be test_suite, and the function name the same as
+ %your file name
+
+ %To collect all tests defined below, this is needed in newer Matlab
+ %versions. test_functions will collect function handles to below test
+ %functions
+ test_functions=localfunctions();
+
+ % This will initialize the test suite, i.e., take the functions from
+ % test_functions, check if they contain "test", convert them into a MOxUnit
+ % Test Case, and add them to the test-runner
+ initTestSuite;
+
+
+ function test_compute
+ dim = [10,13,15];
+ dose_cube = rand(dim);
+ levels = [0.1,0.5,0.95];
+ isoDoseContours = matRad_computeIsoDoseContours(dose_cube,levels);
+ assertTrue(iscell(isoDoseContours));
+ assertEqual(size(isoDoseContours),[max(dim),3])
+ sz = cellfun(@(c) size(c,1),isoDoseContours(:));
+ assertTrue(all(sz == 0 | sz == 2))
+
\ No newline at end of file
diff --git a/test/plotting/test_computeVoiSurfaces.m b/test/plotting/test_computeVoiSurfaces.m
new file mode 100644
index 000000000..a39e08fdc
--- /dev/null
+++ b/test/plotting/test_computeVoiSurfaces.m
@@ -0,0 +1,23 @@
+function test_suite = test_computeVoiSurfaces
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+
+function test_compute
+
+testpatient = load('photons_testData.mat');
+cst = matRad_computeAllVoiSurfaces(testpatient.ct,testpatient.cst);
+assertTrue(isa(cst,'cell'));
+assertTrue(all(iscell(cst(:,8))));
+assertTrue(all(cellfun(@numel,cst(:,8)) == 2)); % Normals and Vertices
+assertTrue(isequal(cst(:,1:6),testpatient.cst(:,1:6)))
\ No newline at end of file
diff --git a/test/rayTracer/test_rayTracer.m b/test/rayTracer/test_rayTracer.m
new file mode 100644
index 000000000..deca5b62e
--- /dev/null
+++ b/test/rayTracer/test_rayTracer.m
@@ -0,0 +1,239 @@
+function test_suite = test_rayTracer
+
+ test_functions=localfunctions();
+
+ initTestSuite;
+
+ function test_siddeonRayTracer
+
+ % test funcion with dummy nummerical example
+
+ cubes{1} = ones([2,2,2]);
+ cubes{2} = cubes{1};
+ cubes{2}(:,:,2) = [2,2; 2,2];
+
+ resolution.x = 1;
+ resolution.y = 1;
+ resolution.z = 1;
+
+ isocenter = [0,0,0];
+ sourcePoint = [1.5, 1.5, -4];
+ targetPoint = [ 2.5, 2.5, 6];
+
+
+ [alphas,l,rho,d12,ix] = matRad_siddonRayTracer(isocenter,...
+ resolution, ...
+ sourcePoint, ...
+ targetPoint, ...
+ cubes);
+
+ % test Output types
+ assertTrue(isvector(alphas));
+ assertTrue(isvector(l));
+ assertTrue(iscell(rho));
+ assertTrue(isfloat(d12));
+ assertTrue(isvector(ix));
+
+ % test numerical Output
+ entryPoints = [1.95,1.95,0.5;
+ 2.05,2.05,1.5;
+ 2.15,2.15,2.5];
+ entryPoints = entryPoints - sourcePoint;
+ alphasNum = sqrt(sum(entryPoints.^2,2))./sqrt(102);
+ lNum = [sqrt(102)/10,sqrt(102)/10];
+ rhoNum{1} = [1,1];
+ rhoNum{2} = [1,2];
+ d12Num = sqrt(102);
+ ixNum = [4,8];
+
+ assertElementsAlmostEqual(alphasNum',alphas)
+ assertElementsAlmostEqual(lNum,l)
+ assertElementsAlmostEqual(rhoNum{1},rho{1})
+ assertElementsAlmostEqual(rhoNum{2},rho{2})
+ assertElementsAlmostEqual(d12Num,d12)
+ assertEqual(ixNum,ix)
+
+ function test_2DCube
+
+ cubes{1} = ones([2,2]);
+ cubes{2} = cubes{1};
+ cubes{2}(:,2) = [2,2];
+
+ resolution.x = 1;
+ resolution.y = 1;
+ resolution.z = 1;
+
+ isocenter = [0,0,0];
+ sourcePoint = [1.5, 1.5, -4];
+ targetPoint = [ 2.5, 2.5, 6];
+
+
+ [alphas,l,rho,d12,ix] = matRad_siddonRayTracer(isocenter,...
+ resolution, ...
+ sourcePoint, ...
+ targetPoint, ...
+ cubes);
+
+ % test Output types
+ assertTrue(isvector(alphas));
+ assertTrue(isvector(l));
+ assertTrue(iscell(rho));
+ assertTrue(isfloat(d12));
+ assertTrue(isvector(ix));
+
+ % test numerical Output
+ entryPoints = [1.95,1.95,0.5;
+ 2.05,2.05,1.5;];
+ entryPoints = entryPoints - sourcePoint;
+ alphasNum = sqrt(sum(entryPoints.^2,2))./sqrt(102);
+ lNum = [sqrt(102)/10];
+ rhoNum{1} = [1];
+ rhoNum{2} = [2];
+ d12Num = sqrt(102);
+ ixNum = [4];
+
+ assertElementsAlmostEqual(alphasNum',alphas)
+ assertElementsAlmostEqual(lNum,l)
+ assertElementsAlmostEqual(rhoNum{1},rho{1})
+ assertElementsAlmostEqual(rhoNum{2},rho{2})
+ assertElementsAlmostEqual(d12Num,d12)
+ assertEqual(ixNum,ix)
+
+ function test_rayDoesNotHitCT
+
+ cubes{1} = ones([2,2,2]);
+
+ resolution.x = 1;
+ resolution.y = 1;
+ resolution.z = 1;
+
+ isocenter = [0,0,0];
+ sourcePoint = [1.5, 1.5 -4];
+ targetPoint = [ 10, 10, 6];
+
+
+ [alphas,l,rho,d12,ix] = matRad_siddonRayTracer(isocenter,...
+ resolution, ...
+ sourcePoint, ...
+ targetPoint, ...
+ cubes);
+
+
+ % test numerical Output
+ alphasNum = [];
+ lNum = [];
+ rhoNum{1} = [];
+ d12Num = norm(sourcePoint - targetPoint);
+ ixNum = [];
+
+
+ assertElementsAlmostEqual(alphasNum',alphas)
+ assertElementsAlmostEqual(lNum,l)
+ assertElementsAlmostEqual(rhoNum{1},rho{1})
+ assertElementsAlmostEqual(d12Num,d12)
+ assertEqual(ixNum,ix)
+
+ sourcePoint = [10, 10 -4];
+ targetPoint = [ 10, 10, 6];
+ d12Num = 10;
+
+
+ [alphas,l,rho,d12,ix] = matRad_siddonRayTracer(isocenter,...
+ resolution, ...
+ sourcePoint, ...
+ targetPoint, ...
+ cubes);
+
+ assertElementsAlmostEqual(alphasNum',alphas)
+ assertElementsAlmostEqual(lNum,l)
+ assertElementsAlmostEqual(rhoNum{1},rho{1})
+ assertElementsAlmostEqual(d12Num,d12)
+ assertEqual(ixNum,ix)
+
+ function test_rayHitsAtBoundary
+
+ cubes{1} = ones([2,2,2]);
+ cubes{2} = cubes{1};
+ cubes{2}(:,:,2) = [2,2; 2,2];
+
+ resolution.x = 1;
+ resolution.y = 1;
+ resolution.z = 1;
+
+ isocenter = [0,0,0];
+ sourcePoint = [2.5, 2.5, -4];
+ targetPoint = [2.5, 2.5, 6];
+
+
+ [alphas,l,rho,d12,ix] = matRad_siddonRayTracer(isocenter,...
+ resolution, ...
+ sourcePoint, ...
+ targetPoint, ...
+ cubes);
+
+ % test Output types
+ assertTrue(isvector(alphas));
+ assertTrue(isvector(l));
+ assertTrue(iscell(rho));
+ assertTrue(isfloat(d12));
+ assertTrue(isvector(ix));
+
+ % test numerical Output
+ entryPoints = [2.5, 2.5, 0.5;
+ 2.5, 2.5, 1.5;
+ 2.5, 2.5, 2.5];
+ entryPoints = entryPoints - sourcePoint;
+ alphasNum = sqrt(sum(entryPoints.^2,2))./10;
+ lNum = [1,1];
+ rhoNum{1} = [1,1];
+ rhoNum{2} = [1,2];
+ d12Num = 10;
+ ixNum = [4,8];
+
+ assertElementsAlmostEqual(alphasNum',alphas)
+ assertElementsAlmostEqual(lNum,l)
+ assertElementsAlmostEqual(rhoNum{1},rho{1})
+ assertElementsAlmostEqual(rhoNum{2},rho{2})
+ assertElementsAlmostEqual(d12Num,d12)
+ assertEqual(ixNum,ix)
+
+
+ function test_rayHitsAtCorner
+
+ cubes{1} = ones([2,2,2]);
+ cubes{2} = cubes{1};
+ cubes{2}(:,:,2) = [2,2; 2,2];
+
+ resolution.x = 1;
+ resolution.y = 1;
+ resolution.z = 1;
+
+ isocenter = [0,0,0];
+ sourcePoint = [1.5, 1.5, -4];
+ targetPoint = [3.5, 3.5, 5];
+
+
+ [alphas,l,rho,d12,ix] = matRad_siddonRayTracer(isocenter,...
+ resolution, ...
+ sourcePoint, ...
+ targetPoint, ...
+ cubes);
+
+
+ % test numerical Output
+ alphasNum = 0.5;
+ ixNum = 1:0;
+ lNum = [];
+ rhoNum{1} = cubes{1}(ixNum);
+ rhoNum{2} = cubes{2}(ixNum);
+ d12Num = norm(sourcePoint - targetPoint);
+
+
+ assertElementsAlmostEqual(alphasNum',alphas)
+ assertElementsAlmostEqual(lNum,l)
+ assertElementsAlmostEqual(rhoNum{1},rho{1})
+ assertElementsAlmostEqual(rhoNum{2},rho{2})
+ assertElementsAlmostEqual(d12Num,d12)
+ assertEqual(ixNum,ix)
+
+
diff --git a/test/scenarios/helper_mvarGauss.m b/test/scenarios/helper_mvarGauss.m
new file mode 100644
index 000000000..fd83ccd0f
--- /dev/null
+++ b/test/scenarios/helper_mvarGauss.m
@@ -0,0 +1,16 @@
+function p = helper_mvarGauss(model)
+%helper_mvarGauss Computes multivariate Gaussian probability for scenario
+%model
+% Can be used to test correct scenario probabilities. Also considers used
+% ct phases for probability computation (uncorrelated)
+ Sigma = diag([model.shiftSD,model.rangeAbsSD,model.rangeRelSD./100].^2);
+ d = size(Sigma,1);
+ [cs,~] = chol(Sigma);
+
+ % Compute from Gaussian errors
+ p = (2*pi)^(-d/2) * exp(-0.5*sum((model.scenForProb(:,2:end)/cs).^2, 2)) / prod(diag(cs));
+
+ % Now multiplay with the phase probability
+ tmpPhaseProb = arrayfun(@(phase) model.ctScenProb(find(model.ctScenProb(:,1) == phase),2),model.scenForProb(:,1));
+ p = p .* tmpPhaseProb;
+end
\ No newline at end of file
diff --git a/test/scenarios/test_importanceScenarios.m b/test/scenarios/test_importanceScenarios.m
new file mode 100644
index 000000000..120563f06
--- /dev/null
+++ b/test/scenarios/test_importanceScenarios.m
@@ -0,0 +1,189 @@
+function test_suite = test_importanceScenarios
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_importanceScenarioConstructor
+ scenario = matRad_ImportanceScenarios();
+
+ %Defaults
+ nShift = 25;
+ nRange = 9;
+ nTot = nShift + nRange - 1; %Does include the nominal scenario by default
+
+
+ assertTrue(isa(scenario, 'matRad_ImportanceScenarios'));
+ assertTrue(isa(scenario, 'matRad_GriddedScenariosAbstract'));
+ assertTrue(isa(scenario, 'matRad_ScenarioModel'));
+ assertEqual(scenario.shortName, 'impScen');
+ %Test correct standard values & sizes
+ assertEqual(scenario.ctScenProb, [1 1]);
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, nTot);
+ assertEqual(scenario.totNumShiftScen, nShift);
+ assertEqual(scenario.totNumRangeScen, nRange);
+ assertEqual(size(scenario.relRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.absRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.isoShift),[scenario.totNumScen,3]);
+ %assertEqual(scenario.isoShift, 2.25 * ones(1,3));
+ assertEqual(scenario.maxAbsRangeShift, max(scenario.absRangeShift));
+ assertEqual(scenario.maxRelRangeShift, max(scenario.relRangeShift));
+ assertEqual(size(scenario.scenMask), [scenario.numOfCtScen,scenario.totNumShiftScen,scenario.totNumRangeScen]);
+ %assertEqual(scenario.scenMask, true(1,1,1));
+ assertEqual(size(scenario.linearMask), [scenario.totNumScen,3]);
+
+ [tmp(:,1),tmp(:,2),tmp(:,3)] = ind2sub(size(scenario.scenMask),find(scenario.scenMask));
+ assertEqual(tmp,scenario.linearMask);
+
+ %assertEqual(ind2sub(find()))
+ %assertEqual(scenario.linearMask, [1 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+
+ tmp = [scenario.ctScenIx scenario.isoShift scenario.absRangeShift scenario.relRangeShift];
+ assertEqual(scenario.scenForProb,tmp);
+ assertEqual(scenario.scenWeight,scenario.scenProb./sum(scenario.scenProb));
+
+function test_importanceScenarioConstructorWithCt
+ n = 5;
+ ct = struct('numOfCtScen',n);
+
+ %Defaults
+ nShift = 25;
+ nRange = 9;
+ nTot = nShift + nRange - 1; %Does include the nominal scenario by default
+
+ scenario = matRad_ImportanceScenarios(ct);
+ assertTrue(isa(scenario, 'matRad_ImportanceScenarios'));
+ assertTrue(isa(scenario, 'matRad_GriddedScenariosAbstract'));
+ assertTrue(isa(scenario, 'matRad_ScenarioModel'));
+ assertEqual(scenario.shortName, 'impScen');
+ %Test correct standard values & sizes
+ assertEqual(scenario.ctScenProb, [(1:n)' ones(n,1)./n]);
+ assertEqual(scenario.numOfCtScen, n);
+ assertEqual(scenario.totNumScen, nTot*n);
+ assertEqual(scenario.totNumShiftScen, nShift);
+ assertEqual(scenario.totNumRangeScen, nRange);
+ assertEqual(size(scenario.relRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.absRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.isoShift),[scenario.totNumScen,3]);
+ %assertEqual(scenario.isoShift, 2.25 * ones(1,3));
+ assertEqual(scenario.maxAbsRangeShift, max(scenario.absRangeShift));
+ assertEqual(scenario.maxRelRangeShift, max(scenario.relRangeShift));
+ assertEqual(size(scenario.scenMask), [scenario.numOfCtScen,scenario.totNumShiftScen,scenario.totNumRangeScen]);
+ %assertEqual(scenario.scenMask, true(1,1,1));
+ assertEqual(size(scenario.linearMask), [scenario.totNumScen,3]);
+
+ tmpScenMask = permute(scenario.scenMask,[2 3 1]);
+
+ [tmp(:,2),tmp(:,3),tmp(:,1)] = ind2sub(size(tmpScenMask),find(tmpScenMask));
+ assertEqual(tmp,scenario.linearMask);
+
+ %assertEqual(ind2sub(find()))
+ %assertEqual(scenario.linearMask, [1 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+
+ tmp = [scenario.ctScenIx scenario.isoShift scenario.absRangeShift scenario.relRangeShift];
+ assertEqual(scenario.scenForProb,tmp);
+ assertEqual(scenario.scenWeight,scenario.scenProb./sum(scenario.scenProb));
+
+
+function test_importanceScenarioExtractSingleScenario
+ refScen = matRad_ImportanceScenarios();
+ for scenNum = 1:refScen.totNumScen
+ scenario = refScen.extractSingleScenario(scenNum);
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ ctScenIx = refScen.ctScenIx(scenNum);
+ ctScenNum = find(ctScenIx == refScen.ctScenProb(:,1));
+ assertEqual(scenario.ctScenProb, refScen.ctScenProb(ctScenNum,:));
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 1);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, refScen.relRangeShift(scenNum));
+ assertEqual(scenario.absRangeShift, refScen.absRangeShift(scenNum));
+ assertEqual(scenario.isoShift, refScen.isoShift(scenNum,:));
+ assertEqual(scenario.maxAbsRangeShift, max(abs(refScen.absRangeShift(scenNum))));
+ assertEqual(scenario.maxRelRangeShift, max(abs(refScen.relRangeShift(scenNum))));
+ assertTrue(scenario.scenMask(ctScenIx,1,1));
+ assertTrue(numel(find(scenario.scenMask)) == 1);
+ assertEqual(scenario.linearMask, [ctScenIx 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,refScen.scenForProb(scenNum,:));
+ assertEqual(scenario.scenWeight, refScen.scenWeight(scenNum));
+ end
+
+
+function test_importanceScenarioExtractSingleScenarioWithCtScen
+ n = 5;
+ ct = struct('numOfCtScen',n);
+ refScen = matRad_ImportanceScenarios(ct);
+
+ for scenNum = 1:refScen.totNumScen
+ scenario = refScen.extractSingleScenario(scenNum);
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ ctScenIx = refScen.ctScenIx(scenNum);
+ ctScenNum = find(ctScenIx == refScen.ctScenProb(:,1));
+ assertEqual(scenario.ctScenProb, refScen.ctScenProb(ctScenNum,:));
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 1);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, refScen.relRangeShift(scenNum));
+ assertEqual(scenario.absRangeShift, refScen.absRangeShift(scenNum));
+ assertEqual(scenario.isoShift, refScen.isoShift(scenNum,:));
+ assertEqual(scenario.maxAbsRangeShift, max(abs(refScen.absRangeShift(scenNum))));
+ assertEqual(scenario.maxRelRangeShift, max(abs(refScen.relRangeShift(scenNum))));
+ assertTrue(scenario.scenMask(ctScenIx,1,1));
+ assertTrue(numel(find(scenario.scenMask)) == 1);
+ assertEqual(scenario.linearMask, [ctScenIx 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,refScen.scenForProb(scenNum,:));
+ assertEqual(scenario.scenWeight, refScen.scenWeight(scenNum));
+ end
+
+
+ function test_importanceScenarioCombineRange
+
+ model = matRad_ImportanceScenarios();
+
+ assertExceptionThrown(@() helper_assignmentTest(model,'combineRange','hello'),'matRad:Error');
+ assertTrue(model.combineRange);
+
+ nRangeScen = model.totNumRangeScen;
+
+ model.combineRange = false;
+ assertFalse(model.combineRange);
+ assertEqual(model.totNumRangeScen,nRangeScen^2);
+ assertEqual(model.totNumScen,model.totNumRangeScen - 1 + model.totNumShiftScen);
+
+ function test_importanceScenarioShiftCombinations
+
+ model = matRad_ImportanceScenarios();
+
+ assertExceptionThrown(@() helper_assignmentTest(model,'combinations','hello'),'matRad:Error');
+ assertEqual(model.combinations,'none');
+
+ nSetupPoints = model.numOfSetupGridPoints;
+ nRangePoints = model.numOfRangeGridPoints;
+
+ model.combinations = 'shift';
+ assertEqual(model.combinations,'shift');
+ assertEqual(model.totNumShiftScen,nSetupPoints^3);
+ assertEqual(model.totNumScen,model.totNumRangeScen - 1 + model.totNumShiftScen);
+
+ model.combinations = 'all';
+ assertEqual(model.combinations,'all');
+ assertEqual(model.totNumShiftScen,nSetupPoints^3);
+ assertEqual(model.totNumRangeScen,nRangePoints);
+ assertEqual(model.totNumScen,model.totNumRangeScen * model.totNumShiftScen);
+
+ model.combineRange = false;
+ assertEqual(model.totNumShiftScen,nSetupPoints^3);
+ assertEqual(model.totNumRangeScen,nRangePoints^2);
+ assertEqual(model.totNumScen,model.totNumRangeScen * model.totNumShiftScen);
+
+ model.combinations = 'shift';
+ assertEqual(model.totNumShiftScen,nSetupPoints^3);
+ assertEqual(model.totNumRangeScen,nRangePoints^2);
+ assertEqual(model.totNumScen,model.totNumRangeScen + model.totNumShiftScen - 1);
\ No newline at end of file
diff --git a/test/scenarios/test_multScen.m b/test/scenarios/test_multScen.m
new file mode 100644
index 000000000..6f93a6c8b
--- /dev/null
+++ b/test/scenarios/test_multScen.m
@@ -0,0 +1,33 @@
+function test_suite = test_multScen
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_multScenConstructWithCt
+ ct.numOfCtScen = 1;
+ model = matRad_multScen(ct,'nomScen');
+ assertTrue(isa(model,'matRad_NominalScenario'));
+
+ model = matRad_multScen(ct,'wcScen');
+ assertTrue(isa(model,'matRad_WorstCaseScenarios'));
+
+ model = matRad_multScen(ct,'rndScen');
+ assertTrue(isa(model,'matRad_RandomScenarios'));
+
+ model = matRad_multScen(ct,'impScen');
+ assertTrue(isa(model,'matRad_ImportanceScenarios'));
+
+function test_multScenConstructWithEmptyCt
+ ct = [];
+ model = matRad_multScen(ct,'nomScen');
+ assertTrue(isa(model,'matRad_NominalScenario'));
+
+ model = matRad_multScen(ct,'wcScen');
+ assertTrue(isa(model,'matRad_WorstCaseScenarios'));
+
+ model = matRad_multScen(ct,'rndScen');
+ assertTrue(isa(model,'matRad_RandomScenarios'));
+
+ model = matRad_multScen(ct,'impScen');
+ assertTrue(isa(model,'matRad_ImportanceScenarios'));
\ No newline at end of file
diff --git a/test/scenarios/test_nominalScenario.m b/test/scenarios/test_nominalScenario.m
new file mode 100644
index 000000000..10e866a31
--- /dev/null
+++ b/test/scenarios/test_nominalScenario.m
@@ -0,0 +1,84 @@
+function test_suite = test_nominalScenario
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_nominalScenarioConstructor
+ scenario = matRad_NominalScenario();
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ assertTrue(isa(scenario, 'matRad_ScenarioModel'));
+ assertEqual(scenario.shortName, 'nomScen');
+ assertEqual(scenario.ctScenProb, [1 1]);
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 1);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, 0);
+ assertEqual(scenario.absRangeShift, 0);
+ assertEqual(scenario.isoShift, zeros(1,3));
+ assertEqual(scenario.maxAbsRangeShift, 0);
+ assertEqual(scenario.maxRelRangeShift, 0);
+ assertEqual(scenario.scenMask, true(1,1,1));
+ assertEqual(scenario.linearMask, [1 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,[1 zeros(1,5)]);
+ assertEqual(scenario.scenWeight, 1);
+
+function test_nominalScenarioConstructorWithCt
+ n = 5;
+ ct = struct('numOfCtScen',n);
+ scenario = matRad_NominalScenario(ct);
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ assertTrue(isa(scenario, 'matRad_ScenarioModel'));
+ assertEqual(scenario.shortName, 'nomScen');
+ assertEqual(scenario.ctScenProb, [(1:n)' ones(n,1)./n]);
+ assertEqual(scenario.numOfCtScen, n);
+ assertEqual(scenario.totNumScen, n);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, zeros(n,1));
+ assertEqual(scenario.absRangeShift, zeros(n,1));
+ assertEqual(scenario.isoShift, zeros(n,3));
+ assertEqual(scenario.maxAbsRangeShift, 0);
+ assertEqual(scenario.maxRelRangeShift, 0);
+ assertTrue(isequal(scenario.scenMask, true(n,1,1)));
+ assertEqual(scenario.linearMask, [(1:n)' ones(n,2)]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,[(1:n)' zeros(n,5)]);
+ assertEqual(scenario.scenWeight, ones(n,1)./n);
+
+function test_nominalScenarioExtractSingleScenario
+ scenario = matRad_NominalScenario();
+ newInstance = scenario.extractSingleScenario(1);
+ assertTrue(isequal(scenario,newInstance));
+
+function test_nominalScenarioExtractSingleScenarioWithCtScen
+ n = 5;
+ ct = struct('numOfCtScen',n);
+ refScen = matRad_NominalScenario(ct);
+
+ for scenNum = 1:refScen.totNumScen
+ scenario = refScen.extractSingleScenario(scenNum);
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ ctScenIx = refScen.ctScenIx(scenNum);
+ ctScenNum = find(ctScenIx == refScen.ctScenProb(:,1));
+ assertEqual(scenario.ctScenProb, refScen.ctScenProb(ctScenNum,:));
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 1);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, refScen.relRangeShift(scenNum));
+ assertEqual(scenario.absRangeShift, refScen.absRangeShift(scenNum));
+ assertEqual(scenario.isoShift, refScen.isoShift(scenNum,:));
+ assertEqual(scenario.maxAbsRangeShift, max(abs(refScen.absRangeShift(scenNum))));
+ assertEqual(scenario.maxRelRangeShift, max(abs(refScen.relRangeShift(scenNum))));
+ assertTrue(scenario.scenMask(ctScenIx,1,1));
+ assertTrue(numel(find(scenario.scenMask)) == 1);
+ assertEqual(scenario.linearMask, [ctScenIx 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,refScen.scenForProb(scenNum,:));
+ assertEqual(scenario.scenWeight, refScen.scenWeight(scenNum));
+ end
+
+
diff --git a/test/scenarios/test_randomScenarios.m b/test/scenarios/test_randomScenarios.m
new file mode 100644
index 000000000..5304bea59
--- /dev/null
+++ b/test/scenarios/test_randomScenarios.m
@@ -0,0 +1,137 @@
+function test_suite = test_randomScenarios
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_randomScenarioConstructor
+ scenario = matRad_RandomScenarios();
+
+ %Defaults
+ nSamples = 10;
+
+ assertTrue(isa(scenario, 'matRad_RandomScenarios'));
+ assertTrue(isa(scenario, 'matRad_ScenarioModel'));
+ assertEqual(scenario.shortName, 'rndScen');
+ %Test correct standard values & sizes
+ assertEqual(scenario.ctScenProb, [1 1]);
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, nSamples);
+ assertEqual(scenario.totNumShiftScen, nSamples);
+ assertEqual(scenario.totNumRangeScen, nSamples);
+ assertEqual(size(scenario.relRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.absRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.isoShift),[scenario.totNumScen,3]);
+ %assertEqual(scenario.isoShift, 2.25 * ones(1,3));
+ assertEqual(scenario.maxAbsRangeShift, max(scenario.absRangeShift));
+ assertEqual(scenario.maxRelRangeShift, max(scenario.relRangeShift));
+ assertEqual(size(scenario.scenMask), [scenario.numOfCtScen,scenario.totNumShiftScen,scenario.totNumRangeScen]);
+ %assertEqual(scenario.scenMask, true(1,1,1));
+ assertEqual(size(scenario.linearMask), [scenario.totNumScen,3]);
+
+ tmpScenMask = permute(scenario.scenMask,[2 3 1]);
+ [tmp(:,2),tmp(:,3),tmp(:,1)] = ind2sub(size(tmpScenMask),find(tmpScenMask));
+ assertEqual(tmp,scenario.linearMask);
+
+ %assertEqual(ind2sub(find()))
+ %assertEqual(scenario.linearMask, [1 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+
+ tmp = [scenario.ctScenIx scenario.isoShift scenario.absRangeShift scenario.relRangeShift];
+ assertEqual(scenario.scenForProb,tmp);
+
+ assertEqual(numel(unique(scenario.scenWeight)),scenario.numOfCtScen);
+
+function test_randomScenarioConstructorWithCt
+ n = 5;
+ ct = struct('numOfCtScen',n);
+
+ %Defaults
+ nSamples = 10;
+
+ scenario = matRad_RandomScenarios(ct);
+ assertTrue(isa(scenario, 'matRad_RandomScenarios'));
+ assertTrue(isa(scenario, 'matRad_ScenarioModel'));
+ assertEqual(scenario.shortName, 'rndScen');
+ %Test correct standard values & sizes
+ assertEqual(scenario.ctScenProb, [(1:n)' ones(n,1)./n]);
+ assertEqual(scenario.numOfCtScen, n);
+ assertEqual(scenario.totNumScen, nSamples*n);
+ assertEqual(scenario.totNumShiftScen, nSamples);
+ assertEqual(scenario.totNumRangeScen, nSamples);
+ assertEqual(size(scenario.relRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.absRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.isoShift),[scenario.totNumScen,3]);
+ %assertEqual(scenario.isoShift, 2.25 * ones(1,3));
+ assertEqual(scenario.maxAbsRangeShift, max(scenario.absRangeShift));
+ assertEqual(scenario.maxRelRangeShift, max(scenario.relRangeShift));
+ assertEqual(size(scenario.scenMask), [scenario.numOfCtScen,scenario.totNumShiftScen,scenario.totNumRangeScen]);
+ %assertEqual(scenario.scenMask, true(1,1,1));
+ assertEqual(size(scenario.linearMask), [scenario.totNumScen,3]);
+
+ tmpScenMask = permute(scenario.scenMask,[2 3 1]);
+ [tmp(:,2),tmp(:,3),tmp(:,1)] = ind2sub(size(tmpScenMask),find(tmpScenMask));
+ assertEqual(tmp,scenario.linearMask);
+
+ %assertEqual(ind2sub(find()))
+ %assertEqual(scenario.linearMask, [1 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+
+ tmp = [scenario.ctScenIx scenario.isoShift scenario.absRangeShift scenario.relRangeShift];
+ assertEqual(scenario.scenForProb,tmp);
+ assertEqual(numel(unique(scenario.scenWeight)),numel(unique(scenario.ctScenProb(:,2))));
+
+
+function test_randomScenarioExtractSingleScenario
+ refScen = matRad_RandomScenarios();
+ for scenNum = 1:refScen.totNumScen
+ scenario = refScen.extractSingleScenario(scenNum);
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ ctScenIx = refScen.ctScenIx(scenNum);
+ ctScenNum = find(ctScenIx == refScen.ctScenProb(:,1));
+ assertEqual(scenario.ctScenProb, refScen.ctScenProb(ctScenNum,:));
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 1);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, refScen.relRangeShift(scenNum));
+ assertEqual(scenario.absRangeShift, refScen.absRangeShift(scenNum));
+ assertEqual(scenario.isoShift, refScen.isoShift(scenNum,:));
+ assertEqual(scenario.maxAbsRangeShift, max(abs(refScen.absRangeShift(scenNum))));
+ assertEqual(scenario.maxRelRangeShift, max(abs(refScen.relRangeShift(scenNum))));
+ assertTrue(scenario.scenMask(ctScenIx,1,1));
+ assertTrue(numel(find(scenario.scenMask)) == 1);
+ assertEqual(scenario.linearMask, [ctScenIx 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,refScen.scenForProb(scenNum,:));
+ assertEqual(scenario.scenWeight, refScen.scenWeight(scenNum));
+ end
+
+
+function test_randomScenarioExtractSingleScenarioWithCtScen
+ n = 5;
+ ct = struct('numOfCtScen',n);
+ refScen = matRad_RandomScenarios(ct);
+ for scenNum = 1:refScen.totNumScen
+ scenario = refScen.extractSingleScenario(scenNum);
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ ctScenIx = refScen.ctScenIx(scenNum);
+ ctScenNum = find(ctScenIx == refScen.ctScenProb(:,1));
+ assertEqual(scenario.ctScenProb, refScen.ctScenProb(ctScenNum,:));
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 1);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, refScen.relRangeShift(scenNum));
+ assertEqual(scenario.absRangeShift, refScen.absRangeShift(scenNum));
+ assertEqual(scenario.isoShift, refScen.isoShift(scenNum,:));
+ assertEqual(scenario.maxAbsRangeShift, max(abs(refScen.absRangeShift(scenNum))));
+ assertEqual(scenario.maxRelRangeShift, max(abs(refScen.relRangeShift(scenNum))));
+ assertTrue(scenario.scenMask(ctScenIx,1,1));
+ assertTrue(numel(find(scenario.scenMask)) == 1);
+ assertEqual(scenario.linearMask, [ctScenIx 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,refScen.scenForProb(scenNum,:));
+ assertEqual(scenario.scenWeight, refScen.scenWeight(scenNum));
+ end
+
diff --git a/test/scenarios/test_scenarioModel.m b/test/scenarios/test_scenarioModel.m
new file mode 100644
index 000000000..18ec75eed
--- /dev/null
+++ b/test/scenarios/test_scenarioModel.m
@@ -0,0 +1,108 @@
+function test_suite = test_scenarioModel
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+%Add automated instance tests for avaiable models
+ct.numOfCtScen = 5;
+models = {matRad_NominalScenario(),matRad_WorstCaseScenarios(),matRad_ImportanceScenarios(),matRad_RandomScenarios(),...
+ matRad_NominalScenario(ct),matRad_WorstCaseScenarios(ct),matRad_ImportanceScenarios(ct),matRad_RandomScenarios(ct)};
+
+instanceTests = cellfun(@func2str,test_functions,'UniformOutput',false);
+funIx = ~cellfun(@isempty,strfind(instanceTests,'instanceTest_'));
+instanceTests = instanceTests(funIx);
+instanceTestHandles = cellfun(@str2func,instanceTests,'UniformOutput',false);
+
+for m = 1:numel(models)
+ model = models{m};
+ for i = 1:numel(instanceTests)
+ funHandle = @() instanceTestHandles{i}(model);
+ test_case=MOxUnitFunctionHandleTestCase([instanceTests{i} '_' class(model)],mfilename,funHandle);
+ test_suite=addTest(test_suite, test_case);
+ end
+end
+
+function assignmentTestHelper(model,property,value)
+ model.(property) = value;
+
+function test_scenarioAbstract
+ if moxunit_util_platform_is_octave()
+ assertExceptionThrown(@() matRad_ScenarioModel(),'');
+ else
+ assertExceptionThrown(@() matRad_ScenarioModel(),'MATLAB:class:abstract');
+ end
+
+function test_scenarioAbstractStaticAvailableModels()
+ availableTypes = matRad_ScenarioModel.getAvailableModels();
+ assertTrue(isstruct(availableTypes) && all(isfield(availableTypes,{'shortName','className','handle'})))
+ availableTypes = {availableTypes.shortName};
+ assertTrue(iscell(availableTypes))
+ assertTrue(all(cellfun(@ischar,availableTypes)));
+
+ for i = 1:numel(availableTypes)
+ model = matRad_ScenarioModel.create(availableTypes{i});
+ assertTrue(isa(model,'matRad_ScenarioModel'));
+ assertEqual(model.shortName,availableTypes{i});
+ end
+
+
+function instanceTest_listAllScenarios(model)
+ model.listAllScenarios();
+ assertTrue(true); %Will be reached if above call does not fail
+
+function instanceTest_relRangeUncertainty(model)
+ newValue = 0.01;
+ model.rangeRelSD = newValue;
+ getValue = model.rangeRelSD;
+ assertEqual(newValue,getValue);
+ assertExceptionThrown(@() assignmentTestHelper(model,'rangeRelSD','a'),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'rangeRelSD',ones(2)),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'rangeRelSD',-1),'matRad:Error');
+
+function instanceTest_absRangeUncertainty(model)
+ newValue = 2;
+ model.rangeAbsSD = newValue;
+ getValue = model.rangeAbsSD;
+ assertEqual(newValue,getValue);
+ assertExceptionThrown(@() assignmentTestHelper(model,'rangeAbsSD','a'),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'rangeAbsSD',ones(2)),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'rangeAbsSD',-1),'matRad:Error');
+
+function instanceTest_shiftUncertainty(model)
+ newValue = [1 1 1];
+ model.shiftSD = newValue;
+ getValue = model.shiftSD;
+ assertEqual(newValue,getValue);
+ assertExceptionThrown(@() assignmentTestHelper(model,'shiftSD','a'),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'shiftSD',ones(3,3)),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'shiftSD',ones(3,1)),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'shiftSD',[-1 2 2]),'matRad:Error');
+
+function instanceTest_wcSigma(model)
+ newValue = 5;
+ model.wcSigma = newValue;
+ getValue = model.wcSigma;
+ assertEqual(newValue,getValue);
+ assertExceptionThrown(@() assignmentTestHelper(model,'wcSigma','a'),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'wcSigma',-1),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'wcSigma',ones(2)),'matRad:Error');
+
+function instanceTest_ctScenProb(model)
+ newValue = rand(model.numOfCtScen,1);
+ newValue = newValue ./ sum(newValue);
+ model.ctScenProb(:,2) = newValue;
+ getValue = model.ctScenProb(:,2);
+ assertEqual(newValue,getValue);
+ assertExceptionThrown(@() assignmentTestHelper(model,'ctScenProb','a'),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'ctScenProb',ones(1,5)),'matRad:Error');
+ assertExceptionThrown(@() assignmentTestHelper(model,'ctScenProb',-1*ones(model.numOfCtScen,1)),'matRad:Error');
+
+function instanceTest_TYPE(model)
+ %assertWarning(@() model.TYPE,'matRad:Deprecated');
+ assertEqual(model.TYPE,model.shortName);
+
+function instanceTest_wcFactor(model)
+ %assertWarning(@() model.wcFactor,'matRad:Deprecated');
+ assertEqual(model.TYPE,model.shortName);
+
diff --git a/test/scenarios/test_wcScenarios.m b/test/scenarios/test_wcScenarios.m
new file mode 100644
index 000000000..def02eb5a
--- /dev/null
+++ b/test/scenarios/test_wcScenarios.m
@@ -0,0 +1,178 @@
+function test_suite = test_wcScenarios
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_worstCaseScenarioConstructor
+ scenario = matRad_WorstCaseScenarios();
+ assertTrue(isa(scenario, 'matRad_WorstCaseScenarios'));
+ assertTrue(isa(scenario, 'matRad_GriddedScenariosAbstract'));
+ assertTrue(isa(scenario, 'matRad_ScenarioModel'));
+ assertEqual(scenario.shortName, 'wcScen');
+ %Test correct standard values & sizes
+ assertEqual(scenario.ctScenProb, [1 1]);
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 9);
+ assertEqual(scenario.totNumShiftScen, 7);
+ assertEqual(scenario.totNumRangeScen, 3);
+ assertEqual(size(scenario.relRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.absRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.isoShift),[scenario.totNumScen,3]);
+ assertEqual(scenario.relRangeShift, [zeros(7,1); -0.035; 0.035]);
+ assertEqual(scenario.absRangeShift, [zeros(7,1); -1; 1]);
+ %assertEqual(scenario.isoShift, 2.25 * ones(1,3));
+ assertEqual(scenario.maxAbsRangeShift, max(scenario.absRangeShift));
+ assertEqual(scenario.maxRelRangeShift, max(scenario.relRangeShift));
+ assertEqual(size(scenario.scenMask), [scenario.numOfCtScen,scenario.totNumShiftScen,scenario.totNumRangeScen]);
+ %assertEqual(scenario.scenMask, true(1,1,1));
+ assertEqual(size(scenario.linearMask), [scenario.totNumScen,3]);
+
+ [tmp(:,1),tmp(:,2),tmp(:,3)] = ind2sub(size(scenario.scenMask),find(scenario.scenMask));
+ assertEqual(tmp,scenario.linearMask);
+
+ %assertEqual(ind2sub(find()))
+ %assertEqual(scenario.linearMask, [1 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+
+ tmp = [scenario.ctScenIx scenario.isoShift scenario.absRangeShift scenario.relRangeShift];
+ assertEqual(scenario.scenForProb,tmp);
+ assertEqual(scenario.scenWeight,scenario.scenProb./sum(scenario.scenProb));
+
+function test_worstCaseScenarioConstructorWithCt
+ n = 5;
+ ct = struct('numOfCtScen',n);
+
+ scenario = matRad_WorstCaseScenarios(ct);
+ assertTrue(isa(scenario, 'matRad_WorstCaseScenarios'));
+ assertTrue(isa(scenario, 'matRad_GriddedScenariosAbstract'));
+ assertTrue(isa(scenario, 'matRad_ScenarioModel'));
+ assertEqual(scenario.shortName, 'wcScen');
+ %Test correct standard values & sizes
+ assertEqual(scenario.ctScenProb, [(1:n)' ones(n,1)./n]);
+ assertEqual(scenario.numOfCtScen, n);
+ assertEqual(scenario.totNumScen, 9*n);
+ assertEqual(scenario.totNumShiftScen, 7);
+ assertEqual(scenario.totNumRangeScen, 3);
+ assertEqual(size(scenario.relRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.absRangeShift),[scenario.totNumScen,1]);
+ assertEqual(size(scenario.isoShift),[scenario.totNumScen,3]);
+ assertEqual(scenario.relRangeShift, repmat([zeros(7,1); -0.035; 0.035],n,1));
+ assertEqual(scenario.absRangeShift, repmat([zeros(7,1); -1; 1],n,1));
+ %assertEqual(scenario.isoShift, 2.25 * ones(1,3));
+ assertEqual(scenario.maxAbsRangeShift, max(scenario.absRangeShift));
+ assertEqual(scenario.maxRelRangeShift, max(scenario.relRangeShift));
+ assertEqual(size(scenario.scenMask), [scenario.numOfCtScen,scenario.totNumShiftScen,scenario.totNumRangeScen]);
+ %assertEqual(scenario.scenMask, true(1,1,1));
+ assertEqual(size(scenario.linearMask), [scenario.totNumScen,3]);
+
+ tmpScenMask = permute(scenario.scenMask,[2 3 1]);
+
+ [tmp(:,2),tmp(:,3),tmp(:,1)] = ind2sub(size(tmpScenMask),find(tmpScenMask));
+ assertEqual(tmp,scenario.linearMask);
+
+ %assertEqual(ind2sub(find()))
+ %assertEqual(scenario.linearMask, [1 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+
+ tmp = [scenario.ctScenIx scenario.isoShift scenario.absRangeShift scenario.relRangeShift];
+ assertEqual(scenario.scenForProb,tmp);
+ assertEqual(scenario.scenWeight,scenario.scenProb./sum(scenario.scenProb));
+
+
+function test_worstCaseScenarioExtractSingleScenario
+ refScen = matRad_WorstCaseScenarios();
+
+ for scenNum = 1:refScen.totNumScen
+ scenario = refScen.extractSingleScenario(scenNum);
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ ctScenIx = refScen.ctScenIx(scenNum);
+ ctScenNum = find(ctScenIx == refScen.ctScenProb(:,1));
+ assertEqual(scenario.ctScenProb, refScen.ctScenProb(ctScenNum,:));
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 1);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, refScen.relRangeShift(scenNum));
+ assertEqual(scenario.absRangeShift, refScen.absRangeShift(scenNum));
+ assertEqual(scenario.isoShift, refScen.isoShift(scenNum,:));
+ assertEqual(scenario.maxAbsRangeShift, max(abs(refScen.absRangeShift(scenNum))));
+ assertEqual(scenario.maxRelRangeShift, max(abs(refScen.relRangeShift(scenNum))));
+ assertTrue(scenario.scenMask(ctScenIx,1,1));
+ assertTrue(numel(find(scenario.scenMask)) == 1);
+ assertEqual(scenario.linearMask, [ctScenIx 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,refScen.scenForProb(scenNum,:));
+ assertEqual(scenario.scenWeight, refScen.scenWeight(scenNum));
+ end
+
+
+function test_worstCaseScenarioExtractSingleScenarioWithCtScen
+ n = 5;
+ scenNum = 1;
+ ct = struct('numOfCtScen',n);
+ refScen = matRad_WorstCaseScenarios(ct);
+ for scenNum = 1:refScen.totNumScen
+ scenario = refScen.extractSingleScenario(scenNum);
+ assertTrue(isa(scenario, 'matRad_NominalScenario'));
+ ctScenIx = refScen.ctScenIx(scenNum);
+ ctScenNum = find(ctScenIx == refScen.ctScenProb(:,1));
+ assertEqual(scenario.ctScenProb, refScen.ctScenProb(ctScenNum,:));
+ assertEqual(scenario.numOfCtScen, 1);
+ assertEqual(scenario.totNumScen, 1);
+ assertEqual(scenario.totNumShiftScen, 1);
+ assertEqual(scenario.totNumRangeScen, 1);
+ assertEqual(scenario.relRangeShift, refScen.relRangeShift(scenNum));
+ assertEqual(scenario.absRangeShift, refScen.absRangeShift(scenNum));
+ assertEqual(scenario.isoShift, refScen.isoShift(scenNum,:));
+ assertEqual(scenario.maxAbsRangeShift, max(abs(refScen.absRangeShift(scenNum))));
+ assertEqual(scenario.maxRelRangeShift, max(abs(refScen.relRangeShift(scenNum))));
+ assertTrue(scenario.scenMask(ctScenIx,1,1));
+ assertTrue(numel(find(scenario.scenMask)) == 1);
+ assertEqual(scenario.linearMask, [ctScenIx 1 1]);
+ assertElementsAlmostEqual(scenario.scenProb,helper_mvarGauss(scenario));
+ assertEqual(scenario.scenForProb,refScen.scenForProb(scenNum,:));
+ assertEqual(scenario.scenWeight, refScen.scenWeight(scenNum));
+ end
+
+function test_worstCaseScenarioCombineRange
+
+ model = matRad_WorstCaseScenarios();
+
+ assertExceptionThrown(@() helper_assignmentTest(model,'combineRange','hello'),'matRad:Error');
+ assertTrue(model.combineRange);
+
+ assertEqual(model.totNumRangeScen,3);
+ model.combineRange = false;
+ assertFalse(model.combineRange);
+ assertEqual(model.totNumScen,15);
+ assertEqual(model.totNumRangeScen,9);
+
+function test_worstCaseScenarioShiftCombinations
+
+ model = matRad_WorstCaseScenarios();
+
+ assertExceptionThrown(@() helper_assignmentTest(model,'combinations','hello'),'matRad:Error');
+ assertEqual(model.combinations,'none');
+
+ model.combinations = 'shift';
+ assertEqual(model.combinations,'shift');
+ assertEqual(model.totNumShiftScen,27);
+ assertEqual(model.totNumRangeScen,3);
+ assertEqual(model.totNumScen,29);
+
+ model.combinations = 'all';
+ assertEqual(model.combinations,'all');
+ assertEqual(model.totNumShiftScen,27);
+ assertEqual(model.totNumRangeScen,3);
+ assertEqual(model.totNumScen,81);
+
+ model.combineRange = false;
+ assertEqual(model.totNumShiftScen,27);
+ assertEqual(model.totNumRangeScen,9);
+ assertEqual(model.totNumScen,243);
+
+ model.combinations = 'shift';
+ assertEqual(model.totNumShiftScen,27);
+ assertEqual(model.totNumRangeScen,9);
+ assertEqual(model.totNumScen,35);
\ No newline at end of file
diff --git a/test/sequencing/test_engelLeafSequencing.m b/test/sequencing/test_engelLeafSequencing.m
new file mode 100644
index 000000000..98e0378fe
--- /dev/null
+++ b/test/sequencing/test_engelLeafSequencing.m
@@ -0,0 +1,79 @@
+function test_suite = test_engelLeafSequencing
+ %The output should always be test_suite, and the function name the same as
+ %your file name
+
+ %To collect all tests defined below, this is needed in newer Matlab
+ %versions. test_functions will collect function handles to below test
+ %functions
+ test_functions=localfunctions();
+
+ % This will initialize the test suite, i.e., take the functions from
+ % test_functions, check if they contain "test", convert them into a MOxUnit
+ % Test Case, and add them to the test-runner
+ initTestSuite;
+
+ function [resultGUI,stf,dij] = helper_getTestData()
+ p = load('photons_testData.mat');
+ resultGUI = p.resultGUI;
+ stf = p.stf;
+ dij = p.dij;
+
+
+ function test_run_sequencing_basic
+ [resultGUI,stf,dij] = helper_getTestData();
+ fn_old = fieldnames(resultGUI);
+
+ numOfLevels = [1,10];
+
+ for levels = numOfLevels
+ resultGUI_sequenced = matRad_engelLeafSequencing(resultGUI,stf,dij,levels);
+
+ fn_new = fieldnames(resultGUI_sequenced);
+ for i = 1:numel(fn_old)
+ assertTrue(any(strcmp(fn_old{i},fn_new)));
+ assertEqual(resultGUI.(fn_old{i}),resultGUI_sequenced.(fn_old{i}));
+ end
+
+ % Basic additions to resultGUI
+ assertTrue(isvector(resultGUI_sequenced.wSequenced));
+ assertTrue(isstruct(resultGUI_sequenced.apertureInfo));
+ assertTrue(isstruct(resultGUI_sequenced.sequencing));
+
+ %Sequencing Struct
+ seq = resultGUI_sequenced.sequencing;
+ assertTrue(isstruct(seq.beam));
+ assertTrue(numel(seq.beam) == numel(stf));
+ for i = 1:numel(seq.beam)
+ assertTrue(isscalar(seq.beam(i).numOfShapes));
+ assertTrue(isnumeric(seq.beam(i).shapes));
+ shapeSize = size(seq.beam(i).shapes);
+ assertEqual(shapeSize(3),seq.beam(i).numOfShapes);
+ assertTrue(isvector(seq.beam(i).shapesWeight));
+ assertTrue(isvector(seq.beam(i).bixelIx));
+ assertTrue(ismatrix(seq.beam(i).fluence));
+ assertEqual(size(seq.beam(i).fluence),shapeSize([1 2]));
+ end
+
+ %ApertureInfo Sturct
+ apInfo = resultGUI_sequenced.apertureInfo;
+ assertTrue(isscalar(apInfo.bixelWidth));
+ assertTrue(isscalar(apInfo.numOfMLCLeafPairs));
+ assertTrue(isscalar(apInfo.totalNumOfBixels));
+ assertTrue(isscalar(apInfo.totalNumOfShapes));
+ assertTrue(isscalar(apInfo.totalNumOfLeafPairs));
+ assertTrue(isvector(apInfo.apertureVector));
+ assertTrue(ismatrix(apInfo.mappingMx));
+ assertTrue(ismatrix(apInfo.limMx));
+ assertTrue(isstruct(apInfo.beam))
+ assertTrue(numel(apInfo.beam) == numel(stf));
+ end
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/sequencing/test_siochiLeafSequencing.m b/test/sequencing/test_siochiLeafSequencing.m
new file mode 100644
index 000000000..af6e91859
--- /dev/null
+++ b/test/sequencing/test_siochiLeafSequencing.m
@@ -0,0 +1,80 @@
+function test_suite = test_xiaLeafSequencing
+ %The output should always be test_suite, and the function name the same as
+ %your file name
+
+ %To collect all tests defined below, this is needed in newer Matlab
+ %versions. test_functions will collect function handles to below test
+ %functions
+ test_functions=localfunctions();
+
+ % This will initialize the test suite, i.e., take the functions from
+ % test_functions, check if they contain "test", convert them into a MOxUnit
+ % Test Case, and add them to the test-runner
+ initTestSuite;
+
+ function [resultGUI,stf,dij] = helper_getTestData()
+ p = load('photons_testData.mat');
+ resultGUI = p.resultGUI;
+ stf = p.stf;
+ dij = p.dij;
+
+
+ function test_run_sequencing_basic
+ [resultGUI,stf,dij] = helper_getTestData();
+ fn_old = fieldnames(resultGUI);
+
+ numOfLevels = [1,10];
+
+ for levels = numOfLevels
+ resultGUI_sequenced = matRad_siochiLeafSequencing(resultGUI,stf,dij,levels);
+
+ fn_new = fieldnames(resultGUI_sequenced);
+ for i = 1:numel(fn_old)
+ assertTrue(any(strcmp(fn_old{i},fn_new)));
+ assertEqual(resultGUI.(fn_old{i}),resultGUI_sequenced.(fn_old{i}));
+ end
+
+ % Basic additions to resultGUI
+ assertTrue(isvector(resultGUI_sequenced.wSequenced));
+ assertTrue(isstruct(resultGUI_sequenced.apertureInfo));
+ assertTrue(isstruct(resultGUI_sequenced.sequencing));
+
+ %Sequencing Struct
+ seq = resultGUI_sequenced.sequencing;
+ assertTrue(isstruct(seq.beam));
+ assertTrue(numel(seq.beam) == numel(stf));
+ for i = 1:numel(seq.beam)
+ assertTrue(isscalar(seq.beam(i).numOfShapes));
+ assertTrue(isnumeric(seq.beam(i).shapes));
+ shapeSize = size(seq.beam(i).shapes);
+ assertEqual(shapeSize(3),seq.beam(i).numOfShapes);
+ assertTrue(isvector(seq.beam(i).shapesWeight));
+ assertTrue(isvector(seq.beam(i).bixelIx));
+ assertTrue(ismatrix(seq.beam(i).fluence));
+ assertEqual(size(seq.beam(i).fluence),shapeSize([1 2]));
+ end
+
+ %ApertureInfo Sturct
+ apInfo = resultGUI_sequenced.apertureInfo;
+ assertTrue(isscalar(apInfo.bixelWidth));
+ assertTrue(isscalar(apInfo.numOfMLCLeafPairs));
+ assertTrue(isscalar(apInfo.totalNumOfBixels));
+ assertTrue(isscalar(apInfo.totalNumOfShapes));
+ assertTrue(isscalar(apInfo.totalNumOfLeafPairs));
+ assertTrue(isvector(apInfo.apertureVector));
+ assertTrue(ismatrix(apInfo.mappingMx));
+ assertTrue(ismatrix(apInfo.limMx));
+ assertTrue(isstruct(apInfo.beam))
+ assertTrue(numel(apInfo.beam) == numel(stf));
+ end
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/sequencing/test_xiaLeafSequencing.m b/test/sequencing/test_xiaLeafSequencing.m
new file mode 100644
index 000000000..9d6aaf943
--- /dev/null
+++ b/test/sequencing/test_xiaLeafSequencing.m
@@ -0,0 +1,80 @@
+function test_suite = test_xiaLeafSequencing
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+function [resultGUI,stf,dij] = helper_getTestData()
+ p = load('photons_testData.mat');
+ resultGUI = p.resultGUI;
+ stf = p.stf;
+ dij = p.dij;
+
+
+function test_run_sequencing_basic
+ [resultGUI,stf,dij] = helper_getTestData();
+ fn_old = fieldnames(resultGUI);
+
+ numOfLevels = [1,10];
+
+ for levels = numOfLevels
+ resultGUI_sequenced = matRad_xiaLeafSequencing(resultGUI,stf,dij,levels);
+
+ fn_new = fieldnames(resultGUI_sequenced);
+ for i = 1:numel(fn_old)
+ assertTrue(any(strcmp(fn_old{i},fn_new)));
+ assertEqual(resultGUI.(fn_old{i}),resultGUI_sequenced.(fn_old{i}));
+ end
+
+ % Basic additions to resultGUI
+ assertTrue(isvector(resultGUI_sequenced.wSequenced));
+ assertTrue(isstruct(resultGUI_sequenced.apertureInfo));
+ assertTrue(isstruct(resultGUI_sequenced.sequencing));
+
+ %Sequencing Struct
+ seq = resultGUI_sequenced.sequencing;
+ assertTrue(isstruct(seq.beam));
+ assertTrue(numel(seq.beam) == numel(stf));
+ for i = 1:numel(seq.beam)
+ assertTrue(isscalar(seq.beam(i).numOfShapes));
+ assertTrue(isnumeric(seq.beam(i).shapes));
+ shapeSize = size(seq.beam(i).shapes);
+ assertEqual(shapeSize(3),seq.beam(i).numOfShapes);
+ assertTrue(isvector(seq.beam(i).shapesWeight));
+ assertTrue(isvector(seq.beam(i).bixelIx));
+ assertTrue(ismatrix(seq.beam(i).fluence));
+ assertEqual(size(seq.beam(i).fluence),shapeSize([1 2]));
+ end
+
+ %ApertureInfo Sturct
+ apInfo = resultGUI_sequenced.apertureInfo;
+ assertTrue(isscalar(apInfo.bixelWidth));
+ assertTrue(isscalar(apInfo.numOfMLCLeafPairs));
+ assertTrue(isscalar(apInfo.totalNumOfBixels));
+ assertTrue(isscalar(apInfo.totalNumOfShapes));
+ assertTrue(isscalar(apInfo.totalNumOfLeafPairs));
+ assertTrue(isvector(apInfo.apertureVector));
+ assertTrue(ismatrix(apInfo.mappingMx));
+ assertTrue(ismatrix(apInfo.limMx));
+ assertTrue(isstruct(apInfo.beam))
+ assertTrue(numel(apInfo.beam) == numel(stf));
+ end
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/steering/test_generateBrachyStf.m b/test/steering/test_generateBrachyStf.m
new file mode 100644
index 000000000..a53e7447c
--- /dev/null
+++ b/test/steering/test_generateBrachyStf.m
@@ -0,0 +1,60 @@
+function test_suite = test_generateBrachyStf
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function pln = createPln(machine,ct,cst)
+ % set up an example pln with relevant fields
+ pln.radiationMode = 'brachy';
+ pln.machine = machine;
+
+ pln.propStf.templateRoot = matRad_getTemplateRoot(ct, cst);
+ pln.propStf.needle.seedDistance = 1; % [mm] seed distance on needle
+ pln.propStf.needle.seedsNo = 2; % number of seeds per needle
+ pln.propStf.template.numOfXPoints = 2;
+ pln.propStf.template.numOfYPoints = 2;
+ pln.propStf.template.xScale = 1; % [mm] distance of neighbouring points
+ pln.propStf.template.yScale = 1; % [mm] distance of neighbouring points
+ pln.propStf.template.activeNeedles = [0 0 0 0 0 0 0 0 0 0 0 0 0;...
+ 0 0 0 0 0 0 0 0 0 0 0 0 0;...
+ 0 0 0 0 1 1 0 1 1 0 0 0 0;...
+ 1 0 1 0 1 0 0 0 1 0 1 0 1;...
+ 0 1 0 1 0 1 0 1 0 1 0 1 0;...
+ 1 0 1 0 1 0 0 0 1 0 1 0 1;...
+ 0 1 0 1 0 1 0 1 0 1 0 1 0;...
+ 1 0 1 0 1 0 1 0 1 0 1 0 1;...
+ 0 1 0 1 0 1 0 1 0 1 0 1 0;...
+ 0 0 1 0 1 0 0 0 1 0 1 0 0;...
+ 0 0 0 1 0 0 0 0 0 1 0 0 0;...
+ 0 0 0 0 0 0 0 0 0 0 0 0 0;...
+ 0 0 0 0 0 0 0 0 0 0 0 0 0];
+ pln.propStf.bixelWidth = 10;
+
+function test_generate_HDR()
+ % geometry settings
+ load PROSTATE.mat ct cst;
+ pln = createPln('HDR',ct,cst);
+
+ stf = matRad_generateStf(ct, cst, pln, 0);
+ assertTrue(isfield(stf, 'radiationMode'));
+ assertTrue(isfield(stf, 'numOfSeedsPerNeedle'));
+ assertTrue(isfield(stf, 'numOfNeedles'));
+ assertTrue(isfield(stf, 'totalNumOfBixels'));
+ assertTrue(isfield(stf, 'template'));
+ assertTrue(isfield(stf, 'seedPoints'));
+
+function test_generate_LDR()
+ % geometry settings
+ load PROSTATE.mat ct cst;
+ pln = createPln('LDR',ct,cst);
+
+ stf = matRad_generateStf(ct, cst, pln, 0);
+ assertTrue(isfield(stf, 'radiationMode'));
+ assertTrue(isfield(stf, 'numOfSeedsPerNeedle'));
+ assertTrue(isfield(stf, 'numOfNeedles'));
+ assertTrue(isfield(stf, 'totalNumOfBixels'));
+ assertTrue(isfield(stf, 'template'));
+ assertTrue(isfield(stf, 'seedPoints'));
+
+
diff --git a/test/steering/test_stfGEneratorParticleIMPT.m b/test/steering/test_stfGEneratorParticleIMPT.m
new file mode 100644
index 000000000..5ad451e76
--- /dev/null
+++ b/test/steering/test_stfGEneratorParticleIMPT.m
@@ -0,0 +1,85 @@
+function test_suite = test_stfGeneratorPhotonIMRT
+
+ test_functions=localfunctions();
+
+ initTestSuite;
+
+ function test_basic_construct()
+ stfGen = matRad_StfGeneratorParticleIMPT();
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorParticleIMPT'));
+
+ function test_pln_construct()
+ load protons_testData.mat
+ stfGen = matRad_StfGeneratorParticleIMPT(pln);
+ stfGen.isAvailable(pln);
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorParticleIMPT'));
+ assertEqual(stfGen.gantryAngles, pln.propStf.gantryAngles);
+ assertEqual(stfGen.couchAngles, pln.propStf.couchAngles);
+ assertEqual(stfGen.isoCenter, pln.propStf.isoCenter);
+ assertEqual(stfGen.radiationMode, pln.radiationMode);
+ assertEqual(stfGen.machine, pln.machine);
+ assertEqual(stfGen.bixelWidth, pln.propStf.bixelWidth);
+
+ function test_generate_multibeams()
+ % geometry settings
+ load protons_testData.mat ct cst pln stf;
+
+ stfGen = matRad_StfGeneratorParticleIMPT(pln);
+ stf2 = stfGen.generate(ct,cst);
+
+ assertTrue(isfield(stf2, 'radiationMode'));
+ assertTrue(isfield(stf2, 'machine'));
+ assertTrue(isfield(stf2, 'gantryAngle'));
+ assertTrue(isfield(stf2, 'couchAngle'));
+ assertTrue(isfield(stf2, 'isoCenter'));
+ assertTrue(isfield(stf2, 'bixelWidth'));
+ assertTrue(isfield(stf2, 'SAD'));
+ assertTrue(isfield(stf2, 'numOfRays'));
+ assertTrue(isfield(stf2, 'numOfBixelsPerRay'));
+ assertTrue(isfield(stf2, 'totalNumOfBixels'));
+ assertTrue(isfield(stf2, 'sourcePoint'));
+ assertTrue(isfield(stf2, 'sourcePoint_bev'));
+ assertTrue(isfield(stf2, 'ray'));
+
+ for i = 1:numel(stf2)
+
+ assertEqual(stf2(i).totalNumOfBixels,stf(i).totalNumOfBixels);
+ assertEqual(stf2(i).numOfBixelsPerRay,stf(i).numOfBixelsPerRay);
+ assertEqual(stf2(i).numOfRays,stf(i).numOfRays);
+ assertEqual(stf2(i).bixelWidth,stfGen.bixelWidth);
+ assertEqual(stf2(i).radiationMode,stfGen.radiationMode);
+ assertEqual(stf2(i).machine,pln.machine);
+ assertEqual(stf2(i).gantryAngle,stfGen.gantryAngles(i));
+ assertEqual(stf2(i).couchAngle,stfGen.couchAngles(i));
+
+ rotMat = matRad_getRotationMatrix(stf2(i).gantryAngle,stf2(i).couchAngle);
+ assertEqual(stf2(i).sourcePoint,stf2(i).sourcePoint_bev*rotMat);
+ assertEqual(stf2(i).sourcePoint_bev,[0 -stf2(i).SAD 0]);
+
+ assertTrue(isstruct(stf2(i).ray));
+ assertEqual(numel(stf2(i).ray),numel(stf(i).ray));
+ assertEqual(numel(stf2(i).ray),stf2(i).numOfRays);
+
+ assertTrue(isfield(stf2(i).ray,'rangeShifter'));
+
+ rayPosTest = vertcat(stf2(i).ray.rayPos);
+ rayPosTest_bev = rayPosTest*rotMat;
+ rayPos_bevTest = vertcat(stf2(i).ray.rayPos_bev);
+ rayPosRef = vertcat(stf(i).ray.rayPos);
+ assertElementsAlmostEqual(sort(rayPosTest,1),sort(rayPosRef,1));
+ assertElementsAlmostEqual(sort(rayPos_bevTest,1),sort(rayPosTest_bev,1));
+
+ targetPointTest = vertcat(stf2(i).ray.targetPoint);
+ targetPointRef = vertcat(stf(i).ray.targetPoint);
+ targetPoint_bevTest = vertcat(stf2(i).ray.targetPoint_bev);
+ targetPoint_bevRef = vertcat(stf(i).ray.targetPoint_bev);
+ assertElementsAlmostEqual(sort(targetPointTest,1),sort(targetPointRef,1));
+ assertElementsAlmostEqual(sort(targetPoint_bevTest,1),sort(targetPoint_bevRef,1));
+ assertElementsAlmostEqual(sort(targetPoint_bevTest,1),sort(targetPointTest*rotMat,1));
+
+ energiesTest = [stf2(i).ray.energy];
+ energiesRef = [stf(i).ray.energy];
+ assertEqual(unique(energiesTest),unique(energiesRef));
+
+ %assertTrue(isscalar(stf2(i).ray.energy));
+ end
diff --git a/test/steering/test_stfGeneratorParticleBeamlet.m b/test/steering/test_stfGeneratorParticleBeamlet.m
new file mode 100644
index 000000000..af7f19597
--- /dev/null
+++ b/test/steering/test_stfGeneratorParticleBeamlet.m
@@ -0,0 +1,132 @@
+function test_suite = test_stfGeneratorParticleBeamlet
+
+ test_functions=localfunctions();
+
+ initTestSuite;
+
+ function test_basic_construct()
+ stfGen = matRad_StfGeneratorParticleSingleBeamlet();
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorParticleSingleBeamlet'));
+ assertEqual(stfGen.radiationMode,'protons');
+
+ function test_pln_construct()
+ load protons_testData.mat
+ stfGen = matRad_StfGeneratorParticleSingleBeamlet(pln);
+ assertTrue(stfGen.isAvailable(pln));
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorParticleSingleBeamlet'));
+ assertEqual(stfGen.gantryAngles, pln.propStf.gantryAngles);
+ assertEqual(stfGen.couchAngles, pln.propStf.couchAngles);
+ assertEqual(stfGen.isoCenter, pln.propStf.isoCenter);
+ assertEqual(stfGen.radiationMode, pln.radiationMode);
+ assertEqual(stfGen.machine, pln.machine);
+ assertEqual(stfGen.bixelWidth, pln.propStf.bixelWidth);
+ assertEqual(stfGen.radiationMode,'protons');
+
+ pln.radiationMode = 'helium';
+ stfGen = matRad_StfGeneratorParticleSingleBeamlet(pln);
+ assertTrue(stfGen.isAvailable(pln));
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorParticleSingleBeamlet'));
+ assertEqual(stfGen.radiationMode, pln.radiationMode);
+
+ pln.radiationMode = 'carbon';
+ stfGen = matRad_StfGeneratorParticleSingleBeamlet(pln);
+ assertTrue(stfGen.isAvailable(pln));
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorParticleSingleBeamlet'));
+ assertEqual(stfGen.radiationMode, pln.radiationMode);
+
+
+ function test_generate_multibeams()
+ % geometry settings
+ load protons_testData.mat ct cst pln;
+
+ stfGen = matRad_StfGeneratorParticleSingleBeamlet(pln);
+ stf = stfGen.generate(ct,cst);
+
+
+ assertTrue(isfield(stf, 'radiationMode'));
+ assertTrue(isfield(stf, 'machine'));
+ assertTrue(isfield(stf, 'gantryAngle'));
+ assertTrue(isfield(stf, 'couchAngle'));
+ assertTrue(isfield(stf, 'isoCenter'));
+ assertTrue(isfield(stf, 'bixelWidth'));
+ assertTrue(isfield(stf, 'SAD'));
+ assertTrue(isfield(stf, 'numOfRays'));
+ assertTrue(isfield(stf, 'numOfBixelsPerRay'));
+ assertTrue(isfield(stf, 'totalNumOfBixels'));
+ assertTrue(isfield(stf, 'sourcePoint'));
+ assertTrue(isfield(stf, 'sourcePoint_bev'));
+ assertTrue(isfield(stf, 'ray'));
+
+ for i = 1:numel(stf)
+
+ assertEqual(stf(i).totalNumOfBixels,1);
+ assertEqual(stf(i).numOfBixelsPerRay,1);
+ assertEqual(stf(i).numOfRays,1);
+ assertEqual(stf(i).bixelWidth,stfGen.bixelWidth);
+ assertEqual(stf(i).radiationMode,stfGen.radiationMode);
+ assertEqual(stf(i).machine,pln.machine);
+ assertEqual(stf(i).gantryAngle,stfGen.gantryAngles(i));
+ assertEqual(stf(i).couchAngle,stfGen.couchAngles(i));
+
+ rotMat = matRad_getRotationMatrix(stf(i).gantryAngle,stf(i).couchAngle);
+ assertEqual(stf(i).sourcePoint,stf(i).sourcePoint_bev*rotMat);
+
+ assertEqual(stf(i).sourcePoint_bev,[0 -stf(i).SAD 0]);
+
+ assertTrue(isstruct(stf(i).ray) && numel(stf(i).ray) == 1);
+ assertEqual(stf(i).ray.rayPos, [0 0 0]);
+ assertEqual(stf(i).ray.rayPos_bev, [0 0 0]);
+ assertEqual(stf(i).ray.targetPoint_bev, [0 stf(i).SAD 0]);
+ assertEqual(stf(i).ray.targetPoint, stf(i).ray.targetPoint_bev*rotMat);
+
+ assertTrue(isfield(stf(i).ray,'rangeShifter'));
+ assertTrue(isscalar(stf(i).ray.energy));
+ end
+
+ function test_generate_single_beam()
+ % geometry settings
+ load protons_testData.mat ct cst pln;
+
+ stfGen = matRad_StfGeneratorParticleSingleBeamlet(pln);
+
+ stfGen.gantryAngles = 0;
+ assertTrue(numel(stfGen.couchAngles) == 1);
+ stfGen.couchAngles = 0;
+
+ stf = stfGen.generate(ct,cst);
+
+ assertTrue(isfield(stf, 'radiationMode'));
+ assertTrue(isfield(stf, 'machine'));
+ assertTrue(isfield(stf, 'gantryAngle'));
+ assertTrue(isfield(stf, 'couchAngle'));
+ assertTrue(isfield(stf, 'isoCenter'));
+ assertTrue(isfield(stf, 'bixelWidth'));
+ assertTrue(isfield(stf, 'SAD'));
+ assertTrue(isfield(stf, 'numOfRays'));
+ assertTrue(isfield(stf, 'numOfBixelsPerRay'));
+ assertTrue(isfield(stf, 'totalNumOfBixels'));
+ assertTrue(isfield(stf, 'sourcePoint'));
+ assertTrue(isfield(stf, 'sourcePoint_bev'));
+ assertTrue(isfield(stf, 'ray'));
+
+ assertEqual(stf.totalNumOfBixels,1);
+ assertEqual(stf.numOfBixelsPerRay,1);
+ assertEqual(stf.numOfRays,1);
+ assertEqual(stf.bixelWidth,stfGen.bixelWidth);
+ assertEqual(stf.radiationMode,stfGen.radiationMode);
+ assertEqual(stf.machine,pln.machine);
+ assertEqual(stf.gantryAngle,stfGen.gantryAngles);
+ assertEqual(stf.couchAngle,stfGen.couchAngles);
+
+ rotMat = matRad_getRotationMatrix(stf.gantryAngle,stf.couchAngle);
+ assertEqual(stf.sourcePoint_bev,[0 -stf.SAD 0]);
+ assertEqual(stf.sourcePoint,stf.sourcePoint_bev*rotMat);
+
+ assertTrue(isstruct(stf.ray) && numel(stf.ray) == 1);
+ assertEqual(stf.ray.rayPos, [0 0 0]);
+ assertEqual(stf.ray.rayPos_bev, [0 0 0]);
+ assertEqual(stf.ray.targetPoint_bev, [0 stf.SAD 0]);
+ assertEqual(stf.ray.targetPoint, stf.ray.targetPoint_bev*rotMat);
+
+ assertTrue(isfield(stf.ray,'rangeShifter'));
+ assertTrue(isscalar(stf.ray.energy));
\ No newline at end of file
diff --git a/test/steering/test_stfGeneratorPhotonBixel.m b/test/steering/test_stfGeneratorPhotonBixel.m
new file mode 100644
index 000000000..e0a12b3cf
--- /dev/null
+++ b/test/steering/test_stfGeneratorPhotonBixel.m
@@ -0,0 +1,118 @@
+function test_suite = test_stfGeneratorPhotonBixel
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_basic_construct()
+ stfGen = matRad_StfGeneratorPhotonSingleBeamlet();
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorPhotonSingleBeamlet'));
+
+function test_pln_construct()
+ load photons_testData.mat
+ stfGen = matRad_StfGeneratorPhotonSingleBeamlet(pln);
+ stfGen.isAvailable(pln);
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorPhotonSingleBeamlet'));
+ assertEqual(stfGen.gantryAngles, pln.propStf.gantryAngles);
+ assertEqual(stfGen.couchAngles, pln.propStf.couchAngles);
+ assertEqual(stfGen.isoCenter, pln.propStf.isoCenter);
+ assertEqual(stfGen.radiationMode, pln.radiationMode);
+ assertEqual(stfGen.machine, pln.machine);
+ assertEqual(stfGen.bixelWidth, pln.propStf.bixelWidth);
+
+function test_generate_multibeams()
+ % geometry settings
+ load photons_testData.mat ct cst pln;
+
+ stfGen = matRad_StfGeneratorPhotonSingleBeamlet(pln);
+ stf = stfGen.generate(ct,cst);
+
+ assertTrue(isfield(stf, 'radiationMode'));
+ assertTrue(isfield(stf, 'machine'));
+ assertTrue(isfield(stf, 'gantryAngle'));
+ assertTrue(isfield(stf, 'couchAngle'));
+ assertTrue(isfield(stf, 'isoCenter'));
+ assertTrue(isfield(stf, 'bixelWidth'));
+ assertTrue(isfield(stf, 'SAD'));
+ assertTrue(isfield(stf, 'SCD'));
+ assertTrue(isfield(stf, 'numOfRays'));
+ assertTrue(isfield(stf, 'numOfBixelsPerRay'));
+ assertTrue(isfield(stf, 'totalNumOfBixels'));
+ assertTrue(isfield(stf, 'sourcePoint'));
+ assertTrue(isfield(stf, 'sourcePoint_bev'));
+ assertTrue(isfield(stf, 'ray'));
+
+ for i = 1:numel(stf)
+
+ assertEqual(stf(i).totalNumOfBixels,1);
+ assertEqual(stf(i).numOfBixelsPerRay,1);
+ assertEqual(stf(i).numOfRays,1);
+ assertEqual(stf(i).bixelWidth,stfGen.bixelWidth);
+ assertEqual(stf(i).radiationMode,stfGen.radiationMode);
+ assertEqual(stf(i).machine,pln.machine);
+ assertEqual(stf(i).gantryAngle,stfGen.gantryAngles(i));
+ assertEqual(stf(i).couchAngle,stfGen.couchAngles(i));
+
+ rotMat = matRad_getRotationMatrix(stf(i).gantryAngle,stf(i).couchAngle);
+ assertEqual(stf(i).sourcePoint,stf(i).sourcePoint_bev*rotMat);
+
+ assertEqual(stf(i).sourcePoint_bev,[0 -stf(i).SAD 0]);
+
+ assertTrue(isstruct(stf(i).ray) && numel(stf(i).ray) == 1);
+ assertEqual(stf(i).ray.rayPos, [0 0 0]);
+ assertEqual(stf(i).ray.rayPos_bev, [0 0 0]);
+ assertEqual(stf(i).ray.targetPoint_bev, [0 stf(i).SAD 0]);
+ assertEqual(stf(i).ray.targetPoint, stf(i).ray.targetPoint_bev*rotMat);
+
+ assertTrue(isfield(stf(i).ray,'beamletCornersAtIso'));
+ assertTrue(isfield(stf(i).ray,'rayCorners_SCD'));
+ assertTrue(isscalar(stf(i).ray.energy));
+ end
+
+ function test_generate_single_beam()
+ % geometry settings
+ load photons_testData.mat ct cst pln;
+
+ stfGen = matRad_StfGeneratorPhotonSingleBeamlet(pln);
+
+ stfGen.gantryAngles = 0;
+ assertTrue(numel(stfGen.couchAngles) == 1);
+ stfGen.couchAngles = 0;
+
+ stf = stfGen.generate(ct,cst);
+
+ assertTrue(isfield(stf, 'radiationMode'));
+ assertTrue(isfield(stf, 'machine'));
+ assertTrue(isfield(stf, 'gantryAngle'));
+ assertTrue(isfield(stf, 'couchAngle'));
+ assertTrue(isfield(stf, 'isoCenter'));
+ assertTrue(isfield(stf, 'bixelWidth'));
+ assertTrue(isfield(stf, 'SAD'));
+ assertTrue(isfield(stf, 'SCD'));
+ assertTrue(isfield(stf, 'numOfRays'));
+ assertTrue(isfield(stf, 'numOfBixelsPerRay'));
+ assertTrue(isfield(stf, 'totalNumOfBixels'));
+ assertTrue(isfield(stf, 'sourcePoint'));
+ assertTrue(isfield(stf, 'sourcePoint_bev'));
+ assertTrue(isfield(stf, 'ray'));
+
+ assertEqual(stf.totalNumOfBixels,1);
+ assertEqual(stf.numOfBixelsPerRay,1);
+ assertEqual(stf.numOfRays,1);
+ assertEqual(stf.bixelWidth,stfGen.bixelWidth);
+ assertEqual(stf.radiationMode,stfGen.radiationMode);
+ assertEqual(stf.machine,pln.machine);
+ assertEqual(stf.gantryAngle,stfGen.gantryAngles);
+ assertEqual(stf.couchAngle,stfGen.couchAngles);
+
+ rotMat = matRad_getRotationMatrix(stf.gantryAngle,stf.couchAngle);
+ assertEqual(stf.sourcePoint_bev,[0 -stf.SAD 0]);
+ assertEqual(stf.sourcePoint,stf.sourcePoint_bev*rotMat);
+
+ assertTrue(isstruct(stf.ray) && numel(stf.ray) == 1);
+ assertEqual(stf.ray.rayPos, [0 0 0]);
+ assertEqual(stf.ray.rayPos_bev, [0 0 0]);
+ assertEqual(stf.ray.targetPoint_bev, [0 stf.SAD 0]);
+ assertEqual(stf.ray.targetPoint, stf.ray.targetPoint_bev*rotMat);
+ assertTrue(isfield(stf.ray,'beamletCornersAtIso'));
+ assertTrue(isfield(stf.ray,'rayCorners_SCD'));
\ No newline at end of file
diff --git a/test/steering/test_stfGeneratorPhotonIMRT.m b/test/steering/test_stfGeneratorPhotonIMRT.m
new file mode 100644
index 000000000..e7605589b
--- /dev/null
+++ b/test/steering/test_stfGeneratorPhotonIMRT.m
@@ -0,0 +1,86 @@
+function test_suite = test_stfGeneratorPhotonIMRT
+
+ test_functions=localfunctions();
+
+ initTestSuite;
+
+ function test_basic_construct()
+ stfGen = matRad_StfGeneratorPhotonIMRT();
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorPhotonIMRT'));
+
+ function test_pln_construct()
+ load photons_testData.mat
+ stfGen = matRad_StfGeneratorPhotonIMRT(pln);
+ stfGen.isAvailable(pln);
+ assertTrue(isa(stfGen, 'matRad_StfGeneratorPhotonIMRT'));
+ assertEqual(stfGen.gantryAngles, pln.propStf.gantryAngles);
+ assertEqual(stfGen.couchAngles, pln.propStf.couchAngles);
+ assertEqual(stfGen.isoCenter, pln.propStf.isoCenter);
+ assertEqual(stfGen.radiationMode, pln.radiationMode);
+ assertEqual(stfGen.machine, pln.machine);
+ assertEqual(stfGen.bixelWidth, pln.propStf.bixelWidth);
+
+ function test_generate_multibeams()
+ % geometry settings
+ load photons_testData.mat ct cst pln stf;
+
+ stfGen = matRad_StfGeneratorPhotonIMRT(pln);
+ stf2 = stfGen.generate(ct,cst);
+
+ assertTrue(isfield(stf2, 'radiationMode'));
+ assertTrue(isfield(stf2, 'machine'));
+ assertTrue(isfield(stf2, 'gantryAngle'));
+ assertTrue(isfield(stf2, 'couchAngle'));
+ assertTrue(isfield(stf2, 'isoCenter'));
+ assertTrue(isfield(stf2, 'bixelWidth'));
+ assertTrue(isfield(stf2, 'SAD'));
+ assertTrue(isfield(stf2, 'SCD'));
+ assertTrue(isfield(stf2, 'numOfRays'));
+ assertTrue(isfield(stf2, 'numOfBixelsPerRay'));
+ assertTrue(isfield(stf2, 'totalNumOfBixels'));
+ assertTrue(isfield(stf2, 'sourcePoint'));
+ assertTrue(isfield(stf2, 'sourcePoint_bev'));
+ assertTrue(isfield(stf2, 'ray'));
+
+ for i = 1:numel(stf2)
+
+ assertEqual(stf2(i).totalNumOfBixels,stf(i).totalNumOfBixels);
+ assertEqual(stf2(i).numOfBixelsPerRay,stf(i).numOfBixelsPerRay);
+ assertEqual(stf2(i).numOfRays,stf(i).numOfRays);
+ assertEqual(stf2(i).bixelWidth,stfGen.bixelWidth);
+ assertEqual(stf2(i).radiationMode,stfGen.radiationMode);
+ assertEqual(stf2(i).machine,pln.machine);
+ assertEqual(stf2(i).gantryAngle,stfGen.gantryAngles(i));
+ assertEqual(stf2(i).couchAngle,stfGen.couchAngles(i));
+
+ rotMat = matRad_getRotationMatrix(stf2(i).gantryAngle,stf2(i).couchAngle);
+ assertEqual(stf2(i).sourcePoint,stf2(i).sourcePoint_bev*rotMat);
+ assertEqual(stf2(i).sourcePoint_bev,[0 -stf2(i).SAD 0]);
+
+ assertTrue(isstruct(stf2(i).ray));
+ assertEqual(numel(stf2(i).ray),numel(stf(i).ray));
+ assertEqual(numel(stf2(i).ray),stf2(i).numOfRays);
+
+ assertTrue(isfield(stf2(i).ray,'beamletCornersAtIso'));
+ assertTrue(isfield(stf2(i).ray,'rayCorners_SCD'));
+
+ rayPosTest = vertcat(stf2(i).ray.rayPos);
+ rayPosTest_bev = rayPosTest*rotMat;
+ rayPos_bevTest = vertcat(stf2(i).ray.rayPos_bev);
+ rayPosRef = vertcat(stf(i).ray.rayPos);
+ assertElementsAlmostEqual(sort(rayPosTest,1),sort(rayPosRef,1));
+ assertElementsAlmostEqual(sort(rayPos_bevTest,1),sort(rayPosTest_bev,1));
+
+ targetPointTest = vertcat(stf2(i).ray.targetPoint);
+ targetPointRef = vertcat(stf(i).ray.targetPoint);
+ targetPoint_bevTest = vertcat(stf2(i).ray.targetPoint_bev);
+ targetPoint_bevRef = vertcat(stf(i).ray.targetPoint_bev);
+ assertElementsAlmostEqual(sort(targetPointTest,1),sort(targetPointRef,1));
+ assertElementsAlmostEqual(sort(targetPoint_bevTest,1),sort(targetPoint_bevRef,1));
+ assertElementsAlmostEqual(sort(targetPoint_bevTest,1),sort(targetPointTest*rotMat,1));
+
+ energiesTest = [stf2(i).ray.energy];
+ energiesRef = [stf(i).ray.energy];
+ assertEqual(energiesTest,energiesRef);
+ %assertTrue(isscalar(stf2(i).ray.energy));
+ end
diff --git a/test/steering/test_stfGeneratorsBase.m b/test/steering/test_stfGeneratorsBase.m
new file mode 100644
index 000000000..4a0b9d217
--- /dev/null
+++ b/test/steering/test_stfGeneratorsBase.m
@@ -0,0 +1,82 @@
+function test_suite = test_stfGeneratorsBase
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function test_stfGeneratorBaseIsAbstract
+ if moxunit_util_platform_is_octave()
+ assertExceptionThrown(@() matRad_StfGeneratorBase(),'');
+ else
+ assertExceptionThrown(@() matRad_StfGeneratorBase(),'MATLAB:class:abstract');
+ end
+
+function test_abstractIsAvailable
+ assertExceptionThrown(@() matRad_StfGeneratorBase.isAvailable,'matRad:Error');
+
+function test_getAvailableGenerators
+ avail = matRad_StfGeneratorBase.getAvailableGenerators();
+ assertTrue(~isempty(avail));
+ assertTrue(isstruct(avail));
+ assertTrue(iscolumn(avail));
+
+ photonDummyPln = struct('radiationMode','photons','machine','Generic');
+ availPhotons = matRad_StfGeneratorBase.getAvailableGenerators(photonDummyPln);
+ assertTrue(~isempty(avail));
+ assertTrue(isstruct(avail));
+ assertTrue(iscolumn(avail));
+ assertTrue(numel(availPhotons) < numel(avail));
+
+function test_loadMachine
+ machine = matRad_StfGeneratorBase.loadMachine('photons','Generic');
+ assertTrue(isstruct(machine));
+
+ if moxunit_util_platform_is_octave()
+ assertExceptionThrown(@() matRad_StfGeneratorBase.loadMachine());
+ assertExceptionThrown(@() matRad_StfGeneratorBase.loadMachine('photons'));
+ else
+ assertExceptionThrown(@() matRad_StfGeneratorBase.loadMachine(),'MATLAB:minrhs');
+ assertExceptionThrown(@() matRad_StfGeneratorBase.loadMachine('photons'),'MATLAB:minrhs');
+ end
+ assertExceptionThrown(@() matRad_StfGeneratorBase.loadMachine('grbl','grbl'),'matRad:Error');
+
+function test_getGeneratorFromPlnDefaults
+ photonDummyPln = struct('radiationMode','photons','machine','Generic');
+ generator = matRad_StfGeneratorBase.getGeneratorFromPln(photonDummyPln);
+ assertTrue(isa(generator,'matRad_StfGeneratorPhotonIMRT'));
+ assertEqual(generator.radiationMode,'photons');
+ assertEqual(generator.machine,'Generic');
+
+ protonDummyPln = struct('radiationMode','protons','machine','Generic');
+ generator = matRad_StfGeneratorBase.getGeneratorFromPln(protonDummyPln);
+ assertTrue(isa(generator,'matRad_StfGeneratorParticleIMPT'));
+ assertEqual(generator.radiationMode,'protons');
+ assertEqual(generator.machine,'Generic');
+
+ carbonDummyPln = struct('radiationMode','carbon','machine','Generic');
+ generator = matRad_StfGeneratorBase.getGeneratorFromPln(carbonDummyPln);
+ assertTrue(isa(generator,'matRad_StfGeneratorParticleIMPT'));
+ assertEqual(generator.radiationMode,'carbon');
+ assertEqual(generator.machine,'Generic');
+
+ heliumDummyPln = struct('radiationMode','helium','machine','Generic');
+ generator = matRad_StfGeneratorBase.getGeneratorFromPln(heliumDummyPln);
+ assertTrue(isa(generator,'matRad_StfGeneratorParticleIMPT'));
+ assertEqual(generator.radiationMode,'helium');
+ assertEqual(generator.machine,'Generic');
+
+ brachyDummyPln = struct('radiationMode','brachy','machine','HDR');
+ generator = matRad_StfGeneratorBase.getGeneratorFromPln(brachyDummyPln);
+ assertTrue(isa(generator,'matRad_StfGeneratorBrachy'));
+ assertEqual(generator.radiationMode,'brachy');
+ assertEqual(generator.machine,'HDR');
+
+function test_getGeneratorFromPlnByName
+ protonDummyPln = struct('radiationMode','protons','machine','Generic','propStf',struct('generator','ParticleIMPT'));
+ generator = matRad_StfGeneratorBase.getGeneratorFromPln(protonDummyPln);
+ assertTrue(isa(generator,'matRad_StfGeneratorParticleIMPT'));
+
+ %Wrong name
+ photonDummyPln = struct('radiationMode','photons','machine','Generic','propStf',struct('generator','SimpleBrachy'));
+ generator = matRad_StfGeneratorBase.getGeneratorFromPln(photonDummyPln);
+ assertTrue(isa(generator,'matRad_StfGeneratorPhotonIMRT'));
diff --git a/test/testData/carbon_testData.mat b/test/testData/carbon_testData.mat
new file mode 100644
index 000000000..097b54828
Binary files /dev/null and b/test/testData/carbon_testData.mat differ
diff --git a/test/testData/helium_testData.mat b/test/testData/helium_testData.mat
new file mode 100644
index 000000000..3f3aba6c6
Binary files /dev/null and b/test/testData/helium_testData.mat differ
diff --git a/test/testData/helper_testDataCreater.m b/test/testData/helper_testDataCreater.m
new file mode 100644
index 000000000..9837f466e
--- /dev/null
+++ b/test/testData/helper_testDataCreater.m
@@ -0,0 +1,68 @@
+% this script creates a testing ct,cst,stf,pln for external radiation
+% therapy, that can be red in and used in testing
+
+%% create ct
+
+ct = struct();
+ct.cubeDim = [20,10,10];
+ct.resolution.x = 10;
+ct.resolution.y = 10;
+ct.resolution.z = 10;
+ct.numOfCtScen = 1;
+ct.cubeHU{1} = ones(ct.cubeDim) * -1000;
+
+ct.cubeHU{1}(2:19,2:9,2:9) = 0;
+VolHelper = false(ct.cubeDim);
+VolHelper(2:19,2:9,2:9) = true;
+ixBody = find(VolHelper);
+VolHelper = false(ct.cubeDim);
+VolHelper(10:11,5:6,5:6) = true;
+ixTarget = find(VolHelper);
+
+for i = 1:2
+ cst{i,1} = i-1;
+ cst{i,5}.TissueClass = 1;
+ cst{i,5}.alphaX = 0.1000;
+ cst{i,5}.betaX = 0.0500;
+ cst{i,5}.Priority = i;
+ cst{i,5}.Visible = 1;
+ cst{i,5}.visibleColor = [0 0 0];
+
+end
+cst{1,2} = 'Target';
+cst{1,3} = 'TARGET';
+cst{1,4}{1} = ixTarget;
+cst{1,6}{1} = struct(DoseObjectives.matRad_SquaredDeviation(800,45));
+cst{2,2} = 'Body';
+cst{2,3} = 'OAR';
+cst{2,4}{1} = ixBody;
+cst{2,6}{1} = struct(DoseObjectives.matRad_SquaredOverdosing(400,0));
+
+clear VolHelper ixBody ixTarget i
+%% create pln, stf
+radMode = 'carbon'; %protons,helium,carbon;
+
+pln.radiationMode = radMode;
+pln.machine = 'Generic';
+pln.numOfFractions = 30;
+pln.propStf.gantryAngles = [0,180];
+pln.propStf.couchAngles = zeros(size(pln.propStf.gantryAngles));
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
+
+pln.propStf.longitudinalSpotSpacing = 8;
+pln.propStf.bixelWidth = 10;
+pln.propDoseCalc.doseGrid.resolution = struct('x',10,'y',10,'z',10); %[mm]
+
+%pln.bioParam = matRad_bioModel(pln.radiationMode,'physicalDose','none');
+
+%% Generate Beam Geometry STF
+pln.propStf.addMargin = false; %to make smaller stf, les bixel
+stf = matRad_generateStf(ct,cst,pln);
+ct = matRad_calcWaterEqD(ct, pln);
+%% Dose Calculation
+%dij = matRad_calcDoseInfluence(ct,cst,stf,pln);
+%resultGUI = matRad_calcCubes(ones(dij.totalNumOfBixels,1),dij);
+
+%% save basic data
+save([radMode '_testData.mat'],'ct','cst','pln','stf','-v7')
\ No newline at end of file
diff --git a/test/testData/photons_testData.mat b/test/testData/photons_testData.mat
new file mode 100644
index 000000000..25df3452c
Binary files /dev/null and b/test/testData/photons_testData.mat differ
diff --git a/test/testData/protons_testData.mat b/test/testData/protons_testData.mat
new file mode 100644
index 000000000..d845f082d
Binary files /dev/null and b/test/testData/protons_testData.mat differ
diff --git a/test/util/test_compareDijStf.m b/test/util/test_compareDijStf.m
new file mode 100644
index 000000000..8442fe4fb
--- /dev/null
+++ b/test/util/test_compareDijStf.m
@@ -0,0 +1,34 @@
+function test_suite = test_matRad_compareDijStf
+test_functions=localfunctions();
+
+initTestSuite;
+
+function [dij,stf] = helper_getDummyBasicDijStf()
+ dij = struct('numOfRaysPerBeam', [100, 200], 'numOfBeams', 2);
+ stf = [struct('numOfRays',100,'gantryAngle',0), struct('numOfRays',200,'gantryAngle',90)];
+
+% Test case 1: Matching dij and stf
+function test_matchingDijStf
+ [dij, stf] = helper_getDummyBasicDijStf();
+ [allMatch, msg] = matRad_compareDijStf(dij, stf);
+ assertTrue(allMatch);
+ assertTrue(isempty(msg));
+
+% Test case 2: Different number of rays per beam
+function test_differentRaysPerBeam
+ [dij, stf] = helper_getDummyBasicDijStf();
+ dij.numOfRaysPerBeam = [100, 300];
+ [allMatch, msg] = matRad_compareDijStf(dij, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
+
+% Test case 3: Different number of beams
+function test_differentNumOfBeams
+ [dij, stf] = helper_getDummyBasicDijStf();
+ dij.numOfBeams = 3;
+ [allMatch, msg] = matRad_compareDijStf(dij, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
+
diff --git a/test/util/test_comparePlnStf.m b/test/util/test_comparePlnStf.m
new file mode 100644
index 000000000..5c60df506
--- /dev/null
+++ b/test/util/test_comparePlnStf.m
@@ -0,0 +1,81 @@
+function test_suite = test_comparePlnStf
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+function [pln,stf] = helper_getDummyBasicPlnStf()
+ pln = struct('radiationMode', 'photons',...
+ 'propStf', struct('numOfBeams', 2, 'gantryAngles', [0, 90],'couchAngles', [0, 90],'bixelWidth', 5, 'isoCenter', [0, 0, 0; 1, 1, 1]));
+ stf = [struct('gantryAngle', 0,'couchAngle', 0,'bixelWidth', 5, 'isoCenter', [0, 0, 0], 'radiationMode', 'photons'),...
+ struct('gantryAngle', 90,'couchAngle', 90,'bixelWidth', 5, 'isoCenter', [1, 1, 1], 'radiationMode', 'photons')];
+
+ % Match should not work if pln does not have propStf
+function test_missingPropStf
+ pln = struct();
+ stf = struct();
+ [allMatch, msg] = matRad_comparePlnStf(pln, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
+
+% Test case for matching gantry angles
+function test_matching
+ [pln, stf] = helper_getDummyBasicPlnStf();
+ [allMatch, msg] = matRad_comparePlnStf(pln, stf);
+ assertTrue(allMatch);
+ assertTrue(isempty(msg));
+
+% Test case for non-matching gantry angles
+function test_nonMatchingGantryAngles
+ [pln, stf] = helper_getDummyBasicPlnStf();
+ pln.propStf.gantryAngles = [0, 75];
+ [allMatch, msg] = matRad_comparePlnStf(pln, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
+
+% Test case for non-matching couch angles
+function test_nonMatchingCouchAngles
+ [pln, stf] = helper_getDummyBasicPlnStf();
+ pln.propStf.couchAngles = [0, 75];
+ [allMatch, msg] = matRad_comparePlnStf(pln, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
+
+%Test case for wrong number of beams provided
+function test_wrongNumberOfAngles
+ [pln, stf] = helper_getDummyBasicPlnStf();
+ pln.propStf.numOfBeams = 3;
+ [allMatch, msg] = matRad_comparePlnStf(pln, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
+
+% Test case for non-matching bixel width
+function test_nonMatchingBixelWidth
+ [pln, stf] = helper_getDummyBasicPlnStf();
+ pln.propStf.bixelWidth = 10;
+ [allMatch, msg] = matRad_comparePlnStf(pln, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
+
+% Test case for non-matching radiation mode
+function test_nonMatchingRadiationMode
+ [pln, stf] = helper_getDummyBasicPlnStf();
+ pln.radiationMode = 'protons';
+ [allMatch, msg] = matRad_comparePlnStf(pln, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
+
+% Test case for non-matching isocenters
+function test_nonMatchingIsocenters
+ [pln, stf] = helper_getDummyBasicPlnStf();
+ pln.propStf.isoCenter = [0, 0, 0; 1, 1, 2];
+ [allMatch, msg] = matRad_comparePlnStf(pln, stf);
+ assertFalse(allMatch);
+ assertFalse(isempty(msg));
+ assertTrue(ischar(msg));
diff --git a/test/util/test_generateBodyContour.m b/test/util/test_generateBodyContour.m
new file mode 100644
index 000000000..7f972bc4f
--- /dev/null
+++ b/test/util/test_generateBodyContour.m
@@ -0,0 +1,44 @@
+function test_suite = test_generateBodyContour
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_boxphantom_contour
+ load BOXPHANTOM.mat
+ cstNew = matRad_generateBodyContour(ct,cst);
+ voxIxGenerated = cstNew{end,4}{1};
+ voxIxExisting = cstNew{1,4}{1};
+
+ assertEqual(numel(voxIxGenerated),numel(voxIxExisting));
+ assertEqual(sort(voxIxGenerated),sort(voxIxExisting));
+
+function test_boxphantom_contour_withThreshold
+ load BOXPHANTOM.mat
+ cstNew = matRad_generateBodyContour(ct,cst,10);
+ voxIxGenerated = cstNew{end,4}{1};
+ assertTrue(isempty(voxIxGenerated));
+
+
\ No newline at end of file
diff --git a/test/util/test_interp1.m b/test/util/test_interp1.m
new file mode 100644
index 000000000..841217541
--- /dev/null
+++ b/test/util/test_interp1.m
@@ -0,0 +1,221 @@
+function test_suite = test_interp1
+
+test_functions=localfunctions();
+
+initTestSuite;
+
+% Test basic values
+function test_matrad_interp1_values
+ %R = realmax; % For R = realmax, the test may often fail because of Inf in matRad_interp1: should first divide and then multiply.
+ R = 10^100;
+
+ % Pick a vector x and sort
+ % First element of x is in [-R, R]
+ x1el1 = (2*rand - 1)*R;
+ if x1el1 < 0
+ x1el2 = x1el1 + rand*(R);
+ else
+ x1el2 = x1el1 + rand*(R - x1el1);
+ end
+ if x1el1<=x1el2
+ x1 = [x1el1; x1el2];
+ else
+ x1 = [x1el2; x1el1];
+ end
+
+ % Pick a sorted vector y
+ y1el1 = (2*rand - 1)*R;
+ if y1el1 < 0
+ y1el2 = y1el1 + rand*(R);
+ else
+ y1el2 = y1el1 + rand*(R - y1el1);
+ end
+ y1 = [y1el1; y1el2];
+
+ % Pick a single value intermediate in x
+ x2 = rand*(x1(2)-x1(1)) + x1(1);
+
+ % Verify interpolation works correctly
+ y2 = matRad_interp1(x1, y1, x2);
+ expectedy2 = y1(1) + (x2 - x1(1))*((y1(2) - y1(1))/(x1(2) - x1(1)));
+ assertTrue(~isnan(y2));
+ assertElementsAlmostEqual(y2, expectedy2);
+
+ % Flip y vector and check interpolation again
+ y1 = flip(y1);
+ y2 = matRad_interp1(x1, y1, x2);
+ expectedy2 = y1(1) + (x2 - x1(1))*(y1(2) - y1(1))/(x1(2) - x1(1));
+ assertTrue(~isnan(y2));
+ assertElementsAlmostEqual(y2, expectedy2);
+
+ % Pick a x value exceeding upper boundaries
+ % If x2 is outside boundaries, we expect a NaN
+ x2 = x1(2) + rand*(R - x1(2));
+ if x1(2) == R
+ assertTrue(~isnan(matRad_interp1(x1, y1, x2)));
+ assertEqual(x2, x1(2));
+ else
+ assertTrue(isnan(matRad_interp1(x1, y1, x2)));
+ end
+ % Pick a x value exceeding lower boundaries
+ % If x2 is outside boundaries, we expect a NaN
+ x2 = x1(1) + rand*(-R - x1(1));
+ if x1(1) == -R
+ assertTrue(~isnan(matRad_interp1(x1, y1, x2)));
+ assertEqual(x2, x1(2));
+ else
+ assertTrue(isnan(matRad_interp1(x1, y1, x2)));
+ end
+
+
+% Test Extrapolation Methods
+function test_matRad_interp1_extrapolation
+ R = 10^100; % Choose a maximum scale
+ realExtrap = R*(2*rand-1); % Choose a random value for Real Extrapolation
+
+ % Pick a vector x and sort
+ % First element of x is in [-R, R]
+ x1el1 = (2*rand - 1)*R;
+ if x1el1 < 0
+ x1el2 = x1el1 + rand*(R);
+ else
+ x1el2 = x1el1 + rand*(R - x1el1);
+ end
+ if x1el1<=x1el2
+ x1 = [x1el1; x1el2];
+ else
+ x1 = [x1el2; x1el1];
+ end
+
+ % Pick a sorted vector y
+ y1el1 = (2*rand - 1)*R;
+ if y1el1 < 0
+ y1el2 = y1el1 + rand*(R);
+ else
+ y1el2 = y1el1 + rand*(R - y1el1);
+ end
+ y1 = [y1el1; y1el2];
+
+ % Pick 2 values exceeding Lower and Upper boundaries
+ % Pick 1 value in the boundaries
+ xLow = x1(1) + rand*(-R - x1(1));
+ xUp = x1(2) + rand*(R - x1(2));
+ x2 = rand*(x1(2)-x1(1)) + x1(1);
+ x2 = [xLow; x2; xUp];
+
+ % Index vector for out-of-boundaries values
+ outIdx = find(x2x1(2));
+ % [1] Real extrapolation: we expect a constant value for
+ % out-of-bondaries interpolation
+ y2 = matRad_interp1(x1, y1, x2, realExtrap);
+ assertElementsAlmostEqual(y2(outIdx), realExtrap.*ones(size(y2(outIdx))));
+ % [2] NaN & 'none': we expect NaNs for both of them in this case
+ % If numel(x2) == 1, we expect an error for 'none'----> Implement this!
+ if moxunit_util_platform_is_octave
+ assertExceptionThrown(@() matRad_interp1(x1, y1, x2, 'none'));
+ else
+ y2 = matRad_interp1(x1, y1, x2, NaN);
+ y3 = matRad_interp1(x1, y1, x2, 'none');
+ assertTrue( sum(isnan(y2(outIdx)))==length(outIdx) );
+ assertTrue( sum(isnan(y3(outIdx)))==length(outIdx) );
+ assertElementsAlmostEqual(y2, y3);
+ end
+ % [3] linear & extrap: in this case we expect they work the same
+ % If numel(x2) == 1, we expect an error for 'linear'----> Implement this!
+ y2 = matRad_interp1(x1, y1, x2, 'extrap');
+ y3 = matRad_interp1(x1, y1, x2, 'linear');
+ assertElementsAlmostEqual(y2, y3);
+
+function test_matRad_interp1_extrapolation_nearest
+ xi = [1 2 3]';
+ yi = [1 2 3]';
+ x = [0 1.5 4]';
+ y = matRad_interp1(xi,yi,x,'nearest');
+ assertEqual(y,[1; 1.5; 3]);
+
+ x = 0;
+ y = matRad_interp1(xi,yi,x,'nearest');
+ assertEqual(y,1);
+
+ x = 4;
+ y = matRad_interp1(xi,yi,x,'nearest');
+ assertEqual(y,3);
+
+ %Multiple y
+ xi = [1 2 3 4]';
+ yi = [1 2 3 4; 1 2 3 4]';
+ x = [-1 0 1.5 5 6]';
+ y = matRad_interp1(xi,yi,x,'nearest');
+ assertEqual(y,[1 1; 1 1; 1.5 1.5; 4 4; 4 4]);
+
+ x = 5;
+ y = matRad_interp1(xi,yi,x,'nearest');
+ assertEqual(y,[4 4]);
+
+ x = 0;
+ y = matRad_interp1(xi,yi,x,'nearest');
+ assertEqual(y,[1 1]);
+
+ %non-vector x
+ yi = [1 2 3 4]';
+ x = zeros(10,3,5);
+ x(1:5:numel(x)) = 2.5;
+ x(2:5:numel(x)) = 5;
+ ixSmaller = x == 0;
+ ixInside = x == 2.5;
+ ixLarger = x == 5;
+
+ y = matRad_interp1(xi,yi,x,'nearest');
+ assertEqual([numel(x) 1],size(y));
+ assertTrue(all(y(ixSmaller) == 1));
+ assertTrue(all(y(ixInside) == 2.5));
+ assertTrue(all(y(ixLarger) == 4));
+
+
+function test_matRad_interp1_errors
+ R = 10^100;
+
+ % Repetition Errors
+ % Pick x1 with repetitions and y1 sorted
+ x1 = (2*rand - 1)*R.*[1;1];
+ y1el1 = (2*rand - 1)*R;
+ if y1el1 < 0
+ y1el2 = y1el1 + rand*(R);
+ else
+ y1el2 = y1el1 + rand*(R - y1el1);
+ end
+ y1 = [y1el1; y1el2];
+ % [1] x2 is a vector: we expect error
+ % First value is repeted value in x1, second value is different;
+ x2 = [x1(1); rand.*((R-x1(1))/10)+x1(1)];
+ if moxunit_util_platform_is_octave
+ assertExceptionThrown(@() matRad_interp1(x1, y1, x2));
+ else
+ assertExceptionThrown(@() matRad_interp1(x1, y1, x2), 'MATLAB:griddedInterpolant:NonUniqueCompVecsPtsErrId');
+ end
+
+ % [2] x2 is a scalar, the result is NaN
+ y2 = matRad_interp1(x1, y1, x2(1));
+ assertTrue(isnan(y2));
+ y2 = matRad_interp1(x1, y1, x2(2));
+ assertTrue(isnan(y2));
+
+ % Extrapolation Errors
+ % [1] Single query point, case 'none'
+ x1 = [1; 10000];
+ y1 = [7.3; 2.4];
+ assertExceptionThrown(@() matRad_interp1(x1, y1, 0.5, 'none'));
+
+ % [2] Single query point, case 'linear'
+ assertExceptionThrown(@() matRad_interp1(x1, y1, 0.5, 'linear'));
+
+%{
+function test_matRad_interp1_multiple1D
+ R = 10^100;
+ x1 = (2.*rand(1000, 1)- 1).*R ;
+ x1 = unique(x1);
+ x1 = sort(x1);
+ y1 = (2.*rand(size(x1))- 1).*R;
+%}
+
+
diff --git a/test/util/test_recursiveFieldAssignment.m b/test/util/test_recursiveFieldAssignment.m
new file mode 100644
index 000000000..10296b6de
--- /dev/null
+++ b/test/util/test_recursiveFieldAssignment.m
@@ -0,0 +1,107 @@
+function test_suite = test_recursiveFieldAssignment
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+% Assigning fields from reference to assignTo
+function test_recursiveFieldAssignment_existingField
+ assignTo = struct('a', 1, 'b', struct('c', 2));
+ reference = struct('a', 3, 'b', struct('c', 4));
+ expected = struct('a', 3, 'b', struct('c', 4));
+ result = matRad_recursiveFieldAssignment(assignTo, reference,true);
+ assertEqual(result, expected);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,false);
+ assertEqual(result, assignTo);
+ %assertWarning(@() matRad_recursiveFieldAssignment(assignTo, reference,'TestWarn')); %Does not work for some reason, despite warnign being correctly raised
+
+
+function test_recursiveFieldAssignment_keepFields
+ assignTo = struct('a', 1, 'b', struct('c', 2), 'd', struct('e','hello'));
+ reference = struct('a', 3, 'b', struct('c', 4));
+ expected = struct('a', 3, 'b', struct('c', 4), 'd', struct('e','hello'));
+ result = matRad_recursiveFieldAssignment(assignTo, reference,true);
+ assertEqual(result, expected);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,false);
+ assertEqual(result, assignTo);
+
+% Assigning a struct field to a non-struct field
+function test_recursiveFieldAssignment_structToNonExisting
+ assignTo = struct();
+ reference = struct('a', 2);
+ expected = struct('a', 2);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,true);
+ assertEqual(result, expected);
+ %assertWarning(@() matRad_recursiveFieldAssignment(assignTo, reference)); %Does not work for some reason, despite warning being correctly raised
+ result = matRad_recursiveFieldAssignment(assignTo, reference,false);
+ assertEqual(result, expected);
+
+ assignTo = struct('a',2);
+ reference = struct('a',3,'b',4);
+ expected = struct('a',3,'b',4);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,true);
+ assertEqual(result, expected);
+
+ expected = struct('a',2,'b',4);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,false);
+ assertEqual(result, expected);
+
+% Overwriting a non-struct field with a non-struct field
+function test_recursiveFieldAssignment_nonStruct
+ assignTo = 1;
+ reference = 2;
+ expected = 2;
+ result = matRad_recursiveFieldAssignment(assignTo, reference,true);
+ assertEqual(result, expected);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,false);
+ assertEqual(result, assignTo);
+ %assertWarning(@() matRad_recursiveFieldAssignment(assignTo, reference,'TestWarn')); %Does not work for some reason, despite warnign being correctly raised
+
+% Overwriting a non-struct field with a struct field
+function test_recursiveFieldAssignment_nonExistingField
+ assignTo = struct('a', 1);
+ reference = struct('a', struct('b', 2));
+ expected = struct('a', struct('b', 2));
+ result = matRad_recursiveFieldAssignment(assignTo, reference,true);
+ assertEqual(result, expected);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,false);
+ assertEqual(result, assignTo);
+ %assertWarning(@() matRad_recursiveFieldAssignment(assignTo, reference)); %Does not work for some reason, despite warnign being correctly raised
+
+
+% Overwriting a struct field with a non-struct field
+function test_recursiveFieldAssignment_nestedField
+ assignTo = struct('a', struct('b', 2));
+ reference = struct('a', 3);
+ expected = struct('a', 3);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,true);
+ assertEqual(result, expected);
+ result = matRad_recursiveFieldAssignment(assignTo, reference,false);
+ assertEqual(result, assignTo);
+ %assertWarning(@() matRad_recursiveFieldAssignment(assignTo, reference)); %Does not work for some reason, despite warnign being correctly raised
+
+
+
+% Add more test cases here...
diff --git a/test/util/test_version.m b/test/util/test_version.m
new file mode 100644
index 000000000..ae61dee8a
--- /dev/null
+++ b/test/util/test_version.m
@@ -0,0 +1,39 @@
+function test_suite = test_version
+%The output should always be test_suite, and the function name the same as
+%your file name
+
+%% Header
+% The header is required to be in this format for automatic test collection
+% by MOxUnit
+
+%To collect all tests defined below, this is needed in newer Matlab
+%versions. test_functions will collect function handles to below test
+%functions
+test_functions=localfunctions();
+
+% This will initialize the test suite, i.e., take the functions from
+% test_functions, check if they contain "test", convert them into a MOxUnit
+% Test Case, and add them to the test-runner
+initTestSuite;
+
+%% Custom Tests
+% Tests use assert*-like Functions to check outputs etc:
+% assertTrue(a) - a is true
+% assertFalse(a) - a is false
+% assertEqual(a,b) - a and be are equal (isequal)
+% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance
+% assertVectorsAlmostEqual(a,b) - numerical test using vector norm
+% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id)
+% Check MOxUnit for more information or look at other tests
+
+function test_matrad_version
+ assertTrue(ischar(matRad_version()));
+ [str,full] = matRad_version();
+ assertTrue(isstruct(full));
+ assertTrue(ischar(str));
+
+function test_matrad_environment
+ env = matRad_getEnvironment();
+ assertTrue(ischar(env));
+ [~,verstr] = matRad_getEnvironment();
+ assertTrue(ischar(verstr));
\ No newline at end of file
diff --git a/optimization/optimizer/compile_ipopt_minGW_octave640.sh b/thirdParty/IPOPT/compile_ipopt_minGW_octave640.sh
similarity index 86%
rename from optimization/optimizer/compile_ipopt_minGW_octave640.sh
rename to thirdParty/IPOPT/compile_ipopt_minGW_octave640.sh
index e14846a90..57d6560e5 100644
--- a/optimization/optimizer/compile_ipopt_minGW_octave640.sh
+++ b/thirdParty/IPOPT/compile_ipopt_minGW_octave640.sh
@@ -27,9 +27,14 @@ sed -i 's,http://www.netlib.org/blas/,http://coin-or-tools.github.io/ThirdParty-
./get.Blas
cd $IPOPTDIR/ThirdParty/Lapack
./get.Lapack
+# For Metis, we need to clone the updated git with the new get url
+rm -rf $IPOPTDIR/ThirdParty/Metis
+git clone https://github.com/coin-or-tools/ThirdParty-Metis.git --depth 1 --branch releases/1.3.10 $IPOPTDIR/ThirdParty/Metis
cd $IPOPTDIR/ThirdParty/Metis
sed -i 's,http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/OLD/,http://coin-or-tools.github.io/ThirdParty-Metis/,g' get.Metis
./get.Metis
+rm -rf $IPOPTDIR/ThirdParty/Mumps
+git clone https://github.com/coin-or-tools/ThirdParty-Mumps.git --depth 1 --branch releases/1.6.3 $IPOPTDIR/ThirdParty/Mumps
cd $IPOPTDIR/ThirdParty/Mumps
# First we need to replace the url as the version can no longer be downloaded
sed -i 's,http://mumps.enseeiht.fr/,http://coin-or-tools.github.io/ThirdParty-Mumps/,g' get.Mumps
@@ -47,13 +52,13 @@ cp $MINGW_PREFIX/lib/gcc/$MINGW_CHOST/$GCC_VERSION/libquadmath.* /usr/lib/gcc/$M
mkdir build
cd build
-../configure --prefix=$IPOPTINSTALLDIR --disable-shared --enable-static
+../configure --prefix=$IPOPTINSTALLDIR --disable-shared --enable-static ADD_FFLAGS=-fallow-argument-mismatch
make
make install
cd ../..
-# If everything worked, you should see some (static) libraries when doing ls /usr/local/lib
+# If everything worked, you should see some (static) libraries when doing ls $IPOPTINSTALLDIR
# we can get the mex interface
git clone --depth 1 --branch 1.1.4 https://github.com/ebertolazzi/mexIPOPT
diff --git a/thirdParty/IPOPT/compile_ipopt_minGW_octave830.sh b/thirdParty/IPOPT/compile_ipopt_minGW_octave830.sh
new file mode 100644
index 000000000..57d6560e5
--- /dev/null
+++ b/thirdParty/IPOPT/compile_ipopt_minGW_octave830.sh
@@ -0,0 +1,77 @@
+# How to compile the IPOPT mex interface for Octave 6.4.0 (64-bit) in Windows
+# matRad only includes the IPOPT mex interface compiled for Matlab. It is also possible to compile the interface from the MSYS/MinGW distribution included in Octave for Windows.
+# The following has been tested for Octave 6.4.0 in 64 bit version to allow 64-bit algebra. Start this script from an octave mingw shell. You can open such a shell by running "cmdshell.bat" (potentially as administrator)from your Octave install directory
+
+pacman -Sy
+# pacman -S --noconfirm --needed wget which git
+pacman -S --noconfirm --needed which
+
+echo "check_certificate = off" >> ~/.wgetrc
+
+# Run the following commands to create directories and get the IPOPT source. We dont need lapack and blas, since Octave comes with lapack and openblas
+
+mkdir ipopt && cd ipopt
+export IPOPTINSTALLDIR=`pwd`
+cd ..
+
+mkdir IpoptMUMPS && cd IpoptMUMPS
+export IPOPTDIR=`pwd`
+
+wget --no-check-certificate https://www.coin-or.org/download/source/Ipopt/Ipopt-3.12.13.tgz
+tar -zxvf Ipopt-3.12.13.tgz
+mv Ipopt-3.12.13/* ./
+
+cd $IPOPTDIR/ThirdParty/Blas
+# First we need to replace the url as the version can no longer be downloaded
+sed -i 's,http://www.netlib.org/blas/,http://coin-or-tools.github.io/ThirdParty-Blas/,g' get.Blas
+./get.Blas
+cd $IPOPTDIR/ThirdParty/Lapack
+./get.Lapack
+# For Metis, we need to clone the updated git with the new get url
+rm -rf $IPOPTDIR/ThirdParty/Metis
+git clone https://github.com/coin-or-tools/ThirdParty-Metis.git --depth 1 --branch releases/1.3.10 $IPOPTDIR/ThirdParty/Metis
+cd $IPOPTDIR/ThirdParty/Metis
+sed -i 's,http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/OLD/,http://coin-or-tools.github.io/ThirdParty-Metis/,g' get.Metis
+./get.Metis
+rm -rf $IPOPTDIR/ThirdParty/Mumps
+git clone https://github.com/coin-or-tools/ThirdParty-Mumps.git --depth 1 --branch releases/1.6.3 $IPOPTDIR/ThirdParty/Mumps
+cd $IPOPTDIR/ThirdParty/Mumps
+# First we need to replace the url as the version can no longer be downloaded
+sed -i 's,http://mumps.enseeiht.fr/,http://coin-or-tools.github.io/ThirdParty-Mumps/,g' get.Mumps
+./get.Mumps
+
+cd $IPOPTDIR
+#- Now we need a workaround for a problem regarding the quadmath library which cannot be found when compiling MUMPS. Run the following command
+export GCC_VERSION=`gcc --version | head -n1 | cut -d" " -f3`
+mkdir -p /usr/lib/gcc/$MINGW_CHOST/$GCC_VERSION/
+cp $MINGW_PREFIX/lib/gcc/$MINGW_CHOST/$GCC_VERSION/libquadmath.* /usr/lib/gcc/$MINGW_CHOST/$GCC_VERSION/
+
+
+# Now lets start the build process
+
+mkdir build
+cd build
+
+../configure --prefix=$IPOPTINSTALLDIR --disable-shared --enable-static ADD_FFLAGS=-fallow-argument-mismatch
+make
+make install
+
+cd ../..
+
+# If everything worked, you should see some (static) libraries when doing ls $IPOPTINSTALLDIR
+# we can get the mex interface
+
+git clone --depth 1 --branch 1.1.4 https://github.com/ebertolazzi/mexIPOPT
+
+# and compile it.
+mkoctfile --mex -ImexIPOPT/toolbox/src -I$IPOPTINSTALLDIR/include/coin mexIPOPT/toolbox/src/ipopt.cc mexIPOPT/toolbox/src/IpoptInterfaceCommon.cc -v -DMATLAB_MEXFILE -DHAVE_CSTDDEF -DIPOPT_INTERFACE_MISSING_COPY_N -lipopt -lcoinmumps -lcoinmetis -lcoinlapack -lcoinblas -lgfortran -L$IPOPTINSTALLDIR/lib
+
+echo "Do you wish to remove the IPOPT compilation directories?"
+select yn in "Yes" "No"; do
+ case $yn in
+ Yes ) rm -rf $IPOPTDIR; rm -rf mexIPOPT; break;;
+ No ) exit;;
+ esac
+done
+
+
diff --git a/thirdParty/IPOPT/compile_ipopt_minGW_octave920.sh b/thirdParty/IPOPT/compile_ipopt_minGW_octave920.sh
new file mode 100644
index 000000000..58a60c765
--- /dev/null
+++ b/thirdParty/IPOPT/compile_ipopt_minGW_octave920.sh
@@ -0,0 +1,82 @@
+# How to compile the IPOPT mex interface for Octave 9.2.0 (64-bit) in Windows
+# matRad only includes the IPOPT mex interface compiled for Matlab.
+# It is also possible to compile the interface from the MSYS/MinGW distribution
+# included in Octave for Windows.
+# The following has been tested for Octave 9.2.0 in 64 bit version to allow
+# 64-bit algebra. Start this script from an octave mingw shell.
+# You can open such a shell by running "cmdshell.bat" (potentially as
+# administrator)from your Octave install directory
+
+pacman -Sy
+# pacman -S --noconfirm --needed wget which git
+pacman -S --noconfirm --needed which
+
+echo "check_certificate = off" >> ~/.wgetrc
+
+# Run the following commands to create directories and get the IPOPT source. We dont need lapack and blas, since Octave comes with lapack and openblas
+
+mkdir ipopt && cd ipopt
+export IPOPTINSTALLDIR=`pwd`
+cd ..
+
+mkdir IpoptMUMPS && cd IpoptMUMPS
+export IPOPTDIR=`pwd`
+
+wget --no-check-certificate https://www.coin-or.org/download/source/Ipopt/Ipopt-3.12.13.tgz
+tar -zxvf Ipopt-3.12.13.tgz
+mv Ipopt-3.12.13/* ./
+
+cd $IPOPTDIR/ThirdParty/Blas
+# First we need to replace the url as the version can no longer be downloaded
+sed -i 's,http://www.netlib.org/blas/,http://coin-or-tools.github.io/ThirdParty-Blas/,g' get.Blas
+./get.Blas
+cd $IPOPTDIR/ThirdParty/Lapack
+./get.Lapack
+# For Metis, we need to clone the updated git with the new get url
+rm -rf $IPOPTDIR/ThirdParty/Metis
+git clone https://github.com/coin-or-tools/ThirdParty-Metis.git --depth 1 --branch releases/1.3.10 $IPOPTDIR/ThirdParty/Metis
+cd $IPOPTDIR/ThirdParty/Metis
+sed -i 's,http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/OLD/,http://coin-or-tools.github.io/ThirdParty-Metis/,g' get.Metis
+./get.Metis
+rm -rf $IPOPTDIR/ThirdParty/Mumps
+git clone https://github.com/coin-or-tools/ThirdParty-Mumps.git --depth 1 --branch releases/1.6.3 $IPOPTDIR/ThirdParty/Mumps
+cd $IPOPTDIR/ThirdParty/Mumps
+# First we need to replace the url as the version can no longer be downloaded
+sed -i 's,http://mumps.enseeiht.fr/,http://coin-or-tools.github.io/ThirdParty-Mumps/,g' get.Mumps
+./get.Mumps
+
+cd $IPOPTDIR
+#- Now we need a workaround for a problem regarding the quadmath library which cannot be found when compiling MUMPS. Run the following command
+export GCC_VERSION=`gcc --version | head -n1 | cut -d" " -f3`
+mkdir -p /usr/lib/gcc/$MINGW_CHOST/$GCC_VERSION/
+cp $MINGW_PREFIX/lib/gcc/$MINGW_CHOST/$GCC_VERSION/libquadmath.* /usr/lib/gcc/$MINGW_CHOST/$GCC_VERSION/
+
+
+# Now lets start the build process
+
+mkdir build
+cd build
+
+../configure --prefix=$IPOPTINSTALLDIR --disable-shared --enable-static ADD_FFLAGS=-fallow-argument-mismatch
+make
+make install
+
+cd ../..
+
+# If everything worked, you should see some (static) libraries when doing ls $IPOPTINSTALLDIR
+# we can get the mex interface
+
+git clone --depth 1 --branch 1.1.4 https://github.com/ebertolazzi/mexIPOPT
+
+# and compile it.
+mkoctfile --mex -ImexIPOPT/toolbox/src -I$IPOPTINSTALLDIR/include/coin mexIPOPT/toolbox/src/ipopt.cc mexIPOPT/toolbox/src/IpoptInterfaceCommon.cc -v -DMATLAB_MEXFILE -DHAVE_CSTDDEF -DIPOPT_INTERFACE_MISSING_COPY_N -lipopt -lcoinmumps -lcoinmetis -lcoinlapack -lcoinblas -lgfortran -L$IPOPTINSTALLDIR/lib
+
+echo "Do you wish to remove the IPOPT compilation directories?"
+select yn in "Yes" "No"; do
+ case $yn in
+ Yes ) rm -rf $IPOPTDIR; rm -rf mexIPOPT; break;;
+ No ) exit;;
+ esac
+done
+
+
diff --git a/optimization/optimizer/compile_ipopt_ubuntu22_octave640.sh b/thirdParty/IPOPT/compile_ipopt_ubuntu22_octave640.sh
similarity index 100%
rename from optimization/optimizer/compile_ipopt_ubuntu22_octave640.sh
rename to thirdParty/IPOPT/compile_ipopt_ubuntu22_octave640.sh
diff --git a/optimization/optimizer/ipopt.m b/thirdParty/IPOPT/ipopt.m
similarity index 100%
rename from optimization/optimizer/ipopt.m
rename to thirdParty/IPOPT/ipopt.m
diff --git a/optimization/optimizer/ipopt.mexa64 b/thirdParty/IPOPT/ipopt.mexa64
similarity index 100%
rename from optimization/optimizer/ipopt.mexa64
rename to thirdParty/IPOPT/ipopt.mexa64
diff --git a/optimization/optimizer/ipopt.mexmaci64 b/thirdParty/IPOPT/ipopt.mexmaci64
similarity index 100%
rename from optimization/optimizer/ipopt.mexmaci64
rename to thirdParty/IPOPT/ipopt.mexmaci64
diff --git a/optimization/optimizer/ipopt.mexoct640a64 b/thirdParty/IPOPT/ipopt.mexoct640a64
similarity index 100%
rename from optimization/optimizer/ipopt.mexoct640a64
rename to thirdParty/IPOPT/ipopt.mexoct640a64
diff --git a/optimization/optimizer/ipopt.mexoct640w64 b/thirdParty/IPOPT/ipopt.mexoct640w64
similarity index 100%
rename from optimization/optimizer/ipopt.mexoct640w64
rename to thirdParty/IPOPT/ipopt.mexoct640w64
diff --git a/thirdParty/IPOPT/ipopt.mexoct920w64 b/thirdParty/IPOPT/ipopt.mexoct920w64
new file mode 100644
index 000000000..216d9a382
Binary files /dev/null and b/thirdParty/IPOPT/ipopt.mexoct920w64 differ
diff --git a/optimization/optimizer/ipopt.mexw64 b/thirdParty/IPOPT/ipopt.mexw64
similarity index 100%
rename from optimization/optimizer/ipopt.mexw64
rename to thirdParty/IPOPT/ipopt.mexw64
diff --git a/MCsquare/bin/LICENSE b/thirdParty/MCsquare/LICENSE
similarity index 100%
rename from MCsquare/bin/LICENSE
rename to thirdParty/MCsquare/LICENSE
diff --git a/MCsquare/bin/BDL/BDL_default_DN.txt b/thirdParty/MCsquare/bin/BDL/BDL_default_DN.txt
similarity index 100%
rename from MCsquare/bin/BDL/BDL_default_DN.txt
rename to thirdParty/MCsquare/bin/BDL/BDL_default_DN.txt
diff --git a/MCsquare/bin/BDL/BDL_default_DN_RangeShifter.txt b/thirdParty/MCsquare/bin/BDL/BDL_default_DN_RangeShifter.txt
similarity index 100%
rename from MCsquare/bin/BDL/BDL_default_DN_RangeShifter.txt
rename to thirdParty/MCsquare/bin/BDL/BDL_default_DN_RangeShifter.txt
diff --git a/MCsquare/bin/BDL/BDL_default_UN.txt b/thirdParty/MCsquare/bin/BDL/BDL_default_UN.txt
similarity index 100%
rename from MCsquare/bin/BDL/BDL_default_UN.txt
rename to thirdParty/MCsquare/bin/BDL/BDL_default_UN.txt
diff --git a/MCsquare/bin/BDL/BDL_default_UN_RangeShifter.txt b/thirdParty/MCsquare/bin/BDL/BDL_default_UN_RangeShifter.txt
similarity index 100%
rename from MCsquare/bin/BDL/BDL_default_UN_RangeShifter.txt
rename to thirdParty/MCsquare/bin/BDL/BDL_default_UN_RangeShifter.txt
diff --git a/MCsquare/bin/BDL/BDL_matrad.txt b/thirdParty/MCsquare/bin/BDL/BDL_matrad.txt
similarity index 100%
rename from MCsquare/bin/BDL/BDL_matrad.txt
rename to thirdParty/MCsquare/bin/BDL/BDL_matrad.txt
diff --git a/MCsquare/bin/MCSquare_windows.exe b/thirdParty/MCsquare/bin/MCSquare_windows.exe
similarity index 100%
rename from MCsquare/bin/MCSquare_windows.exe
rename to thirdParty/MCsquare/bin/MCSquare_windows.exe
diff --git a/MCsquare/bin/MCsquare_linux b/thirdParty/MCsquare/bin/MCsquare_linux
similarity index 100%
rename from MCsquare/bin/MCsquare_linux
rename to thirdParty/MCsquare/bin/MCsquare_linux
diff --git a/MCsquare/bin/MCsquare_mac b/thirdParty/MCsquare/bin/MCsquare_mac
similarity index 100%
rename from MCsquare/bin/MCsquare_mac
rename to thirdParty/MCsquare/bin/MCsquare_mac
diff --git a/MCsquare/bin/Materials.zip b/thirdParty/MCsquare/bin/Materials.zip
similarity index 100%
rename from MCsquare/bin/Materials.zip
rename to thirdParty/MCsquare/bin/Materials.zip
diff --git a/MCsquare/bin/Scanners/SolidWater_Phantom/HU_Density_Conversion.txt b/thirdParty/MCsquare/bin/Scanners/SolidWater_Phantom/HU_Density_Conversion.txt
similarity index 100%
rename from MCsquare/bin/Scanners/SolidWater_Phantom/HU_Density_Conversion.txt
rename to thirdParty/MCsquare/bin/Scanners/SolidWater_Phantom/HU_Density_Conversion.txt
diff --git a/MCsquare/bin/Scanners/SolidWater_Phantom/HU_Material_Conversion.txt b/thirdParty/MCsquare/bin/Scanners/SolidWater_Phantom/HU_Material_Conversion.txt
similarity index 100%
rename from MCsquare/bin/Scanners/SolidWater_Phantom/HU_Material_Conversion.txt
rename to thirdParty/MCsquare/bin/Scanners/SolidWater_Phantom/HU_Material_Conversion.txt
diff --git a/MCsquare/bin/Scanners/Water_Phantom/HU_Density_Conversion.txt b/thirdParty/MCsquare/bin/Scanners/Water_Phantom/HU_Density_Conversion.txt
similarity index 100%
rename from MCsquare/bin/Scanners/Water_Phantom/HU_Density_Conversion.txt
rename to thirdParty/MCsquare/bin/Scanners/Water_Phantom/HU_Density_Conversion.txt
diff --git a/MCsquare/bin/Scanners/Water_Phantom/HU_Material_Conversion.txt b/thirdParty/MCsquare/bin/Scanners/Water_Phantom/HU_Material_Conversion.txt
similarity index 100%
rename from MCsquare/bin/Scanners/Water_Phantom/HU_Material_Conversion.txt
rename to thirdParty/MCsquare/bin/Scanners/Water_Phantom/HU_Material_Conversion.txt
diff --git a/MCsquare/bin/Scanners/default/HU_Density_Conversion.txt b/thirdParty/MCsquare/bin/Scanners/default/HU_Density_Conversion.txt
similarity index 94%
rename from MCsquare/bin/Scanners/default/HU_Density_Conversion.txt
rename to thirdParty/MCsquare/bin/Scanners/default/HU_Density_Conversion.txt
index 23efa1ca0..a1a1fbd8a 100644
--- a/MCsquare/bin/Scanners/default/HU_Density_Conversion.txt
+++ b/thirdParty/MCsquare/bin/Scanners/default/HU_Density_Conversion.txt
@@ -15,4 +15,4 @@
588 1.335
1046 1.560
1565 1.825
-5000 1.825
+5000 1.825
\ No newline at end of file
diff --git a/MCsquare/bin/Scanners/default/HU_Material_Conversion.txt b/thirdParty/MCsquare/bin/Scanners/default/HU_Material_Conversion.txt
similarity index 95%
rename from MCsquare/bin/Scanners/default/HU_Material_Conversion.txt
rename to thirdParty/MCsquare/bin/Scanners/default/HU_Material_Conversion.txt
index a7341586c..c65e9d85a 100644
--- a/MCsquare/bin/Scanners/default/HU_Material_Conversion.txt
+++ b/thirdParty/MCsquare/bin/Scanners/default/HU_Material_Conversion.txt
@@ -25,5 +25,4 @@
1200 59 # Schneider_Marrow_Bone12
1300 60 # Schneider_Marrow_Bone13
1400 61 # Schneider_Marrow_Bone14
-1500 62 # Schneider_Marrow_Bone15
-
+1500 62 # Schneider_Marrow_Bone15
\ No newline at end of file
diff --git a/thirdParty/MCsquare/bin/Scanners/matRad_default/HU_Density_Conversion.txt b/thirdParty/MCsquare/bin/Scanners/matRad_default/HU_Density_Conversion.txt
new file mode 100644
index 000000000..d1babc1a0
--- /dev/null
+++ b/thirdParty/MCsquare/bin/Scanners/matRad_default/HU_Density_Conversion.txt
@@ -0,0 +1,11 @@
+# ===================
+# HU density g/cm3
+# ===================
+-1024.0 0.001
+-999 0.001
+0 1
+200.0 1.20000
+449.0 1.20001
+2000.0 2.49066
+2048 2.53060
+3071 2.53061
\ No newline at end of file
diff --git a/MCsquare/bin/Scanners/matRad_water/HU_Material_Conversion.txt b/thirdParty/MCsquare/bin/Scanners/matRad_default/HU_Material_Conversion.txt
similarity index 100%
rename from MCsquare/bin/Scanners/matRad_water/HU_Material_Conversion.txt
rename to thirdParty/MCsquare/bin/Scanners/matRad_default/HU_Material_Conversion.txt
diff --git a/thirdParty/MCsquare/bin/Scanners/matRad_default/README.md b/thirdParty/MCsquare/bin/Scanners/matRad_default/README.md
new file mode 100644
index 000000000..de6a5aa4a
--- /dev/null
+++ b/thirdParty/MCsquare/bin/Scanners/matRad_default/README.md
@@ -0,0 +1 @@
+The HU to SPR conversion is set to the matRad default HU lookup table for consistency with the conversion used for dose calculation by the PBA.
\ No newline at end of file
diff --git a/MCsquare/bin/libiomp5md.dll b/thirdParty/MCsquare/bin/libiomp5md.dll
similarity index 100%
rename from MCsquare/bin/libiomp5md.dll
rename to thirdParty/MCsquare/bin/libiomp5md.dll
diff --git a/ompMC/LICENSE b/thirdParty/ompMC/LICENSE
similarity index 100%
rename from ompMC/LICENSE
rename to thirdParty/ompMC/LICENSE
diff --git a/ompMC/data/msnew.data b/thirdParty/ompMC/data/msnew.data
similarity index 100%
rename from ompMC/data/msnew.data
rename to thirdParty/ompMC/data/msnew.data
diff --git a/ompMC/data/spinms.data b/thirdParty/ompMC/data/spinms.data
similarity index 100%
rename from ompMC/data/spinms.data
rename to thirdParty/ompMC/data/spinms.data
diff --git a/ompMC/data/xcom_compton.data b/thirdParty/ompMC/data/xcom_compton.data
similarity index 100%
rename from ompMC/data/xcom_compton.data
rename to thirdParty/ompMC/data/xcom_compton.data
diff --git a/ompMC/data/xcom_pair.data b/thirdParty/ompMC/data/xcom_pair.data
similarity index 100%
rename from ompMC/data/xcom_pair.data
rename to thirdParty/ompMC/data/xcom_pair.data
diff --git a/ompMC/data/xcom_photo.data b/thirdParty/ompMC/data/xcom_photo.data
similarity index 100%
rename from ompMC/data/xcom_photo.data
rename to thirdParty/ompMC/data/xcom_photo.data
diff --git a/ompMC/data/xcom_rayleigh.data b/thirdParty/ompMC/data/xcom_rayleigh.data
similarity index 100%
rename from ompMC/data/xcom_rayleigh.data
rename to thirdParty/ompMC/data/xcom_rayleigh.data
diff --git a/ompMC/data/xcom_triplet.data b/thirdParty/ompMC/data/xcom_triplet.data
similarity index 100%
rename from ompMC/data/xcom_triplet.data
rename to thirdParty/ompMC/data/xcom_triplet.data
diff --git a/ompMC/omc_matrad.mexa64 b/thirdParty/ompMC/omc_matrad.mexa64
old mode 100755
new mode 100644
similarity index 100%
rename from ompMC/omc_matrad.mexa64
rename to thirdParty/ompMC/omc_matrad.mexa64
diff --git a/ompMC/omc_matrad.mexmaci64 b/thirdParty/ompMC/omc_matrad.mexmaci64
old mode 100755
new mode 100644
similarity index 100%
rename from ompMC/omc_matrad.mexmaci64
rename to thirdParty/ompMC/omc_matrad.mexmaci64
diff --git a/ompMC/omc_matrad.mexoct640a64 b/thirdParty/ompMC/omc_matrad.mexoct640a64
similarity index 100%
rename from ompMC/omc_matrad.mexoct640a64
rename to thirdParty/ompMC/omc_matrad.mexoct640a64
diff --git a/ompMC/omc_matrad.mexoct640w64 b/thirdParty/ompMC/omc_matrad.mexoct640w64
similarity index 100%
rename from ompMC/omc_matrad.mexoct640w64
rename to thirdParty/ompMC/omc_matrad.mexoct640w64
diff --git a/ompMC/omc_matrad.mexw64 b/thirdParty/ompMC/omc_matrad.mexw64
similarity index 100%
rename from ompMC/omc_matrad.mexw64
rename to thirdParty/ompMC/omc_matrad.mexw64
diff --git a/ompMC/output/README b/thirdParty/ompMC/output/README
similarity index 100%
rename from ompMC/output/README
rename to thirdParty/ompMC/output/README
diff --git a/ompMC/pegs4/521icru.pegs4dat b/thirdParty/ompMC/pegs4/521icru.pegs4dat
similarity index 100%
rename from ompMC/pegs4/521icru.pegs4dat
rename to thirdParty/ompMC/pegs4/521icru.pegs4dat
diff --git a/ompMC/pegs4/700icru.pegs4dat b/thirdParty/ompMC/pegs4/700icru.pegs4dat
similarity index 100%
rename from ompMC/pegs4/700icru.pegs4dat
rename to thirdParty/ompMC/pegs4/700icru.pegs4dat
diff --git a/ompMC/pegs4/pgs4form.dat b/thirdParty/ompMC/pegs4/pgs4form.dat
similarity index 100%
rename from ompMC/pegs4/pgs4form.dat
rename to thirdParty/ompMC/pegs4/pgs4form.dat
diff --git a/ompMC/spectra/250.spectrum b/thirdParty/ompMC/spectra/250.spectrum
similarity index 100%
rename from ompMC/spectra/250.spectrum
rename to thirdParty/ompMC/spectra/250.spectrum
diff --git a/ompMC/spectra/mohan6.spectrum b/thirdParty/ompMC/spectra/mohan6.spectrum
similarity index 100%
rename from ompMC/spectra/mohan6.spectrum
rename to thirdParty/ompMC/spectra/mohan6.spectrum
diff --git a/ompMC/spectra/var_6MV.spectrum b/thirdParty/ompMC/spectra/var_6MV.spectrum
similarity index 100%
rename from ompMC/spectra/var_6MV.spectrum
rename to thirdParty/ompMC/spectra/var_6MV.spectrum
diff --git a/thirdParty/parfor_progress/LICENSE.md b/thirdParty/parfor_progress/LICENSE.md
new file mode 100644
index 000000000..35ddbfa45
--- /dev/null
+++ b/thirdParty/parfor_progress/LICENSE.md
@@ -0,0 +1,22 @@
+Copyright (c) 2011, Jeremy Scheff
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/thirdParty/parfor_progress/README.md b/thirdParty/parfor_progress/README.md
new file mode 100644
index 000000000..4bf177733
--- /dev/null
+++ b/thirdParty/parfor_progress/README.md
@@ -0,0 +1,3 @@
+https://github.com/dumbmatter/parfor_progress
+
+Jeremy (2024). Progress monitor (progress bar) that works with parfor (https://www.mathworks.com/matlabcentral/fileexchange/32101-progress-monitor-progress-bar-that-works-with-parfor), MATLAB Central File Exchange. Abgerufen 30. April 2024
\ No newline at end of file
diff --git a/thirdParty/parfor_progress/parfor_progress.m b/thirdParty/parfor_progress/parfor_progress.m
new file mode 100644
index 000000000..71bd76786
--- /dev/null
+++ b/thirdParty/parfor_progress/parfor_progress.m
@@ -0,0 +1,81 @@
+function percent = parfor_progress(N)
+%PARFOR_PROGRESS Progress monitor (progress bar) that works with parfor.
+% PARFOR_PROGRESS works by creating a file called parfor_progress.txt in
+% your working directory, and then keeping track of the parfor loop's
+% progress within that file. This workaround is necessary because parfor
+% workers cannot communicate with one another so there is no simple way
+% to know which iterations have finished and which haven't.
+%
+% PARFOR_PROGRESS(N) initializes the progress monitor for a set of N
+% upcoming calculations.
+%
+% PARFOR_PROGRESS updates the progress inside your parfor loop and
+% displays an updated progress bar.
+%
+% PARFOR_PROGRESS(0) deletes parfor_progress.txt and finalizes progress
+% bar.
+%
+% To suppress output from any of these functions, just ask for a return
+% variable from the function calls, like PERCENT = PARFOR_PROGRESS which
+% returns the percentage of completion.
+%
+% Example:
+%
+% N = 100;
+% parfor_progress(N);
+% parfor i=1:N
+% pause(rand); % Replace with real code
+% parfor_progress;
+% end
+% parfor_progress(0);
+%
+% See also PARFOR.
+
+% By Jeremy Scheff - jdscheff@gmail.com - http://www.jeremyscheff.com/
+
+narginchk(0, 1);
+
+if nargin < 1
+ N = -1;
+end
+
+percent = 0;
+w = 50; % Width of progress bar
+
+if N > 0
+ f = fopen('parfor_progress.txt', 'w');
+ if f<0
+ error('Do you have write permissions for %s?', pwd);
+ end
+ fprintf(f, '%d\n', N); % Save N at the top of progress.txt
+ fclose(f);
+
+ if nargout == 0
+ disp([' 0%[>', repmat(' ', 1, w), ']']);
+ end
+elseif N == 0
+ delete('parfor_progress.txt');
+ percent = 100;
+
+ if nargout == 0
+ disp([repmat(char(8), 1, (w+9)), char(10), '100%[', repmat('=', 1, w+1), ']']);
+ end
+else
+ if ~exist('parfor_progress.txt', 'file')
+ error('parfor_progress.txt not found. Run PARFOR_PROGRESS(N) before PARFOR_PROGRESS to initialize parfor_progress.txt.');
+ end
+
+ f = fopen('parfor_progress.txt', 'a');
+ fprintf(f, '1\n');
+ fclose(f);
+
+ f = fopen('parfor_progress.txt', 'r');
+ progress = fscanf(f, '%d');
+ fclose(f);
+ percent = (length(progress)-1)/progress(1)*100;
+
+ if nargout == 0
+ perc = sprintf('%3.0f%%', percent); % 4 characters wide, percentage
+ disp([repmat(char(8), 1, (w+9)), char(10), perc, '[', repmat('=', 1, round(percent*w/100)), '>', repmat(' ', 1, w - round(percent*w/100)), ']']);
+ end
+end
diff --git a/thirdParty/pu/LICENSE.md b/thirdParty/pu/LICENSE.md
new file mode 100644
index 000000000..d1c85c8d8
--- /dev/null
+++ b/thirdParty/pu/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright (c) 2009, E. Cojocaru
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the distribution
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/thirdParty/pu/README.md b/thirdParty/pu/README.md
new file mode 100644
index 000000000..2e8691639
--- /dev/null
+++ b/thirdParty/pu/README.md
@@ -0,0 +1 @@
+E. Cojocaru (2024). Parabolic Cylinder Functions (https://www.mathworks.com/matlabcentral/fileexchange/22620-parabolic-cylinder-functions), MATLAB Central File Exchange. Abgerufen 30. April 2024.
\ No newline at end of file
diff --git a/thirdParty/pu/pu.m b/thirdParty/pu/pu.m
new file mode 100644
index 000000000..ec04e7ff1
--- /dev/null
+++ b/thirdParty/pu/pu.m
@@ -0,0 +1,83 @@
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+function u=pu(a,x)
+% ===================================================================
+% Purpose: Compute parabolic cylinder function U(a,x)
+% Input : a --- Parameter (|a| < 5)
+% x --- Argument (|x| < 5)
+% Output : u ------ U(a,x)
+% ===================================================================
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% This program was inspired by the Matlab program 'specfun' (author
+% B. Barrowes) which is a direct conversion by 'f2matlab' (author
+% B. Barrowes) of the corresponding Fortran program in
+% S. Zhang and J. Jin, 'Computation of Special functions' (Wiley, 1966).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% E. Cojocaru, January 2009
+% Observations, suggestions and recommendations are welcome at e-mail:
+% ecojocaru@theory.nipne.ro
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+c=zeros(1,100);
+d=zeros(1,100);
+eps=1.0e-15;
+sqpi=sqrt(pi);
+c0=1.0;
+c1=a;
+c(1)=a;
+
+for k1=4:2:200%;
+ m=k1/2;
+ ck=a*c1+(k1-2.0)*(k1-3.0)*c0/4;
+ c(m)=ck;
+ c0=c1;
+ c1=ck;
+end%;
+
+y1=1.0;
+r=1.0;
+
+
+for k=1:100%;
+ r=0.5*r.*x.*x./(k*(2.0*k-1.0));
+ r1=c(k).*r;
+ y1=y1+r1;
+ if(abs(r1./y1)<= eps & k > 30)
+ break; end%;
+end%;
+
+d1=1.0;
+d2=a;
+d(1)=1.0;
+d(2)=a;
+
+for k2=5:2:160%;
+ m=fix((k2+1)/2);
+ dk=a*d2+0.25*(k2-2.0)*(k2-3.0)*d1;
+ d(m)=dk;
+ d1=d2;
+ d2=dk;
+end%;
+
+y2=1.0;
+r=1.0;
+
+for k=1:99% with 100 before it exceedes array elements;
+ r=0.5*r.*x.*x/(k.*(2.0.*k+1.0));
+ r1=d(k+1)*r;
+ y2=y2+r1;
+ if(abs(r1./y2)<= eps & k > 30)
+ break; end%;
+end%;
+
+y2=x.*y2;
+if a < 0&&(a+1/2)==fix(a+1/2)
+ ar=pi*(1/4+a/2);
+ f1=gamma(1/4-a/2)/(sqpi*2^(a/2+1/4));
+ f2=gamma(3/4-a/2)/(sqpi*2^(a/2-1/4));
+ u=cos(ar)*f1*y1-sin(ar)*f2*y2;
+else
+ sq2=sqrt(2.0);
+ p0=sqpi/2^(a/2+1/4);
+ g1=gamma(1/4+a/2);
+ g3=gamma(3/4+a/2);
+ u=p0*(y1/g3-sq2*y2/g1);
+end%;
diff --git a/tools/README.md b/tools/README.md
new file mode 100644
index 000000000..677b19836
--- /dev/null
+++ b/tools/README.md
@@ -0,0 +1,4 @@
+# Tools
+
+This folder contains a collection of helper functions that are not directly related to the source code of matRad.
+Most users will not have use for those, but if you have something useful add it here.
diff --git a/tools/matRad_fixExportedGUI.m b/tools/matRad_fixExportedGUI.m
new file mode 100644
index 000000000..363db3fea
--- /dev/null
+++ b/tools/matRad_fixExportedGUI.m
@@ -0,0 +1,155 @@
+function matRad_fixExportedGUI(guiFile,replaceOnly)
+% matRad function to postprocess figure files created from GUI. Removes
+% unnecessary and compatability problem causing code and extracts the
+% layout and main function storing them in separate files. Backups of the
+% figure & m files are created
+%
+% call
+% matRad_fixExportedGUI(guiFile,replaceOnly)
+%
+% input
+% guiFile: m-file exported from guide
+% replaceOnly: optional, default false. when set to true, only
+% unnecessary and incompatible code is removed and no
+% external files (i.e. layout / main function) or backups
+% are created
+%
+%
+% References
+% -
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2019 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if nargin < 2
+ replaceOnly = false;
+end
+
+[filepath, name, ext] = fileparts(which(guiFile));
+
+if ~replaceOnly
+ copyfile(guiFile,[filepath filesep name ext '.bak']);
+ copyfile([filepath filesep name '.fig'],[filepath filesep name '.fig.bak']);
+end
+
+fid = fopen(guiFile,'r');
+f=fread(fid,'*char')';
+fclose(fid);
+
+
+%fix strange newlines after string attributes
+f = regexprep(f,'(String'',''\w+?)\r+?('')','$1$2');
+f = regexprep(f,'''String'',\[\]?','''String'',''''');
+
+
+f = regexprep(f,'\r?\n''Alphamap'',\[[\s.;0-9]*?\](,\.\.\.|\))','');
+f = regexprep(f,'\r?\n''DecorationContainer'',\[[\s.;0-9]*?\](,\.\.\.|\))','');
+f = regexprep(f,'\r?\n''Layer'',''(back|middle|front)''(,\.\.\.|\))','');
+f = regexprep(f,'\r?\n''DisplayName'',blanks\(0\)(,\.\.\.|\))','');
+f = regexprep(f,'\r?\n''HelpTopicKey'',blanks\(0\)(,\.\.\.|\))','');
+f = regexprep(f,'\r?\n''DimensionNames'',{[\s\w'']*?}(,\.\.\.|\))','');
+f = regexprep(f,'\r?\n''Description'',''[\w\s]*?''(,\.\.\.|\))','');
+f = regexprep(f,'\r?\n''Tooltip'',''[\w\s]*?''(,\.\.\.|\))','');
+
+%uitable problems
+expr = {'Units','FontUnits','RearrangeableColumns','RowStriping','ForegroundColor','Enable','HandleVisibility','FontSize','FontName','FontAngle','FontWeight'};
+expr = strjoin(expr,'|');
+expr = ['\r?\n''(' expr ')'',get\(0,''defaultuitable(' expr ')''\)(,\.\.\.|\))'];
+f = regexprep(f,expr,'');
+
+%On/Off properties
+expr = {'FontSmoothing','CLimInclude','ALimInclude','IncludeRenderer','IsContainer','IsContainer','Serializable','TransformForPrintFcnImplicitInvoke'};
+expr = strjoin(expr,'|');
+expr = ['\r?\n''(' expr ')'',''(on|off)''(,\.\.\.|\))'];
+f = regexprep(f,expr,'');
+
+%Mode sets
+modestrings = {'ScreenPixelsPerInch','DecorationContainer','Color','FontSize','Layer','FontSmoothing','IsContainer','PickableParts','DimensionNames','Description','TransformForPrintFcnImplicitInvoke','CurrentAxes','CurrentObject','CurrentPoint','SelectionType'};
+modestrings = strjoin(modestrings,'|');
+expr = ['\r?\n''(' modestrings ')Mode'',''\w*?''(,\.\.\.|\))'];
+f = regexprep(f,expr,'');
+
+
+%Axes Mode gets
+modestrings = {'Colormap','Alphamap','Camera','DataSpace','ColorSpace','FontSize','DecorationContainer','ChildContainer','XRuler','YRuler','ZRuler','AmbientLightSource','ActivePositionProperty'};
+modestrings = strjoin(modestrings,'|');
+expr = ['\r?\n''(' modestrings ')Mode'',get\(0,''defaultaxes(' modestrings ')Mode''\)(,\.\.\.|\))'];
+f = regexprep(f,expr,'');
+
+%Figure Mode gets
+modestrings = {'PaperSize','PaperType','PaperUnits'};
+modestrings = strjoin(modestrings,'|');
+expr = ['\r?\n''(' modestrings ')Mode'',get\(0,''defaultfigure(' modestrings ')Mode''\)(,\.\.\.|\))'];
+f = regexprep(f,expr,'');
+
+%Tooltip property
+f = regexprep(f,'\r?\n''TooltipMode'',get\(0,''default(uipushtool|uitoggletool|uicontrol)TooltipMode''\)(,\.\.\.|\))','');
+
+%ui element mode gets
+modestrings = {'BackgroundColor','Value'};
+modestrings = strjoin(modestrings,'|');
+f = regexprep(f,['\r?\n''(' modestrings ')Mode'',get\(0,''default(uipushtool|uitoggletool|uicontrol)(' modestrings ')Mode''\)(,\.\.\.|\))'],'');
+
+%Fix remaining whitespaces
+f = regexprep(f,'(,\.\.\.)\s*?;',');');
+
+%Fix Titles
+f = regexprep(f,'''Title'',{(.*?)}','''Title'',strtrim(strjoin({$1}))');
+
+%Octave doesn't handle ishghandle
+f = regexprep(f,'ishghandle','isgraphics');
+
+%Octave doesn't know guidemfile
+f = regexprep(f,'guidemfile','%guidemfile');
+
+%rename the MainFcn
+f = regexprep(f,'gui_mainfcn',[name '_gui_mainFcn']);
+
+if ~replaceOnly
+ %now extract the layout and mainfcn, which are added to the end of the file
+ %by the export
+ expr = '(% --- Creates and returns a handle to the GUI figure\..*?)(% --- Handles default GUIDE GUI creation and callback dispatch.*)';
+ out = regexp(f,expr,'tokens');
+ layoutFcn = out{1}{1};
+ guiMainFcn = out{1}{2};
+ %remove the functions
+ f = regexprep(f,expr,'');
+ %write the functions to files
+ [~,~] = mkdir([filepath filesep 'gui']);
+ addpath([filepath filesep 'gui']);
+ fLayoutId = fopen([filepath filesep 'gui' filesep name '_LayoutFcn.m'],'w');
+ fprintf(fLayoutId,'%s',layoutFcn);
+ fclose(fLayoutId);
+ fGuiMainFcnId = fopen([filepath filesep 'gui' filesep name '_gui_mainFcn.m'],'w');
+ fprintf(fGuiMainFcnId,'%s',guiMainFcn);
+ fclose(fGuiMainFcnId);
+end
+
+fid = fopen(guiFile,'w');
+fprintf(fid,'%s',f);
+fclose(fid);
+
+if ~replaceOnly
+ try
+ mat = load([filepath filesep name '.mat']);
+ mat = mat.mat;
+ save([filepath filesep name '.mat'],'mat','-v7');
+ catch
+ fprintf('No .mat file was exported with GUI\n');
+ end
+
+ h1 = feval([name '_LayoutFcn'],'reuse');
+ savefig(h1,[filepath filesep name '.fig']);
+ close(h1);
+end
+end
\ No newline at end of file
diff --git a/unitTest/matRad_runTests.m b/unitTest/matRad_runTests.m
deleted file mode 100644
index c429f57cb..000000000
--- a/unitTest/matRad_runTests.m
+++ /dev/null
@@ -1,74 +0,0 @@
-%% This file runs the complete matRad test suite.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-% Copyright 2017 the matRad development team.
-%
-% This file is part of the matRad project. It is subject to the license
-% terms in the LICENSE file found in the top-level directory of this
-% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
-% of the matRad project, including this file, may be copied, modified,
-% propagated, or distributed except according to the terms contained in the
-% LICENSE file.
-%
-% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% Set path
-run(['..' filesep 'matRad_rc']);
-
-%% Prepare settings for testing
-matRad_cfg = MatRad_Config.instance();
-matRad_cfg.setDefaultPropertiesForTesting();
-
-% supressing the inherent Ocatave warnings for division by zero
-if strcmp(matRad_getEnvironment,'OCTAVE')
- warning('off','Octave:divide-by-zero');
-end
-
-exampleScripts = {'../examples/matRad_example1_phantom.m',...
- '../examples/matRad_example2_photons.m',...
- '../examples/matRad_example3_photonsDAO.m',...
- '../examples/matRad_example4_photonsMC.m',...
- '../examples/matRad_example5_protons.m',...
- '../examples/matRad_example6_protonsNoise.m',...
- '../examples/matRad_example7_carbon.m',...
- '../matRad.m'};
-
-testing_suffix = '_test';
-unitTestBixelWidth = 20;
-
-%Copy and manipulate all scripts
-[folders,names,exts] = cellfun(@fileparts,exampleScripts,'UniformOutput',false);
-testScriptNames = strcat(names,testing_suffix);
-testScripts = cellfun(@fullfile,folders,strcat(testScriptNames,exts),'UniformOutput',false);
-status = cellfun(@copyfile,exampleScripts,testScripts);
-
-matRad_unitTestTextManipulation(testScripts,'pln.propStf.bixelWidth',['pln.propStf.bixelWidth = ' num2str(unitTestBixelWidth)]);
-matRad_unitTestTextManipulation(testScripts,'display(','%%%%%%%%%%%%%%% REMOVED DISPLAY FOR TESTING %%%%%%%%%%%%%%');
-
-errors = {};
-%Running tests
-for testIx = 1:length(testScriptNames)
- fprintf('Running Integration Test for ''%s''\n',names{testIx});
- try
- run(testScripts{testIx});
- clear ct cst pln stf dij resultGUI; %Make sure the workspace is somewhat clean
- delete(testScripts{testIx}); %Delete after successful run
- catch ME
- [~,scriptName] = fileparts(testScripts{testIx});
- if matRad_cfg.isMatlab
- message = ME.getReport();
- else
- message = ME.message;
- end
- errMsg = sprintf('Experiencd an error during testing of %s. Error-Message:\n %s',scriptName,message);
- warning(errMsg);
- errors{end+1} = errMsg;
- end
-end
-
-%Check if at least one script failed and report error
-if ~isempty(errors)
- error(strjoin(errors,'\n\n============================\n\n'));
-end
-
\ No newline at end of file
diff --git a/userdata/README.md b/userdata/README.md
new file mode 100644
index 000000000..050bb827c
--- /dev/null
+++ b/userdata/README.md
@@ -0,0 +1,3 @@
+# Userdata folder
+
+This folder can be used to store user-importet patients (in the patients folder), Hounsfield-Unit look-up tables (hluts folder) or user-configured machines (machine folder). Files in this folder will be automatically added to the matRad search paths. Using `matRad_rc` with the argument `-addUserFolder pathToFolder` other folders can be added following the same naming scheme and subfolder scheme.
\ No newline at end of file
diff --git a/userdata/hluts/README.md b/userdata/hluts/README.md
new file mode 100644
index 000000000..ec320639c
--- /dev/null
+++ b/userdata/hluts/README.md
@@ -0,0 +1,4 @@
+# Folder for user-configured Hounsfield-Unit LookUp-Tables
+
+This folder can be used to store custom Hounsfield LookUp-Tables (HU -> rSP, HU -> rED). HLUTs in here will be added on to the search path once `matRad_rc` is run and subsequently recognized in matRad, but will by default be ignored within git.
+For the format of a user-defined ASCII *.hlut files check-out the default HLUTs stored in matRad/hluts and follow the naming scheme explained in the Wiki.
diff --git a/userdata/machines/README.md b/userdata/machines/README.md
new file mode 100644
index 000000000..e674ed02f
--- /dev/null
+++ b/userdata/machines/README.md
@@ -0,0 +1,6 @@
+# Folder for user-configured machines
+
+This folder can be used to store user-imported machines as *.mat files. Machines in here will be added on to the search path once `matRad_rc` is run and subsequently recognized in matRad including the GUI, but will by default be ignored within git.
+For the format of machine files check-out the Generic machines in the matRad/basedata subfolder or consult the Wiki.
+
+For maximum compatibility, we suggest to store manually imported mat-files in the v7 format when using Matlab's `save` function.
\ No newline at end of file
diff --git a/userdata/patients/README.md b/userdata/patients/README.md
new file mode 100644
index 000000000..fd9c53a48
--- /dev/null
+++ b/userdata/patients/README.md
@@ -0,0 +1,5 @@
+# Folder for user-imported patients
+
+This folder can be used to store user-imported patients (default import location for DICOM and binary imports). *.mat files containing matRad structures (ct,cst,stf, etc.) stored in here will be added on to the search path once `matRad_rc` is run, but will by default be ignored within git.
+
+For maximum compatibility, we suggest to store manually imported mat-files in the v7 format when using Matlab's `save` function.
\ No newline at end of file
diff --git a/userdata/scripts/README.md b/userdata/scripts/README.md
new file mode 100644
index 000000000..203c50387
--- /dev/null
+++ b/userdata/scripts/README.md
@@ -0,0 +1,3 @@
+# Folder for user scripts
+
+This folder can be used to store user-defined Matlab scripts. While it is not necessary to use this folder, it will be automatically on the matRad path and helps to not clutter the base of the userfolder.
\ No newline at end of file