diff --git a/.gitignore b/.gitignore index 0aa682893..ceae346ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ MCsquare/bin/Materials/ +topas/MCrun diff --git a/4D/matRad_calc4dDose.m b/4D/matRad_calc4dDose.m index 39bc2e8f8..1f783e492 100644 --- a/4D/matRad_calc4dDose.m +++ b/4D/matRad_calc4dDose.m @@ -84,12 +84,12 @@ resultGUI.accSqrtBetaDose = matRad_doseAcc(ct,resultGUI.phaseSqrtBetaDose, cst, 'DDM'); % only compute where we have biologically defined tissue - ix = dij.alphaX~=0; + ix = dij.ax(:,1)~=0; resultGUI.accEffect = resultGUI.accAlphaDose + resultGUI.accSqrtBetaDose.^2; resultGUI.accRBExD = zeros(ct.cubeDim); - resultGUI.accRBExD(ix) = ((sqrt(dij.alphaX(ix).^2 + 4 .* dij.betaX(ix) .* resultGUI.accEffect(ix)) - dij.alphaX(ix))./(2.*dij.betaX(ix))); + resultGUI.accRBExD(ix) = ((sqrt(dij.ax(ix).^2 + 4 .* dij.bx(ix) .* resultGUI.accEffect(ix)) - dij.ax(ix))./(2.*dij.bx(ix))); end diff --git a/4D/matRad_doseAcc.m b/4D/matRad_doseAcc.m index 17e5cd984..b1119a603 100644 --- a/4D/matRad_doseAcc.m +++ b/4D/matRad_doseAcc.m @@ -61,10 +61,10 @@ error('dose accumulation via direct dose mapping (DDM) requires pull dvfs'); end - [Y,X,Z] = meshgrid(yGridVec,xGridVec,zGridVec); + [X,Y,Z] = meshgrid(xGridVec,yGridVec,zGridVec); % TODODODODODOD %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - ix = [];1:prod(ct.cubeDim);%[]; + ix = []; for i = 1:size(cst,1) ix = unique([ix; cst{i,4}{1}]); end @@ -74,8 +74,8 @@ 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(yGridVec,xGridVec',zGridVec,phaseCubes{i}, ... + + d_ref = matRad_interp3(yGridVec,xGridVec,zGridVec,permute(phaseCubes{i},[2 1 3]), ... Y(ix) + dvf_y_i(ix), ... X(ix) + dvf_x_i(ix), ... Z(ix) + dvf_z_i(ix), ... diff --git a/ChangeLog.txt b/ChangeLog.txt index c0a3b841c..ae60edf09 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,37 @@ +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 diff --git a/MCsquare/MatRad_MCsquareConfig.m b/MCsquare/MatRad_MCsquareConfig.m deleted file mode 100644 index a43d819a8..000000000 --- a/MCsquare/MatRad_MCsquareConfig.m +++ /dev/null @@ -1,151 +0,0 @@ -classdef MatRad_MCsquareConfig -% MatRad_MCsquareConfig 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/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 - - %%% 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 - 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 = Num_Primaries) - % 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_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 - - %%% Physical parameters - Simulate_Nuclear_Interactions = true; % Enable/Disable the simulation of nuclear interactions. Default: True - Simulate_Secondary_Protons = true; % Enable/Disable the simulation of secondary protons (emitted during nuclear interactions). Default: True - Simulate_Secondary_Deuterons = true; % Enable/Disable the simulation of secondary deuterons (emitted during nuclear interactions). Default: True - Simulate_Secondary_Alphas = true; % Enable/Disable the simulation of secondary alphas (emitted during nuclear interactions). Default: True - - - %%% 4D simulation - fourD_Mode = false; % Enable/Disable the 4D simulation mode. Default: False - fourD_Dose_Accumulation = false; % Enable/Disable the dose accumulation for all 4D-CT phases. Default: False - Field_type = 'Velocity'; % Field type: Displacement or Velocity. Default: Velocity - Create_Ref_from_4DCT = false; % Create the reference phase image from 4D CT images (True), or import the reference image (False). Default: False - Create_4DCT_from_Ref = false; % Create 4D CT images by deforming the reference phase image (True), or import 4D CT images (False). Default: False - Dynamic_delivery = false; % Enable/Disable simulation of dynamic delivery (interplay simulation). Default: False - Breathing_period = 7.0; % Period (in seconds) of the breathing motion. Default: 7.0 - - - %%% Robustness simulation - Robustness_Mode = false; % Enable/Disable the robustness verification mode. Default: False - %Scenario_selection = 'All' % Method for scenario selection: All (simulate all combinations), Random (randomly sample scenarios). Default: All - Simulate_nominal_plan = true; % Simulate the nominal plan (without any systematic or random uncertainty). Default: True - %Systematic_Setup_Error = [0.25 0.25 0.25]; % Systematic error for the patient setup along the XYZ axes (cm). Default: 0.25 0.25 0.25 - %Random_Setup_Error = [0.1 0.1 0.1]; % Standard deviation of the patient random setup error along the XYZ axes (cm). Default: 0.1 0.1 0.1 - %Systematic_Range_Error = 3.0; % Systematic error in percent of the proton range (%). Default: 3.0 - %Systematic_Amplitude_Error = 5.0; % Systematic error in percent of the breathing motion amplitude for 4D simulations. Default: 5.0 - %Random_Amplitude_Error = 5.0; % Random error in percent of the breathing motion amplitude for 4D simulations. Default: 5.0 - %Systematic_Period_Error = 5.0; % Systematic error in percent of the breathing motion period for simulations of interplay with dynamic delivery. Default: 5.0 - %Random_Period_Error = 5.0; % Random error in percent of the breathing motion period for simulations of interplay with dynamic delivery. Default: 5.0 - - - %%% Beamlet simulation - Beamlet_Mode = false; % Enable/Disable the beamlet computation mode. Default: False - Beamlet_Parallelization = false; % Parallelization on beamlet level is sometimes faster for beamlet simulation. This requires more memory. Default: False - - - %%% Output parameters - Output_Directory = 'MCsquareOutput'; % Name of the output directory. Default: Outputs - - Energy_ASCII_Output = false; % Enable/Disable the output of Energy in ASCII format. Default: False - Energy_MHD_Output = false; % Enable/Disable the output of Energy in MHD format. Default: False - Energy_Sparse_Output = false; % Enable/Disable the output of Energy in Sparse matrix format. Default: False - Dose_ASCII_Output = false; % Enable/Disable the output of Dose in ASCII format. Default: False - Dose_MHD_Output = true; % Enable/Disable the output of Dose in MHD format. Default: True - Dose_Sparse_Output = true; % Enable/Disable the output of Dose in Sparse matrix format. Default: False - LET_ASCII_Output = false; % Enable/Disable the output of LET in ASCII format. Default: False - LET_MHD_Output = false; % Enable/Disable the output of LET in MHD format. Default: False - LET_Sparse_Output = false; % Enable/Disable the output of LET in Sparse matrix format. Default: False - - Densities_Output = false; % Enable/Disable the export of the density map (converted from the CT image). Default: False - Materials_Output = false; % Enable/Disable the export of the map of materials (converted from the CT image). Default: False - - Compute_DVH = false; % Enable/Disable the computation and export of DVH based on RT-Struct binary masks. Default: False - - Dose_Sparse_Threshold = 0; % The dose values above the threshold will be stored in the sparse matrix file. Default: 0 - Energy_Sparse_Threshold = 0; % The energy values above the threshold will be stored in the sparse matrix file. Default: 0 - LET_Sparse_Threshold = 0; % The LET values above the threshold will be stored in the sparse matrix file. Default: 0 - - Score_PromptGammas = false; % Enable/Disable the scoring of Prompt Gammas (emitted during nuclear interactions). Default: False - PG_LowEnergyCut = 0.0; % Disable the scoring of Prompt Gammas with energy below this value (MeV). Default: 0.0 - PG_HighEnergyCut = 50.0; % Disable the scoring of Prompt Gammas with energy above this value (MeV). Default: 50.0 - % Typical gamma camera would be sensitive between 3.0 and 6.0 MeV - PG_Spectrum_NumBin = 150; % Number of bins to score the Prompt Gamma energy spectrum. Default: 150 - PG_Spectrum_Binning = 0.1; % Bin width (MeV) for the scoring of Prompt Gamma spectrum. Default: 0.1 - - LET_Calculation_Method = 'StopPow'; % Select the method employed for the calculation of LET (DepositedEnergy, StopPow). Default: StopPow - - %Export_Beam_dose = 'Disabled' % Export dose distribution for each beam (Enable) or entire plan (Disable). Default: Disable - Dose_to_Water_conversion = 'Disabled'; % Select the method employed to convert simulation results (dose to medium) to dose to water (Disabled, PostProcessing, OnlineSPR). Default: Disabled - - Dose_Segmentation = false; % Enable/Disable a segmentation of the dose map based on a density thresholding (remove dose artifacts in the air). Default: False - Density_Threshold_for_Segmentation = 0.01; % Density threshold employed for the segmentation (in g/cm3). Default: 0.01 - end - - methods - 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') - fprintf(fid,[writeString ' ' num2str(obj.(MCsquareProperties{i})) '\n']); - elseif isa(obj.(MCsquareProperties{i}),'char') - fprintf(fid,[writeString ' ' obj.(MCsquareProperties{i}) '\n']); - else - error('export not defined'); - end - - end - - end - - end -end - diff --git a/MCsquare/bin/Scanners/default/HU_Density_Conversion.txt b/MCsquare/bin/Scanners/default/HU_Density_Conversion.txt index 23efa1ca0..a1a1fbd8a 100644 --- a/MCsquare/bin/Scanners/default/HU_Density_Conversion.txt +++ b/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/MCsquare/bin/Scanners/default/HU_Material_Conversion.txt index a7341586c..c65e9d85a 100644 --- a/MCsquare/bin/Scanners/default/HU_Material_Conversion.txt +++ b/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/MCsquare/MatRad_MCsquareBaseData.m b/MCsquare/matRad_MCsquareBaseData.m similarity index 85% rename from MCsquare/MatRad_MCsquareBaseData.m rename to MCsquare/matRad_MCsquareBaseData.m index 57820e874..15416c4e1 100644 --- a/MCsquare/MatRad_MCsquareBaseData.m +++ b/MCsquare/matRad_MCsquareBaseData.m @@ -1,5 +1,5 @@ -classdef MatRad_MCsquareBaseData < MatRad_MCemittanceBaseData - % MatRad_MCsquareBaseData class for calculating MCsquare base data and +classdef matRad_MCsquareBaseData < matRad_MCemittanceBaseData + % matRad_MCsquareBaseData class for calculating MCsquare base data and % writing it to a .txt file, for MCsquare to use % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -16,9 +16,13 @@ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% methods (Access = public) - function obj = MatRad_MCsquareBaseData(machine,stf) - %Call MatRad_MCemmitanceBaseData constructor - obj = obj@MatRad_MCemittanceBaseData(machine,stf); + 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) @@ -35,7 +39,7 @@ end %remove field not needed for MCsquare base data - selectedData = rmfield(selectedData, 'FWHMatIso'); +% selectedData = rmfield(selectedData, 'FWHMatIso'); %write MCsqaure data base file try @@ -77,9 +81,11 @@ end fprintf(fileID, '\n'); + indices = obj.selectedFocus(obj.energyIndex); for k = 1:size(selectedData,2) for m = 1:numel(fn) - fprintf(fileID, '%g', selectedData(k).(fn{m})); + tmp = selectedData(k).(fn{m}); + fprintf(fileID, '%g ', tmp(indices(k))); fprintf(fileID, '\t'); end fprintf(fileID, '\n'); diff --git a/MCsquare/matRad_MCsquareConfig.m b/MCsquare/matRad_MCsquareConfig.m new file mode 100644 index 000000000..cea250cd7 --- /dev/null +++ b/MCsquare/matRad_MCsquareConfig.m @@ -0,0 +1,440 @@ +classdef matRad_MCsquareConfig +% matRad_MCsquareConfig 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/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 + %%% Parameter for continuity + engine = 'MCsquare'; + + %%% 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 + + % This parameter can be overwritten through MatRad_Config default parameters + numHistories = 1e6; % Number of primary protons to simulate. Default: 1e7 + 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_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 + + %%% Physical parameters + Simulate_Nuclear_Interactions = true; % Enable/Disable the simulation of nuclear interactions. Default: True + Simulate_Secondary_Protons = true; % Enable/Disable the simulation of secondary protons (emitted during nuclear interactions). Default: True + Simulate_Secondary_Deuterons = true; % Enable/Disable the simulation of secondary deuterons (emitted during nuclear interactions). Default: True + Simulate_Secondary_Alphas = true; % Enable/Disable the simulation of secondary alphas (emitted during nuclear interactions). Default: True + + + %%% 4D simulation + fourD_Mode = false; % Enable/Disable the 4D simulation mode. Default: False + fourD_Dose_Accumulation = false; % Enable/Disable the dose accumulation for all 4D-CT phases. Default: False + Field_type = 'Velocity'; % Field type: Displacement or Velocity. Default: Velocity + Create_Ref_from_4DCT = false; % Create the reference phase image from 4D CT images (True), or import the reference image (False). Default: False + Create_4DCT_from_Ref = false; % Create 4D CT images by deforming the reference phase image (True), or import 4D CT images (False). Default: False + Dynamic_delivery = false; % Enable/Disable simulation of dynamic delivery (interplay simulation). Default: False + Breathing_period = 7.0; % Period (in seconds) of the breathing motion. Default: 7.0 + + + %%% Robustness simulation + Robustness_Mode = false; % Enable/Disable the robustness verification mode. Default: False + %Scenario_selection = 'All' % Method for scenario selection: All (simulate all combinations), Random (randomly sample scenarios). Default: All + Simulate_nominal_plan = true; % Simulate the nominal plan (without any systematic or random uncertainty). Default: True + %Systematic_Setup_Error = [0.25 0.25 0.25]; % Systematic error for the patient setup along the XYZ axes (cm). Default: 0.25 0.25 0.25 + %Random_Setup_Error = [0.1 0.1 0.1]; % Standard deviation of the patient random setup error along the XYZ axes (cm). Default: 0.1 0.1 0.1 + %Systematic_Range_Error = 3.0; % Systematic error in percent of the proton range (%). Default: 3.0 + %Systematic_Amplitude_Error = 5.0; % Systematic error in percent of the breathing motion amplitude for 4D simulations. Default: 5.0 + %Random_Amplitude_Error = 5.0; % Random error in percent of the breathing motion amplitude for 4D simulations. Default: 5.0 + %Systematic_Period_Error = 5.0; % Systematic error in percent of the breathing motion period for simulations of interplay with dynamic delivery. Default: 5.0 + %Random_Period_Error = 5.0; % Random error in percent of the breathing motion period for simulations of interplay with dynamic delivery. Default: 5.0 + + + %%% Beamlet simulation + Beamlet_Mode = false; % Enable/Disable the beamlet computation mode. Default: False + Beamlet_Parallelization = false; % Parallelization on beamlet level is sometimes faster for beamlet simulation. This requires more memory. Default: False + + + %%% Output parameters + Output_Directory = 'MCsquareOutput'; % Name of the output directory. Default: Outputs + + Energy_ASCII_Output = false; % Enable/Disable the output of Energy in ASCII format. Default: False + Energy_MHD_Output = false; % Enable/Disable the output of Energy in MHD format. Default: False + Energy_Sparse_Output = false; % Enable/Disable the output of Energy in Sparse matrix format. Default: False + Dose_ASCII_Output = false; % Enable/Disable the output of Dose in ASCII format. Default: False + Dose_MHD_Output = true; % Enable/Disable the output of Dose in MHD format. Default: True + Dose_Sparse_Output = true; % Enable/Disable the output of Dose in Sparse matrix format. Default: False + LET_ASCII_Output = false; % Enable/Disable the output of LET in ASCII format. Default: False + LET_MHD_Output = false; % Enable/Disable the output of LET in MHD format. Default: False + LET_Sparse_Output = false; % Enable/Disable the output of LET in Sparse matrix format. Default: False + + Densities_Output = false; % Enable/Disable the export of the density map (converted from the CT image). Default: False + Materials_Output = false; % Enable/Disable the export of the map of materials (converted from the CT image). Default: False + + Compute_DVH = false; % Enable/Disable the computation and export of DVH based on RT-Struct binary masks. Default: False + + Dose_Sparse_Threshold = 0; % The dose values above the threshold will be stored in the sparse matrix file. Default: 0 + Energy_Sparse_Threshold = 0; % The energy values above the threshold will be stored in the sparse matrix file. Default: 0 + LET_Sparse_Threshold = 0; % The LET values above the threshold will be stored in the sparse matrix file. Default: 0 + + Score_PromptGammas = false; % Enable/Disable the scoring of Prompt Gammas (emitted during nuclear interactions). Default: False + PG_LowEnergyCut = 0.0; % Disable the scoring of Prompt Gammas with energy below this value (MeV). Default: 0.0 + PG_HighEnergyCut = 50.0; % Disable the scoring of Prompt Gammas with energy above this value (MeV). Default: 50.0 + % Typical gamma camera would be sensitive between 3.0 and 6.0 MeV + PG_Spectrum_NumBin = 150; % Number of bins to score the Prompt Gamma energy spectrum. Default: 150 + PG_Spectrum_Binning = 0.1; % Bin width (MeV) for the scoring of Prompt Gamma spectrum. Default: 0.1 + + LET_Calculation_Method = 'StopPow'; % Select the method employed for the calculation of LET (DepositedEnergy, StopPow). Default: StopPow + + %Export_Beam_dose = 'Disabled' % Export dose distribution for each beam (Enable) or entire plan (Disable). Default: Disable + Dose_to_Water_conversion = 'Disabled'; % Select the method employed to convert simulation results (dose to medium) to dose to water (Disabled, PostProcessing, OnlineSPR). Default: Disabled + + Dose_Segmentation = false; % Enable/Disable a segmentation of the dose map based on a density thresholding (remove dose artifacts in the air). Default: False + Density_Threshold_for_Segmentation = 0.01; % Density threshold employed for the segmentation (in g/cm3). Default: 0.01 + end + + methods + function obj = matRad_MCsquareConfig() + %matRad_MCsquareConfig Configuration Class for MCsquare + matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class + + % Set default histories from MatRad_Config + if isfield(matRad_cfg.propMC,'defaultNumHistories') + obj.numHistories = matRad_cfg.propMC.defaultNumHistories; + end + end + + function writeMCsquareinputAllFiles(obj,filename,stf) + % generate input files for MCsquare dose calcualtion from matRad + % + % call + % obj.writeMCsquareinputAllFiles(filename,MCsquareConfig,stf) + % + % input + % filename: filename + % 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'); + obj.write(fileHandle); + fclose(fileHandle); + + %% prepare steering file writing + numOfFields = length(stf); + if obj.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.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.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.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.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 + 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 + + function writeMhd(obj,cube,resolution) + % 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(obj.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'); + filenameRaw = [obj.CT_File(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); + end + + function cube = readMhd(obj,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([obj.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.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 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)]; + elseif strncmp(MCsquareProperties{i},'numHistories',5) + writeString = 'Num_Primaries'; + 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') + fprintf(fid,[writeString ' ' num2str(obj.(MCsquareProperties{i})) '\n']); + elseif isa(obj.(MCsquareProperties{i}),'char') + fprintf(fid,[writeString ' ' obj.(MCsquareProperties{i}) '\n']); + else + error('export not defined'); + end + + end + + end + + end +end + 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 3560df225..000000000 --- a/MCsquare/matRad_writeMCsquareinputAllFiles.m +++ /dev/null @@ -1,172 +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']); - - %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 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']); - - %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 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 index f1f5aba64..991c0efa7 100644 --- a/MatRad_Config.m +++ b/MatRad_Config.m @@ -1,47 +1,47 @@ 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. -% -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - + % 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; - + defaults; - + %Disable GUI disableGUI = false; end - + properties (SetAccess = private) messageLog = {}; logFileHandle; - + %For storing the Environment & its version env; envVersion; @@ -49,18 +49,17 @@ isMatlab; %Helper bool to check for Matlab matRad_version; %MatRad version string 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(); - + obj.matRadRoot = fileparts(mfilename('fullpath')); addpath(genpath(obj.matRadRoot)); @@ -70,13 +69,13 @@ %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 @@ -85,7 +84,7 @@ 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 @@ -96,18 +95,18 @@ function displayToConsole(obj,type,formatSpec,varargin) % 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 @@ -132,38 +131,39 @@ function displayToConsole(obj,type,formatSpec,varargin) %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 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.doseGrid.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] @@ -172,43 +172,52 @@ function setDefaultProperties(obj) obj.propDoseCalc.defaultIgnoreOutsideDensities = true; %Ignore densities outside of cst contours obj.propDoseCalc.defaultVoxelSubIx = []; %Allows specification of a subindex list for dose calculation, empty by default means automatic setting obj.propDoseCalc.defaultUseCustomPrimaryPhotonFluence = false; %Use a custom primary photon fluence - obj.propDoseCalc.defaultCalcLET = false; %calculate LETs for particles - - % default properties for fine sampling calculation - obj.propDoseCalc.defaultFineSamplingProperties.sigmaSub = 1; - obj.propDoseCalc.defaultFineSamplingProperties.N = 21; - obj.propDoseCalc.defaultFineSamplingProperties.method = 'russo'; - + obj.propDoseCalc.defaultCalcLET = true; %calculate LETs for particles + + obj.propDoseCalc.defaultAirOffsetCorrection = true; + obj.propOpt.defaultMaxIter = 500; - + obj.propOpt.defaultRunDAO = 0; + obj.propOpt.defaultRunSequencing = 0; + obj.propMC.ompMC_defaultHistories = 1e6; - obj.propMC.ompMC_defaultOutputVariance = false; - - obj.propMC.direct_defaultHistories = 1e7; - obj.propMC.particles_defaultHistories = 2e4; + obj.propMC.ompMC_defaultOutputVariance = false; + + % Set default histories for MonteCarlo here if necessary + % obj.propMC.defaultNumHistories = 100; + + obj.propMC.default_photon_engine = 'matRad_OmpConfig'; + % obj.propMC.default_photon_engine = 'matRad_TopasConfig'; + obj.propMC.default_proton_engine = 'matRad_MCsquareConfig'; + obj.propMC.default_carbon_engine = 'matRad_TopasConfig'; - %obj.propMC.default_photon_engine = 'ompMC'; - obj.propMC.default_proton_engine = 'MCsquare'; % Default settings for TOPAS - obj.propMC.topas_defaultNumBatches = 5; - obj.propMC.topas_materialConversion = 'RSP'; - obj.propMC.topas_rsp_basematerial = 'G4_WATER'; - + obj.propMC.default_beamProfile_particles = 'biGaussian'; + obj.propMC.default_beamProfile_photons = 'uniform'; + obj.propMC.defaultExternalCalculation = false; + obj.propMC.defaultCalcDij = false; + + % default properties for fine sampling calculation + obj.propDoseCalc.fineSampling.defaultSigmaSub = 1; + obj.propDoseCalc.fineSampling.defaultN = 21; + obj.propDoseCalc.fineSampling.defaultMethod = 'russo'; + obj.propDoseCalc.fineSampling.defaultCalcMode = 'standard'; + obj.disableGUI = false; - + obj.defaults.samplingScenarios = 25; 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.logLevel = 3; %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; @@ -218,42 +227,40 @@ function setDefaultPropertiesForTesting(obj) obj.propDoseCalc.defaultIgnoreOutsideDensities = true; obj.propDoseCalc.defaultVoxelSubIx = []; %Allows specification of a subindex list for dose calculation, empty by default means automatic setting obj.propDoseCalc.defaultUseCustomPrimaryPhotonFluence = false; %Use a custom primary photon fluence - obj.propDoseCalc.defaultCalcLET = false; %calculate LET for particles - + obj.propDoseCalc.defaultCalcLET = true; %calculate LET for particles + % default properties for fine sampling calculation - obj.propDoseCalc.defaultFineSamplingProperties.sigmaSub = 2; - obj.propDoseCalc.defaultFineSamplingProperties.N = 5; - obj.propDoseCalc.defaultFineSamplingProperties.method = 'russo'; - + obj.propDoseCalc.fineSamplingProperties.sigmaSub = 2; + obj.propDoseCalc.fineSamplingProperties.N = 5; + obj.propDoseCalc.fineSamplingProperties.method = 'russo'; + obj.propOpt.defaultMaxIter = 10; - + obj.propMC.ompMC_defaultHistories = 100; obj.propMC.ompMC_defaultOutputVariance = true; - - obj.propMC.particles_defaultHistories = 100; - obj.propMC.direct_defaultHistories = 100; - %obj.propMC.default_photon_engine = 'ompMC'; - obj.propMC.default_proton_engine = 'MCsquare'; - % Default settings for TOPAS - obj.propMC.topas_defaultNumBatches = 5; - obj.propMC.topas_materialConversion = 'RSP'; - obj.propMC.topas_rsp_basematerial = 'G4_WATER'; - + % Set default histories for MonteCarlo + obj.propMC.defaultNumHistories = 100; + + obj.propMC.default_photon_engine = 'matRad_OmpConfig'; + % obj.propMC.default_photon_engine = 'matRad_TopasConfig'; + obj.propMC.default_proton_engine = 'matRad_MCsquareConfig'; + obj.propMC.default_carbon_engine = 'matRad_TopasConfig'; + obj.defaults.samplingScenarios = 2; - + 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 @@ -261,7 +268,7 @@ function dispInfo(obj,formatSpec,varargin) % 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 @@ -270,7 +277,7 @@ function dispError(obj,formatSpec,varargin) % 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 @@ -279,22 +286,22 @@ function dispWarning(obj,formatSpec,varargin) % 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; @@ -305,7 +312,7 @@ function dispDeprecationWarning(obj,formatSpec,varargin) 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']; @@ -316,14 +323,14 @@ function dispDeprecationWarning(obj,formatSpec,varargin) 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; @@ -331,8 +338,161 @@ function getEnvironment(obj) 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) = struct(); + end + + fnames = fieldnames(obj.(currField)); + for f = 1:length(fnames) + if ~isempty(strfind(fnames{f},'default')) + cutName = [lower(fnames{f}(8)) fnames{f}(9:end)]; + if ~isfield(pln.(currField),cutName) + pln.(currField).(cutName) = obj.(currField).(fnames{f}); + end + else + if ~isfield(pln.(currField),fnames{f}) + pln.(currField).(fnames{f}) = struct(); + end + subfields = fieldnames(obj.(currField).(fnames{f})); + for s = 1:length(subfields) + if ~isempty(strfind(subfields{s},'default')) + if length(subfields{s})==8 + cutName = [subfields{s}(8)]; + else + cutName = [lower(subfields{s}(8)) subfields{s}(9:end)]; + end + if ~isfield(pln.(currField).(fnames{f}),cutName) + pln.(currField).(fnames{f}).(cutName) = obj.(currField).(fnames{f}).(subfields{s}); + end + end + end + end + end + end + end + end + + function pln = getDefaultClass(obj,pln,propName,configName) + % load config from pln or from class + if (isfield(pln,propName) && isstruct(pln.(propName)) && nargin < 4) || (~isfield(pln,propName) && nargin < 4)%if there is no config found + switch propName + case 'propMC' + if isfield(pln,'propMC') && strcmp(propName,'propMC') && isfield(pln.propMC,'engine') + switch pln.propMC.engine + case 'ompMC' + configName = 'matRad_OmpConfig'; + case 'TOPAS' + configName = 'matRad_TopasConfig'; + case 'MCsquare' + configName = 'matRad_MCsquareConfig'; + end + pln.propMC = rmfield(pln.propMC,'engine'); + else + if isfield(pln,'radiationMode') && ~isempty(pln.radiationMode) + switch pln.radiationMode + case 'photons' + configName = obj.propMC.default_photon_engine; + case 'protons' + configName = obj.propMC.default_proton_engine; + otherwise + configName = obj.propMC.default_carbon_engine; + end + end + end + otherwise + obj.dispError('Config for ''%s'' not implemented',configName); + end + elseif nargin == 4 + + elseif nargin < 4 && ~isstruct(pln.(propName)) + % get config name from input field + configName = class(pln.(propName)); + else + obj.dispError('Error in default clasee'); end + + if ~isfield(pln,propName) + pln.(propName) = struct(); + end + %Overwrite parameters + %mc = metaclass(topasConfig); %get metaclass information to check if we can overwrite properties + if isstruct(pln.(propName)) + % Load configs + switch configName + case 'matRad_OmpConfig' + config = matRad_OmpConfig(); + pln.propMC.engine = 'ompMC'; + case 'matRad_TopasConfig' + config = matRad_TopasConfig(); + pln.propMC.engine = 'TOPAS'; + case 'matRad_MCsquareConfig' + config = matRad_MCsquareConfig(); + pln.propMC.engine = 'MCsquare'; + case 'matRad_HeterogeneityConfig' + config = matRad_HeterogeneityConfig; + end + + props = fieldnames(pln.(propName)); + for fIx = 1:numel(props) + fName = props{fIx}; + if isprop(config,fName) + if isstruct(pln.(propName).(fName)) + SubProps = fieldnames(pln.(propName).(fName)); + for SubfIx = 1:numel(SubProps) + subfName = SubProps{SubfIx}; + if isfield(config.(fName),subfName) + %We use a try catch block to catch errors when trying + %to overwrite protected/private properties instead of a + %metaclass approach + try + config.(fName).(subfName) = pln.(propName).(fName).(subfName); + catch + obj.dispWarning(['Property ''%s'' for ' configName ' will be omitted due to protected/private access or invalid value.'],fName); + end + else + obj.dispWarning(['Unkown property ''%s'' for ' configName ' will be omitted.'],fName); + end + end + else + %We use a try catch block to catch errors when trying + %to overwrite protected/private properties instead of a + %metaclass approach + try + config.(fName) = pln.(propName).(fName); + catch + obj.dispWarning(['Property ''%s'' for ' class(config) ' will be omitted due to protected/private access or invalid value.'],fName); + end + end + else + obj.dispWarning(['Unkown property ''%s'' for ' class(config) ' will be omitted.'],fName); + end + end + + % Write config to pln + pln.(propName) = config; + end + + % Send info to console + obj.dispInfo(['Class ' class(pln.(propName)) ' has been loaded to pln.' propName '!\n']); + end function configureEnvironment(obj) @@ -342,15 +502,15 @@ function configureEnvironment(obj) 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; @@ -358,21 +518,21 @@ function configureEnvironment(obj) 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 - + % 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 + % 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) + 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})) + 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}); @@ -380,14 +540,14 @@ function configureEnvironment(obj) 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 + % 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(); + obj = MatRad_Config(); % Use a metaclass object to get the properties because % Octave <= 5.2 doesn't have a properties function props = {metaclass(obj).PropertyList.Name}; @@ -420,10 +580,10 @@ function configureEnvironment(obj) end else obj = sobj; - end + end end - - + + end end diff --git a/MatRad_MCemittanceBaseData.m b/MatRad_MCemittanceBaseData.m deleted file mode 100644 index 6c3864cd6..000000000 --- a/MatRad_MCemittanceBaseData.m +++ /dev/null @@ -1,328 +0,0 @@ -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/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 - 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 - FWHMatIso %array containing FWHM values at iscenter for every energy - - rangeShifters %Stores range shifters - 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 - obj.stfCompressed = false; - else - obj.stfCompressed = true; - obj = obj.getRangeShiftersFromStf(stf); - end - - 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 - matRad_cfg.dispWarning('No information on BAMS to isocenter distance. Using generic value of 500mm'); - obj.nozzleToIso = 500; - end - - SAD = machine.meta.SAD; - - obj.smx = SAD; - obj.smy = SAD; - - 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 ii = 1:numel(obj.energyIndex) - - i = obj.energyIndex(ii); - - %look up whether MonteCarlo data are already present in - %machine file , if so do not recalculate - if isfield(machine.data(i),'monteCarloData') - if (isempty(machine.data(i).monteCarloData) == 0) - obj.monteCarloData = [obj.monteCarloData, machine.data(i).monteCarloData]; - count = count + 1; - continue; - end - end - - - %calculate monteCarloData for given energy and every focus - %index - data = []; - energyData = obj.fitPhaseSpaceForEnergy(i); - obj.FWHMatIso = []; - for j = 1:size(machine.data(i).initFocus.sigma,1) - - tmp = energyData; - opticsData = obj.fitBeamOpticsForEnergy(i, j); - - f = fieldnames(opticsData); - for a = 1:length(f) - tmp.(f{a}) = opticsData.(f{a}); - end - - data = [data; tmp]; - 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 - 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); - - i = energyIx; - - mcDataEnergy.NominalEnergy = obj.machine.data(i).energy; - - newDepths = linspace(0,obj.machine.data(i).depths(end),numel(obj.machine.data(i).depths) * 100); - newDepths = newDepths; - 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 - - %calcualte mean energy used my mcSquare with a formula fitted - %to TOPAS data - meanEnergy = @(x) 5.762374661332111e-20 * x^9 - 9.645413625310569e-17 * x^8 + 7.073049219034644e-14 * x^7 ... - - 2.992344292008054e-11 * x^6 + 8.104111934547256e-09 * x^5 - 1.477860913846939e-06 * x^4 ... - + 1.873625800704108e-04 * x^3 - 1.739424343114980e-02 * x^2 + 1.743224692623838e+00 * x ... - + 1.827112816899668e+01; - mcDataEnergy.MeanEnergy = meanEnergy(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. - totalSigmaSq = ((w50) / 6.14)^2; - - totalSpreadSq = @(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; - - % use formula deducted from Bragg Kleeman rule to calcuate - % energy straggling given the total sigma and the range - % straggling - energySpread = (totalSigmaSq - totalSpreadSq(r80)) / (0.022^2 * 1.77^2 * mcDataEnergy.MeanEnergy^(2*1.77-2)); - energySpread(energySpread < 0) = 0; - mcDataEnergy.EnergySpread = sqrt(energySpread); - 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,:); - sigmaSq = sigma.^2; - - %fit Courant-Synder equation to data using ipopt, formulae - %given in mcSquare documentation - sigmaNull = sqrt(interp1(z,sigmaSq,0)); - - qRes = @(rho, sigmaT) (sigmaSq - (sigmaNull^2 - 2*sigmaNull*rho*sigmaT.*z + sigmaT^2.*z.^2)); - - funcs.objective = @(x) sum(qRes(x(1), x(2)).^2); - funcs.gradient = @(x) [ 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaNull * x(2) * z)); - 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaNull * 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'; - options.ipopt.print_level = 1; - - start = [0.9; 0.1]; - [result, ~] = ipopt (start, funcs, options); - rho = result(1); - sigmaT = result(2); - - %calculate divergence, spotsize and correlation at nozzle - DivergenceAtNozzle = sigmaT; - SpotsizeAtNozzle = sqrt(sigmaNull^2 - 2 * rho * sigmaNull * sigmaT * obj.nozzleToIso + sigmaT^2 * obj.nozzleToIso^2); - CorrelationAtNozzle = (rho * sigmaNull - 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 = sigmaNull^2 - 2*rho*sigmaNull*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 * sigmaNull; - 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 = energyIndex' - - obj.machine.data(i).monteCarloData = obj.monteCarloData(:,count); - - count = count + 1; - end - machine = obj.machine; - save(strcat('../../', machineName, '.mat'),'machine'); - 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 -end - diff --git a/dicom/matRad_importDicomRTPlan.m b/dicom/matRad_importDicomRTPlan.m index b906826c0..5ed21408c 100644 --- a/dicom/matRad_importDicomRTPlan.m +++ b/dicom/matRad_importDicomRTPlan.m @@ -136,7 +136,7 @@ pln.machine = BeamSequence.Item_1.TreatmentMachineName; % set bio model parameters (default physical opt, no bio model) -pln.bioParam = matRad_bioModel(pln.radiationMode,'physicalDose','none'); +pln.bioParam = matRad_BioModel(pln.radiationMode,'physicalDose','none'); % set properties for steering pln.propStf.isoCenter = isoCenter; diff --git a/examples/matRad_example10_4DphotonRobust.m b/examples/matRad_example10_4DphotonRobust.m index 13211ab22..7556ca670 100644 --- a/examples/matRad_example10_4DphotonRobust.m +++ b/examples/matRad_example10_4DphotonRobust.m @@ -194,7 +194,7 @@ pln.propDoseCalc.doseGrid.resolution.z = 5; % [mm] % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt,modelName); % retrieve 9 worst case scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'nomScen'); diff --git a/examples/matRad_example11_helium.m b/examples/matRad_example11_helium.m index e28ded925..98c3d107a 100644 --- a/examples/matRad_example11_helium.m +++ b/examples/matRad_example11_helium.m @@ -66,7 +66,7 @@ % 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); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt, modelName); % dose calculation settings pln.propDoseCalc.doseGrid.resolution.x = 5; % [mm] diff --git a/examples/matRad_example12_simpleParticleMonteCarlo.m b/examples/matRad_example12_simpleParticleMonteCarlo.m index 06757215f..3da9af897 100644 --- a/examples/matRad_example12_simpleParticleMonteCarlo.m +++ b/examples/matRad_example12_simpleParticleMonteCarlo.m @@ -50,7 +50,7 @@ %Biology modelName = 'none'; quantityOpt = 'physicalDose'; -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt,modelName); % optimization settings pln.propOpt.optimizer = 'IPOPT'; @@ -61,20 +61,27 @@ % retrieve scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'nomScen'); % optimize on the nominal scenario - -pln.propMC.proton_engine = 'MCsquare'; -%pln.propMC.proton_engine = 'TOPAS'; + +% select Monte Carlo engine ('MCsquare' very fast for physical protons, 'TOPAS' slow but versatile for everything else) +pln.propMC.engine = 'MCsquare'; +% pln.propMC.engine = 'TOPAS'; + +% set number of histories lower than default for this example (default: 1e8) +pln.propMC.numHistories = 1e5; %Enable/Disable use of range shifter (has effect only when we need to fill %up the low-range region) pln.propStf.useRangeShifter = 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); %stf = matRad_generateSingleBixelStf(ct,cst,pln); %Example to create a single beamlet stf -%% dose calculation +%% analytical dose calculation dij = matRad_calcParticleDose(ct, stf, pln, cst); %Calculate particle dose influence matrix (dij) with analytical algorithm %dij = matRad_calcParticleDoseMC(ct,stf,pln,cst,1e4); %Calculate particle dose influence matrix (dij) with MC algorithm (slow!!) @@ -82,10 +89,11 @@ resultGUI = matRad_fluenceOptimization(dij,cst,pln); %Optimize %resultGUI = matRad_calcCubes(ones(dij.totalNumOfBixels,1),dij); %Use uniform weights +%% Monte Carlo dose calculation %resultGUI = matRad_calcDoseDirect(ct,stf,pln,cst,resultGUI.w); -resultGUI_MC = matRad_calcDoseDirectMC(ct,stf,pln,cst,resultGUI.w,1e5); +resultGUI_MC = matRad_calcDoseDirectMC(ct,stf,pln,cst,resultGUI.w); -%% Compare Dose +%% Compare Dose (number of histories not sufficient for accurate representation) resultGUI = matRad_appendResultGUI(resultGUI,resultGUI_MC,0,'MC'); matRad_compareDose(resultGUI.physicalDose, resultGUI.physicalDose_MC, ct, cst, [1, 1, 0] , 'off', pln, [2, 2], 1, 'global'); diff --git a/examples/matRad_example13_fitAnalyticalParticleBaseData.m b/examples/matRad_example13_fitAnalyticalParticleBaseData.m index 349ab1ffc..ee50c8600 100644 --- a/examples/matRad_example13_fitAnalyticalParticleBaseData.m +++ b/examples/matRad_example13_fitAnalyticalParticleBaseData.m @@ -1,7 +1,20 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%% Expample script for baseData fitting to mcSquare simulation %%%%%%% +%% 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/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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 1) CT & CST creation +clear matRad_rc ixOAR = 1; @@ -45,6 +58,8 @@ zDim = 250; cubeDim = [xDim yDim zDim]; +ct.cubeDim = cubeDim; + ct.cube{1} = ones(cubeDim) * 1; ct.cube{1}(1,1,1) = 0; @@ -52,8 +67,6 @@ ct.resolution.y = 0.32; ct.resolution.z = 0.32; -ct.cubeDim = cubeDim; - ct.numOfCtScen = 1; @@ -67,38 +80,55 @@ ct.hlut = [1,0;0,-1024]; % create body of full phantom size -mask = ones(ct.cubeDim); -cst{1,4}{1} = find(mask == 1); +body = ones(ct.cubeDim); +cst{1,4}{1} = find(body == 1); -%create target +% create target centerP_corr = iso; height_corr = 10; width_corr = 10; depth_corr = 10; - -mask = zeros(ct.cubeDim); - +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 - mask(centerP_corr(1)+i,centerP_corr(2)+j,centerP_corr(3)+k) = 1; + target(centerP_corr(1)+i,centerP_corr(2)+j,centerP_corr(3)+k) = 1; end end end -cst{2,4}{1} = find(mask == 1); +cst{2,4}{1} = find(target == 1); disp('CT creation done!'); -clearvars -except ct cst +clearvars -except ct cst matRad_cfg - %% 2) MCsquare computation and baseData fitting +%% 2) MCsquare computation and baseData fitting % meta information for treatment plan -pln.radiationMode = 'protons'; % either photons / protons / carbon -pln.machine = 'matRadBDLold'; +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.matRadRoot,'basedata',[fileName '.mat']); + +matRad_cfg.dispInfo('Saving temporary machine %s to %s\n',fileName,filePath); +save(filePath,'machine','-v7'); +clear machine; -pln.numOfFractions = 30; % beam geometry settings pln.propStf.bixelWidth = 50; % [mm] / also corresponds to lateral spot spacing for particles @@ -124,26 +154,45 @@ 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_generateStf(ct,cst,pln); - -%read MC phase space data -dataMC = readtable('BDL_matrad.txt'); -dataMC = dataMC{11:end,:}; -tmp = []; -for i = 1:size(dataMC,1) - tmp = [tmp; strsplit(dataMC{i})]; -end -energyMC = str2double(tmp(:,1)); -spotMC = (str2double(tmp(:,6)) + str2double(tmp(:,9))) / 2; -divMC = (str2double(tmp(:,7)) + str2double(tmp(:,10))) / 2; -corMC = (str2double(tmp(:,8)) + str2double(tmp(:,11))) / 2; - +stf = matRad_generateStfSinglePencilBeam(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 = 88; +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) @@ -159,28 +208,19 @@ 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 + % 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,ones(sum(stf(:).totalNumOfBixels),1),1000000); + resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,1); machine.data(count) = matRad_fitBaseData(resultGUI.physicalDose, ct.resolution, currentEnergy, mcData); - - disp(['baseData Progress :', ' ', num2str(round(count/nEnergy*100)), '%']); + + matRad_plotParticleBaseDataEntry(machine,count,hf); count = count + 1; end -% save data in machine -machine.meta.radiationMode = 'protons'; -machine.meta.dataType = 'singleGauss'; -machine.meta.created_on = date; -machine.meta.created_by = 'Paul Anton Meder'; -machine.meta.SAD = (2218 + 1839) / 2; -machine.meta.BAMStoIsoDist = 420.0; -machine.meta.machine = 'Generic'; -machine.meta.LUT_bxWidthminFWHM = [1, Inf; 8 ,8]; -machine.meta.fitAirOffset = 420.0; - -% save machine -save('protons_fitMachine.mat', 'machine'); \ No newline at end of file +%% 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 index 9211f0022..1ea8f34d3 100644 --- a/examples/matRad_example14_spotRemoval.m +++ b/examples/matRad_example14_spotRemoval.m @@ -62,7 +62,7 @@ % 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); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt, modelName); % retrieve scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'nomScen'); diff --git a/examples/matRad_example1_phantom.m b/examples/matRad_example1_phantom.m index f0bbfc35f..94d44a1f5 100644 --- a/examples/matRad_example1_phantom.m +++ b/examples/matRad_example1_phantom.m @@ -214,7 +214,7 @@ pln.propOpt.runSequencing = 0; % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt,modelName); % retrieve nominal scenario for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'nomScen'); diff --git a/examples/matRad_example2_photons.m b/examples/matRad_example2_photons.m index 77ba87929..e1bdbbe7b 100644 --- a/examples/matRad_example2_photons.m +++ b/examples/matRad_example2_photons.m @@ -137,7 +137,7 @@ pln.propOpt.runDAO = 0; % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt, modelName); % retrieve scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'nomScen'); diff --git a/examples/matRad_example3_photonsDAO.m b/examples/matRad_example3_photonsDAO.m index ba412af9c..b543d77c8 100644 --- a/examples/matRad_example3_photonsDAO.m +++ b/examples/matRad_example3_photonsDAO.m @@ -49,7 +49,7 @@ % 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); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt, modelName); % retrieve scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'nomScen'); diff --git a/examples/matRad_example4_photonsMC.m b/examples/matRad_example4_photonsMC.m index bf0273f6b..9d4d1c5ba 100644 --- a/examples/matRad_example4_photonsMC.m +++ b/examples/matRad_example4_photonsMC.m @@ -47,7 +47,7 @@ modelName = 'none'; % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt, modelName); % retrieve scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'nomScen'); diff --git a/examples/matRad_example5_protons.m b/examples/matRad_example5_protons.m index d3c167893..09ac9b119 100644 --- a/examples/matRad_example5_protons.m +++ b/examples/matRad_example5_protons.m @@ -70,7 +70,7 @@ % 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); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt, modelName); % retrieve scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'nomScen'); diff --git a/examples/matRad_example6_protonsNoise.m b/examples/matRad_example6_protonsNoise.m index 94d0a2e57..329b31e57 100644 --- a/examples/matRad_example6_protonsNoise.m +++ b/examples/matRad_example6_protonsNoise.m @@ -63,7 +63,7 @@ quantityOpt = 'RBExD'; % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); +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 diff --git a/examples/matRad_example7_carbon.m b/examples/matRad_example7_carbon.m index e65ce0ba5..03c6d3fe7 100644 --- a/examples/matRad_example7_carbon.m +++ b/examples/matRad_example7_carbon.m @@ -70,7 +70,7 @@ pln.propOpt.runSequencing = 0; % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); +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 @@ -125,7 +125,7 @@ % biological effect instead of the RBE-weighted dose. Therefore we have to % change the optimization mode and restart the optimization quantityOpt = 'effect'; -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt,modelName); resultGUI_effect = matRad_fluenceOptimization(dij,cst,pln); diff --git a/examples/matRad_example8_protonsRobust.m b/examples/matRad_example8_protonsRobust.m index fcea816f3..f89e2ad2f 100644 --- a/examples/matRad_example8_protonsRobust.m +++ b/examples/matRad_example8_protonsRobust.m @@ -170,7 +170,7 @@ pln.propOpt.runSequencing = 0; % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt,modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt,modelName); % retrieve 9 worst case scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,'wcScen'); diff --git a/examples/matRad_example9_4DDoseCalcMinimal.m b/examples/matRad_example9_4DDoseCalcMinimal.m index 816ea1cbf..f0aa0e855 100644 --- a/examples/matRad_example9_4DDoseCalcMinimal.m +++ b/examples/matRad_example9_4DDoseCalcMinimal.m @@ -56,7 +56,7 @@ scenGenType = 'nomScen'; % scenario creation type 'nomScen' 'wcScen' 'impScen' 'rndScen' % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt, modelName); % retrieve scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,scenGenType); diff --git a/matRad.m b/matRad.m index e60170e95..54b9d3ba9 100644 --- a/matRad.m +++ b/matRad.m @@ -51,7 +51,7 @@ scenGenType = 'nomScen'; % scenario creation type 'nomScen' 'wcScen' 'impScen' 'rndScen' % retrieve bio model parameters -pln.bioParam = matRad_bioModel(pln.radiationMode,quantityOpt, modelName); +pln.bioParam = matRad_BioModel(pln.radiationMode,quantityOpt, modelName); % retrieve scenarios for dose calculation and optimziation pln.multScen = matRad_multScen(ct,scenGenType); diff --git a/matRadGUI.m b/matRadGUI.m index f76b2c86d..f2ad95db0 100644 --- a/matRadGUI.m +++ b/matRadGUI.m @@ -2180,7 +2180,7 @@ function getPlnFromGUI(handles) cellBioModel = get(handles.popMenuBioOpt,'String'); cellQuantOpt = get(handles.popMenuQuantOpt,'String'); -pln.bioParam = matRad_bioModel(pln.radiationMode, cellQuantOpt{get(handles.popMenuQuantOpt,'Value'),1}, cellBioModel{get(handles.popMenuBioOpt,'Value'),1}); +pln.bioParam = matRad_BioModel(pln.radiationMode, cellQuantOpt{get(handles.popMenuQuantOpt,'Value'),1}, cellBioModel{get(handles.popMenuBioOpt,'Value'),1}); pln.bioOptimization = pln.bioParam.identifier; scenGenTypes = get(handles.popupmenuScenGen,'String'); @@ -3745,7 +3745,7 @@ function popMenuBioOpt_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end -bioParam = matRad_bioModel('photons','physicalDose','none'); +bioParam = matRad_BioModel('photons','physicalDose','none'); set(hObject,'String',bioParam.AvailableModels); @@ -3756,7 +3756,7 @@ function popMenuQuantOpt_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end -bioParam = matRad_bioModel('photons','physicalDose','none'); +bioParam = matRad_BioModel('photons','physicalDose','none'); set(hObject,'String',bioParam.AvailableQuantitiesForOpt); % --- Executes on selection change in popMenuQuantOpt. diff --git a/matRad_bioModel.m b/matRad_BioModel.m similarity index 95% rename from matRad_bioModel.m rename to matRad_BioModel.m index 5aa70bdd6..9613f86ce 100644 --- a/matRad_bioModel.m +++ b/matRad_BioModel.m @@ -1,13 +1,13 @@ -classdef matRad_bioModel +classdef matRad_BioModel % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - % matRad_bioModel + % matRad_BioModel % 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_bioModel(sRadiationMode,sQuantityOpt, sModel) + % pln.bioParam = matRad_BioModel(sRadiationMode,sQuantityOpt, sModel) % - % e.g. pln.bioParam = matRad_bioModel('protons','constRBE_RBExD') + % e.g. pln.bioParam = matRad_BioModel('protons','constRBE_RBExD') % % input % sRadiationMode: radiation modality 'photons' 'protons' 'carbon' @@ -163,12 +163,12 @@ end otherwise - matRad_cfg.dispWarning('matRad: Invalid biological optimization quantity: %s; using physical dose instead. \n',this.quantityOpt); + 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. \n',this.model); + matRad_cfg.dispWarning('matRad: Invalid biological model: %s; using "none" instead.',this.model); this.model = 'none'; this.quantityVis = 'physicalDose'; this.quantityOpt = 'physicalDose'; @@ -184,7 +184,7 @@ this.bioOpt = false; this.quantityVis = 'physicalDose'; else - matRad_cfg.dispWarning('matRad: Invalid biological model: %s; using "none" instead. \n',this.model); + matRad_cfg.dispWarning('matRad: Invalid biological model: %s; using "none" instead.',this.model); this.model = 'none'; end @@ -198,7 +198,7 @@ this.bioOpt = false; end else - matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using constant RBE instead. \n']); + matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using constant RBE instead.']); this.model = 'constRBE'; end @@ -208,12 +208,12 @@ this.bioOpt = true; this.quantityVis = 'RBExD'; else - matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using MCN Model instead. \n']); + 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. \n']); + matRad_cfg.dispWarning(['matRad: Invalid biological optimization quantity: ' this.quantityOpt '; using "none" instead.']); this.quantityOpt = 'physicalDose'; end @@ -226,7 +226,7 @@ this.bioOpt = false; this.quantityVis = 'physicalDose'; else - matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using "none" instead. \n']); + matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using "none" instead.']); this.model = 'none'; end @@ -236,12 +236,12 @@ this.bioOpt = true; this.quantityVis = 'RBExD'; else - matRad_cfg.dispWarning(['matRad: Invalid biological Model: ' this.model '; using "none" instead. \n']); + matRad_cfg.dispWarning(['matRad: Invalid biological Model: ' this.model '; using "none" instead.']); this.model = 'none'; end otherwise - matRad_cfg.dispWarning(['matRad: Invalid biological optimization quantity: ' this.quantityOpt '; using "none" instead. \n']); + matRad_cfg.dispWarning(['matRad: Invalid biological optimization quantity: ' this.quantityOpt '; using "none" instead.']); this.quantityOpt = 'physicalDose'; end @@ -254,7 +254,7 @@ this.bioOpt = false; this.quantityVis = 'physicalDose'; else - matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using "none" instead. \n']); + matRad_cfg.dispWarning(['matRad: Invalid biological model: ' this.model '; using "none" instead.']); this.model = 'none'; end @@ -264,17 +264,17 @@ this.bioOpt = true; this.quantityVis = 'RBExD'; else - matRad_cfg.dispWarning(['matRad: Invalid biological Model: ' this.model '; using Local Effect Model instead. \n']); + 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. \n']); + matRad_cfg.dispWarning(['matRad: Invalid biological optimization quantity: ' this.quantityOpt '; using "none" instead.']); this.quantityOpt = 'physicalDose'; end otherwise - matRad_cfg.dispWarning(['matRad: Invalid biological radiation mode: ' this.radiationMode '; using photons instead. \n']); + matRad_cfg.dispWarning(['matRad: Invalid biological radiation mode: ' this.radiationMode '; using photons instead.']); this.radiationMode = 'photons'; end end @@ -315,7 +315,7 @@ methods % default constructor - function this = matRad_bioModel(sRadiationMode,sQuantityOpt, sModel) + function this = matRad_BioModel(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; @@ -383,7 +383,7 @@ - function [bixelAlpha,bixelBeta] = calcLQParameter(this,vRadDepths,baseDataEntry,mTissueClass,vAlpha_x,vBeta_x,vABratio) + 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); @@ -416,11 +416,14 @@ % TODO: assign normal tissue an RBE of 1.1 case {'protons_MCN'} - - - bixelLET = matRad_interp1(depths,baseDataEntry.LET,vRadDepths); + + 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; @@ -428,7 +431,11 @@ case {'protons_WED'} - bixelLET = matRad_interp1(depths,baseDataEntry.LET,vRadDepths); + 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); diff --git a/matRad_MCemittanceBaseData.m b/matRad_MCemittanceBaseData.m new file mode 100644 index 000000000..8fd8c076a --- /dev/null +++ b/matRad_MCemittanceBaseData.m @@ -0,0 +1,476 @@ +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/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 + 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 + + %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; + 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 = []; + emittance = machine.data(ixE).initFocus.emittance; + 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); + + i = energyIx; + + mcDataEnergy.NominalEnergy = ones(1, size(obj.machine.data(1).initFocus.dist,1)) * 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 + + %calcualte mean energy used my mcSquare with a formula fitted + %to TOPAS data + switch obj.machine.meta.radiationMode + case 'protons' + meanEnergy = @(x) 5.762374661332111e-20 * x^9 - 9.645413625310569e-17 * x^8 + 7.073049219034644e-14 * x^7 ... + - 2.992344292008054e-11 * x^6 + 8.104111934547256e-09 * x^5 - 1.477860913846939e-06 * x^4 ... + + 1.873625800704108e-04 * x^3 - 1.739424343114980e-02 * x^2 + 1.743224692623838e+00 * x ... + + 1.827112816899668e+01; + mcDataEnergy.MeanEnergy = ones(1, size(obj.machine.data(1).initFocus.dist,1)) * meanEnergy(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. + totalSigmaSq = ((w50) / 6.14)^2; + + totalSpreadSq = @(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; + + % use formula deducted from Bragg Kleeman rule to calcuate + % energy straggling given the total sigma and the range + % straggling + energySpread = (totalSigmaSq - totalSpreadSq(r80)) / (0.022^2 * 1.77^2 * meanEnergy(r80)^(2*1.77-2)); + energySpread(energySpread < 0) = 0; + mcDataEnergy.EnergySpread = ones(1, size(obj.machine.data(1).initFocus.dist,1)) * sqrt(energySpread); + case 'carbon' + % 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 + meanEnergy = @(x) 11.39 * x^0.628 + 11.24; + mcDataEnergy.MeanEnergy = meanEnergy(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? + mcDataEnergy.EnergySpread = obj.defaultRelativeEnergySpread; + case 'helium' + % 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 + meanEnergy = @(x) 7.57* x.^0.5848 + 3.063; + mcDataEnergy.MeanEnergy = meanEnergy(r80); + 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,:); + sigmaSq = sigma.^2; + + + % fitting for either matlab or octave_core_file_limit + if ~obj.matRad_cfg.isOctave + + %fit Courant-Synder equation to data using ipopt, formulae + %given in mcSquare documentation + sigmaNull = sqrt(interp1(z,sigmaSq,0)); + + qRes = @(rho, sigmaT) (sigmaSq - (sigmaNull^2 - 2*sigmaNull*rho*sigmaT.*z + sigmaT^2.*z.^2)); + + funcs.objective = @(x) sum(qRes(x(1), x(2)).^2); + funcs.gradient = @(x) [ 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaNull * x(2) * z)); + 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaNull * 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'; + options.ipopt.print_level = 1; + + start = [0.9; 0.1]; + [result, ~] = ipopt (start, funcs, options); + rho = result(1); + sigmaT = result(2); + + else + + %fit Courant-Synder equation to data using ipopt, formulae + %given in mcSquare documentation + sigmaNull = sqrt(interp1(z,sigmaSq,0)); + + qRes = @(rho, sigmaT) (sigmaSq - (sigmaNull^2 - 2*sigmaNull*rho*sigmaT.*z + sigmaT^2.*z.^2)); + + phi{1} = @(x) sum(qRes(x(1), x(2)).^2); + phi{2} = @(x) [ 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaNull * x(2) * z)); + 2 * sum(qRes(x(1), x(2)) .* (2 * sigmaNull * 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(sigmaNull^2 - 2 * rho * sigmaNull * sigmaT * obj.nozzleToIso + sigmaT^2 * obj.nozzleToIso^2); + CorrelationAtNozzle = (rho * sigmaNull - 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 = sigmaNull^2 - 2*rho*sigmaNull*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 * sigmaNull; + 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); + + obj.machine.data(ixE).initFocus.emittance.type = 'bigaussian'; + obj.machine.data(ixE).initFocus.emittance.sigmaX = [obj.monteCarloData(:,count).SpotSize1x obj.monteCarloData(:,count).SpotSize2x]; + obj.machine.data(ixE).initFocus.emittance.sigmaY = [obj.monteCarloData(:,count).SpotSize1y obj.monteCarloData(:,count).SpotSize2y]; + obj.machine.data(ixE).initFocus.emittance.divX = [obj.monteCarloData(:,count).Divergence1x obj.monteCarloData(:,count).Divergence2x]; + obj.machine.data(ixE).initFocus.emittance.divY = [obj.monteCarloData(:,count).Divergence1y obj.monteCarloData(:,count).Divergence2y]; + obj.machine.data(ixE).initFocus.emittance.corrX = [obj.monteCarloData(:,count).Correlation1x obj.monteCarloData(:,count).Correlation2x]; + obj.machine.data(ixE).initFocus.emittance.corrY = [obj.monteCarloData(:,count).Correlation1y obj.monteCarloData(:,count).Correlation2y]; + obj.machine.data(ixE).initFocus.emittance.weight = [obj.monteCarloData(:,count).Weight2]; %Weight one will not be stored explicitly due to normalization + + 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.matRadRoot,'basedata',[machineName '.mat']); + + save('-v7',machineFilePath,'machine'); + obj.matRad_cfg.dispInfo('Saved Emittance to 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 +end + diff --git a/matRad_aperture2collimation.m b/matRad_aperture2collimation.m new file mode 100644 index 000000000..2cb30a6c7 --- /dev/null +++ b/matRad_aperture2collimation.m @@ -0,0 +1,130 @@ +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/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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%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); + stfTmp.ray = ray; + + stf(iBeam) = stfTmp; +end + + diff --git a/matRad_calcCubes.m b/matRad_calcCubes.m index ebfc74363..ac650bb2b 100644 --- a/matRad_calcCubes.m +++ b/matRad_calcCubes.m @@ -1,5 +1,5 @@ function resultGUI = matRad_calcCubes(w,dij,scenNum) -% matRad computation of all cubes for the resultGUI struct +% matRad computation of all cubes for the resultGUI struct % which is used as result container and for visualization in matRad's GUI % % call @@ -19,13 +19,13 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% 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/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. % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -43,24 +43,38 @@ % get bixel - beam correspondence for i = 1:dij.numOfBeams beamInfo(i).suffix = ['_beam', num2str(i)]; - beamInfo(i).logIx = (dij.beamNum == i); + beamInfo(i).logIx = (dij.beamNum == i); end beamInfo(dij.numOfBeams+1).suffix = ''; -beamInfo(dij.numOfBeams+1).logIx = true(size(w)); -% +beamInfo(dij.numOfBeams+1).logIx = true(size(resultGUI.w,1),1); + +%% Physical Dose +doseFields = {'physicalDose','doseToWater'}; +doseQuantities = {'','_std','_batchStd'}; % 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); +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 -% consider RBE for protons -if isfield(dij,'RBE') - for i = 1:length(beamInfo) - resultGUI.(['RBExD', beamInfo(i).suffix]) = resultGUI.(['physicalDose', beamInfo(i).suffix]) * dij.RBE; - end -end +%% LET % consider LET if isfield(dij,'mLETDose') for i = 1:length(beamInfo) @@ -71,50 +85,80 @@ 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') - - for i = 1:length(beamInfo) - - wBeam = (resultGUI.w .* beamInfo(i).logIx); - - ix = dij.bx~=0 & resultGUI.(['physicalDose', beamInfo(i).suffix])(:) > 0; - - 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.(['RBExD', beamInfo(i).suffix]) = zeros(size(resultGUI.(['effect', beamInfo(i).suffix]))); - resultGUI.(['RBExD', 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.(['RBExD', 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); +%% 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 - SqrtBetaDoseCube = full(dij.mSqrtBetaDose{scenNum} * wBeam); - resultGUI.(['beta', beamInfo(i).suffix])(ix) = (SqrtBetaDoseCube(ix)./resultGUI.(['physicalDose', beamInfo(i).suffix])(ix)).^2; + % 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(:,scenNum)~=0 & resultGUI.(['physicalDose', beamInfo(i).suffix])(:) > 0; + + % 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(ix).^2 + 4 .* dij.bx(ix) .* resultGUI.(['effect', RBE_model{j}, beamInfo(i).suffix])(ix)) - dij.ax(ix))./(2.*dij.bx(ix)); + + % Divide RBExD with the physicalDose to get the plain RBE cube + resultGUI.(['RBE', RBE_model{j}, beamInfo(i).suffix]) = resultGUI.(['RBExD', RBE_model{j}, beamInfo(i).suffix])./resultGUI.(['physicalDose', beamInfo(i).suffix]); + + % 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 -% Add non-processed MC tallies -% Note that the tallies are already computed per beam and altogether -if isfield(dij,'MC_tallies') - for f = 1:numel(dij.MC_tallies) - tally = dij.MC_tallies{f}; - % skip tallies processed above - if ~isfield(resultGUI,tally) - resultGUI.(tally) = reshape(full(dij.(tally){scenNum}),dij.doseGrid.dimensions); - 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 @@ -122,22 +166,18 @@ % 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 + myFields = fieldnames(resultGUI); + for i = 1:numel(myFields) + if numel(resultGUI.(myFields{i})) == dij.doseGrid.numOfVoxels -end + % 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 index a6b204097..f4824a2c7 100644 --- a/matRad_calcDoseDirect.m +++ b/matRad_calcDoseDirect.m @@ -70,7 +70,7 @@ if strcmp(pln.radiationMode,'photons') dij = matRad_calcPhotonDose(ct,stf,pln,cst,true); %dij = matRad_calcPhotonDoseVmc(ct,stf,pln,cst,5000,4,calcDoseDirect); -elseif strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon') +elseif strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon') || strcmp(pln.radiationMode,'helium') dij = matRad_calcParticleDose(ct,stf,pln,cst,true); end @@ -78,7 +78,11 @@ if pln.multScen.totNumScen == 1 % calculate cubes; use uniform weights here, weighting with actual fluence % already performed in dij construction - resultGUI = matRad_calcCubes(ones(pln.propStf.numOfBeams,1),dij,1); + if size(dij.physicalDose{1},2) ~= pln.propStf.numOfBeams + matRad_cfg.dispError('Number of beams stored not the same as size of dij.'); + else + resultGUI = matRad_calcCubes(ones(pln.propStf.numOfBeams,1),dij,1); + end % calc individual scenarios else diff --git a/matRad_calcDoseDirectMC.m b/matRad_calcDoseDirectMC.m index 421d644eb..1d33b8ca1 100644 --- a/matRad_calcDoseDirectMC.m +++ b/matRad_calcDoseDirectMC.m @@ -1,12 +1,11 @@ -function resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w,nHistories) -% matRad function to bypass dij calculation for MC dose calculation +function resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w) +% 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,w,nHistories) +% resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w) % % input % ct: ct cube @@ -15,7 +14,6 @@ % 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 @@ -25,35 +23,36 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% 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/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(); +% Instance of MatRad_Config class +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 +% load appropriate config from pln or from class +pln = matRad_cfg.getDefaultClass(pln,'propMC'); + +% load default parameters in case they haven't been set yet +pln = matRad_cfg.getDefaultProperties(pln,'propDoseCalc'); % 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'); + 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) + if sum([stf.totalNumOfBixels]) ~= size(w,1) matRad_cfg.dispError('weighting does not match steering information'); end counter = 0; @@ -61,7 +60,7 @@ for j = 1:stf(i).numOfRays for k = 1:stf(i).numOfBixelsPerRay(j) counter = counter + 1; - stf(i).ray(j).weight(k) = w(counter); + stf(i).ray(j).weight(k,:) = w(counter,:); end end end @@ -75,54 +74,64 @@ w(counter) = stf(i).ray(j).weight(k); end end - end -end + end +end % dose calculation -if strcmp(pln.radiationMode,'protons') - engines = {'TOPAS','MCsquare'}; - if ~isfield(pln,'propMC') || ~isfield(pln.propMC,'proton_engine') || ~any(strcmp(pln.propMC.proton_engine,engines)) - matRad_cfg.dispInfo('Using default proton MC engine "%s"\n',matRad_cfg.propMC.default_proton_engine); - pln.propMC.proton_engine = matRad_cfg.propMC.default_proton_engine; - end - - switch pln.propMC.proton_engine - case 'MCsquare' - dij = matRad_calcParticleDoseMCsquare(ct,stf,pln,cst,nHistories,calcDoseDirect); - case 'TOPAS' - dij = matRad_calcParticleDoseMCtopas(ct,stf,pln,cst,nHistories,calcDoseDirect); - end -else - matRad_cfg.dispError('Forward MC only implemented for protons.'); +switch pln.propMC.engine + case 'MCsquare' + dij = matRad_calcParticleDoseMCsquare(ct,stf,pln,cst,calcDoseDirect); + case 'TOPAS' + dij = matRad_calcParticleDoseMCtopas(ct,stf,pln,cst,calcDoseDirect); end +% calc resulting dose +if ~isprop(pln.propMC,'externalCalculation') || ~pln.propMC.externalCalculation + if pln.multScen.numOfCtScen == 1 + % calculate cubes; use uniform weights here, weighting with actual fluence + % already performed in dij construction + if size(dij.physicalDose{1},2) ~= dij.numOfBeams || size(dij.physicalDose{1},2) ~= numel(dij.beamNum) + matRad_cfg.dispWarning('Number of beams stored not the same as size of dij. Using singular weight for MC'); + % This parameter should be 1 since calcDoseDirect is true and all weights are used in the simulation already + dij.numOfBeams = size(dij.physicalDose{1},2); + dij.beamNum = (1:dij.numOfBeams)'; + end + resultGUI = matRad_calcCubes(ones(dij.numOfBeams,1),dij,1); + + % calc individual scenarios + else + Cnt = 1; + ixForOpt = find(~cellfun(@isempty, dij.physicalDose))'; + for i = ixForOpt + tmpResultGUI = matRad_calcCubes(ones(size(dij.physicalDose{i},2),1),dij,i); + if i == 1 + resultGUI.([pln.bioParam.quantityVis]) = tmpResultGUI.(pln.bioParam.quantityVis); + end + resultGUI.([pln.bioParam.quantityVis '_' num2str(Cnt,'%d')]) = tmpResultGUI.(pln.bioParam.quantityVis); + resultGUI.phaseDose{1,i} = tmpResultGUI.(pln.bioParam.quantityVis); + Cnt = Cnt + 1; + end + + end -dij.numOfBeams = size(dij.physicalDose{1},2); -dij.beamNum = 1:size(dij.physicalDose{1},2); + if pln.multScen.numOfCtScen ~= 1 + resultGUI.accPhysicalDose = zeros(size(resultGUI.phaseDose{1})); + for i = 1:pln.multScen.numOfCtScen + resultGUI.accPhysicalDose = resultGUI.accPhysicalDose + resultGUI.phaseDose{i}; + end + end +end -% calc resulting dose -if pln.multScen.totNumScen == 1 - % calculate cubes; use uniform weights here, weighting with actual fluence - % already performed in dij construction - resultGUI = matRad_calcCubes(ones(size(dij.physicalDose{1},2),1),dij,1); - -% calc individual scenarios -else - Cnt = 1; - ixForOpt = find(~cellfun(@isempty, dij.physicalDose))'; - for i = ixForOpt - tmpResultGUI = matRad_calcCubes(ones(size(dij.physicalDose{i},2),1),dij,i); - if i == 1 - resultGUI.([pln.bioParam.quantityVis]) = tmpResultGUI.(pln.bioParam.quantityVis); - end - resultGUI.([pln.bioParam.quantityVis '_' num2str(Cnt,'%d')]) = tmpResultGUI.(pln.bioParam.quantityVis); - Cnt = Cnt + 1; - end - +% Export histories to resultGUI +if isfield(dij,'nbHistoriesTotal') + resultGUI.nbHistoriesTotal = dij.nbHistoriesTotal; + resultGUI.nbParticlesTotal = dij.nbParticlesTotal; +elseif isprop(pln.propMC,'numHistories') + resultGUI.historiesMC = pln.propMC.numHistories; end % remember original fluence weights -resultGUI.w = w; +resultGUI.w = w; diff --git a/matRad_calcDoseInit.m b/matRad_calcDoseInit.m index 595e4b7ab..e0d41e560 100644 --- a/matRad_calcDoseInit.m +++ b/matRad_calcDoseInit.m @@ -6,29 +6,6 @@ calcDoseDirect = false; end -% assign analytical mode -if isfield(pln,'propDoseCalc') && isfield(pln.propDoseCalc,'fineSampling') && strcmp(pln.radiationMode, 'protons') - pbCalcMode = 'fineSampling'; - defaultFineSampling = matRad_cfg.propDoseCalc.defaultFineSamplingProperties; - if isfield(pln.propDoseCalc.fineSampling,'N') - fineSamplingN = pln.propDoseCalc.fineSampling.N; - else - fineSamplingN = defaultFineSampling.N; - end - if isfield(pln.propDoseCalc.fineSampling,'sigmaSub') - fineSamplingSigmaSub = pln.propDoseCalc.fineSampling.sigmaSub; - else - fineSamplingSigmaSub = defaultFineSampling.sigmaSub; - end - if isfield(pln.propDoseCalc.fineSampling,'method') - fineSamplingMethod = pln.propDoseCalc.fineSampling.method; - else - fineSamplingMethod = defaultFineSampling.method; - end -else - pbCalcMode = 'standard'; -end - % to guarantee downwards compatibility with data that does not have % ct.x/y/z if ~any(isfield(ct,{'x','y','z'})) @@ -183,7 +160,7 @@ % Convert CT subscripts to coarse linear indices. [yCoordsV_voxDoseGrid, xCoordsV_voxDoseGrid, zCoordsV_voxDoseGrid] = ind2sub(dij.doseGrid.dimensions,VdoseGrid); -% load base data% load machine file +% load base data fileName = [pln.radiationMode '_' pln.machine]; try load([fileparts(mfilename('fullpath')) filesep 'basedata' filesep fileName '.mat']); diff --git a/matRad_calcDoseInitBeam.m b/matRad_calcDoseInitBeam.m index cd4b3677e..fd66b0d39 100644 --- a/matRad_calcDoseInitBeam.m +++ b/matRad_calcDoseInitBeam.m @@ -43,10 +43,10 @@ % Calculate radiological depth cube matRad_cfg.dispInfo('matRad: calculate radiological depth cube... '); -if strcmp(pbCalcMode, 'fineSampling') - [radDepthVctGrid, radDepthsMat] = matRad_rayTracing(stf(i),ct,VctGrid,rot_coordsV,effectiveLateralCutoff); +if strcmp(pln.propDoseCalc.fineSampling.calcMode, 'fineSampling') + [radDepthVctGrid, radDepthsMat] = matRad_rayTracing(stf(i),ct,VctGrid,rot_coordsV,pln.propDoseCalc.effectiveLateralCutOff); else - radDepthVctGrid = matRad_rayTracing(stf(i),ct,VctGrid,rot_coordsV,effectiveLateralCutoff); + radDepthVctGrid = matRad_rayTracing(stf(i),ct,VctGrid,rot_coordsV,pln.propDoseCalc.effectiveLateralCutOff); end matRad_cfg.dispInfo('done.\n'); diff --git a/matRad_calcLateralParticleCutOff.m b/matRad_calcLateralParticleCutOff.m index 587ea5672..6165d5da5 100644 --- a/matRad_calcLateralParticleCutOff.m +++ b/matRad_calcLateralParticleCutOff.m @@ -36,11 +36,7 @@ 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; @@ -120,13 +116,7 @@ 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); @@ -143,15 +133,9 @@ cumIntEnergy(peakIxOrg+1):energyStepsTail:cumIntEnergy(end) cumIntEnergy(end)]); [cumIntEnergy,ix] = unique(cumIntEnergy); - depthValues = matRad_interp1(cumIntEnergy,machine.data(energyIx).depths(ix),vEnergySteps); + 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 + idd = matRad_interp1(machine.data(energyIx).depths,machine.data(energyIx).Z,depthValues) * conversionFactor; cnt = cnt +1 ; % % calculate dose in spot @@ -259,14 +243,8 @@ 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); @@ -289,11 +267,8 @@ 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, diff --git a/matRad_calcParticleDose.m b/matRad_calcParticleDose.m index dfc28a3ce..1e5a7c21b 100644 --- a/matRad_calcParticleDose.m +++ b/matRad_calcParticleDose.m @@ -31,21 +31,27 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -matRad_cfg = MatRad_Config.instance(); +% Instance of MatRad_Config class +matRad_cfg = MatRad_Config.instance(); % initialize waitbar, but catch exceptions (e.g. in Octave when run from cmd-line) figureWait = []; -try - figureWait = waitbar(0,'calculate dose influence matrix for particles...'); - % prevent closure of waitbar and show busy state - set(figureWait,'pointer','watch'); -catch - figureWait = []; +if matRad_cfg.logLevel > 1 + try + figureWait = waitbar(0,'calculate dose influence matrix for particles...'); + % prevent closure of waitbar and show busy state + set(figureWait,'pointer','watch'); + catch + figureWait = []; + end end matRad_cfg.dispInfo('matRad: Particle dose calculation... \n'); +% load default parameters in case they haven't been set yet +pln = matRad_cfg.getDefaultProperties(pln,{'propDoseCalc'}); + % init dose calc matRad_calcDoseInit; @@ -54,39 +60,35 @@ % if biological optimization considering a variable RBE is true then create alphaDose and betaDose containers and sparse matrices if pln.bioParam.bioOpt - + alphaDoseTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.totNumShiftScen,pln.multScen.totNumRangeScen); betaDoseTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.totNumShiftScen,pln.multScen.totNumRangeScen); - + for ctScen = 1:pln.multScen.numOfCtScen for shiftScen = 1:pln.multScen.totNumShiftScen - for rangeShiftScen = 1:pln.multScen.totNumRangeScen + for rangeShiftScen = 1:pln.multScen.totNumRangeScen if pln.multScen.scenMask(ctScen,shiftScen,rangeShiftScen) dij.mAlphaDose{ctScen,shiftScen,rangeShiftScen} = spalloc(dij.doseGrid.numOfVoxels,numOfColumnsDij,1); dij.mSqrtBetaDose{ctScen,shiftScen,rangeShiftScen} = spalloc(dij.doseGrid.numOfVoxels,numOfColumnsDij,1); - end - end - end - end -end - -if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'calcLET') - pln.propDoseCalc.calcLET = matRad_cfg.propDoseCalc.defaultCalcLET; + end + end + end + end end -if pln.propDoseCalc.calcLET +if pln.propDoseCalc.calcLET if isfield(machine.data,'LET') - + letDoseTmpContainer = cell(numOfBixelsContainer,pln.multScen.numOfCtScen,pln.multScen.totNumShiftScen,pln.multScen.totNumRangeScen); - + for ctScen = 1:pln.multScen.numOfCtScen for shiftScen = 1:pln.multScen.totNumShiftScen for rangeShiftScen = 1:pln.multScen.totNumRangeScen - + if pln.multScen.scenMask(ctScen,shiftScen,rangeShiftScen) dij.mLETDose{ctScen,shiftScen,rangeShiftScen} = spalloc(dij.doseGrid.numOfVoxels,numOfColumnsDij,1); end - + end end end @@ -98,22 +100,20 @@ %Toggles correction of small difference of current SSD to distance used %in generation of base data (e.g. phantom surface at isocenter) -if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc, 'airOffsetCorrection') - pln.propDoseCalc.airOffsetCorrection = true; - +if pln.propDoseCalc.airOffsetCorrection if ~isfield(machine.meta, 'fitAirOffset') 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',fitAirOffset); else fitAirOffset = machine.meta.fitAirOffset; - end -else + end +else fitAirOffset = 0; end if ~isfield(machine.meta, 'BAMStoIsoDist') BAMStoIsoDist = 1000; - matRad_cfg.dispWarning('Machine data does not contain BAMStoIsoDist. Using default value of %f mm\n.',BAMStoIsoDist); + matRad_cfg.dispWarning('Machine data does not contain BAMStoIsoDist. Using default value of %f mm.',BAMStoIsoDist); else BAMStoIsoDist = machine.meta.BAMStoIsoDist; end @@ -127,32 +127,34 @@ % generates tissue class matrix for biological treatment planning and alpha_x, beta_x, vectors if pln.bioParam.bioOpt - + vTissueIndex = zeros(size(VdoseGrid,1),1); - dij.ax = zeros(dij.doseGrid.numOfVoxels,1); - dij.bx = zeros(dij.doseGrid.numOfVoxels,1); - dij.abx = zeros(dij.doseGrid.numOfVoxels,1); % alpha beta ratio - + dij.ax = zeros(dij.doseGrid.numOfVoxels,ct.numOfCtScen); + dij.bx = zeros(dij.doseGrid.numOfVoxels,ct.numOfCtScen); + dij.abx = zeros(dij.doseGrid.numOfVoxels,ct.numOfCtScen); % alpha beta ratio + % retrieve photon LQM parameter for the current dose grid voxels - [dij.ax,dij.bx] = matRad_getPhotonLQMParameters(cst,dij.doseGrid.numOfVoxels,1,VdoseGrid); - + % [dij.ax,dij.bx] = matRad_getPhotonLQMParameters(cst,dij.doseGrid.numOfVoxels,ct.numOfCtScen,VdoseGrid); + [dij.ax,dij.bx] = matRad_getPhotonLQMParameters(cst,dij.doseGrid.numOfVoxels,ct.numOfCtScen); + + dij.abx(dij.bx>0) = dij.ax(dij.bx>0)./dij.bx(dij.bx>0); - + % only if LEM is used corresponding bio data must be available in the base data set if strcmp(pln.bioParam.model,'LEM') if isfield(machine.data,'alphaX') && isfield(machine.data,'betaX') - - matRad_cfg.dispInfo('\tloading biological base data...'); - + + matRad_cfg.dispInfo('loading biological base data...'); + 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 consistency of biological baseData and cst settings if ~isempty(IdxTissue) isInVdoseGrid = ismember(VdoseGrid,cst{i,4}{1}); @@ -160,31 +162,29 @@ else matRad_cfg.dispError('biological base data and cst inconsistent!'); end - + else vTissueIndex(row) = 1; matRad_cfg.dispInfo(' tissue type of %s was set to 1...',cst{i,2}); end end - - matRad_cfg.dispInfo(' done.\n'); - + + matRad_cfg.dispInfo('Done!\n'); + else matRad_cfg.dispError('base data is incomplement - alphaX and/or betaX is missing'); end - + else % parametrized biological models are based on the LET if ~isfield(machine.data,'LET') matRad_cfg.dispError('base data is incomplement - LET is missing'); end - end % end is LEM model + end % end is LEM model end % lateral cutoff for raytracing and geo calculations -if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'geometricCutOff') - effectiveLateralCutoff = matRad_cfg.propDoseCalc.defaultGeometricCutOff; -end +pln.propDoseCalc.effectiveLateralCutOff = matRad_cfg.propDoseCalc.defaultGeometricCutOff; if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'lateralCutOff') pln.propDoseCalc.lateralCutOff = matRad_cfg.propDoseCalc.defaultLateralCutOff; @@ -199,50 +199,48 @@ %raytracing for shiftScen = 1:pln.multScen.totNumShiftScen - + %Find first instance of the shift to select the shift values ixShiftScen = find(pln.multScen.linearMask(:,2) == shiftScen,1); - % manipulate isocenter for k = 1:length(stf) stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(ixShiftScen,:); end - + if pln.multScen.totNumShiftScen > 1 matRad_cfg.dispInfo('\tShift scenario %d of %d: \n',shiftScen,pln.multScen.totNumShiftScen); end - + counter = 0; - + % compute SSDs only for first scenario stf = matRad_computeSSD(stf,ct); - + for i = 1:numel(stf) % loop over all beams - + % init beam matRad_calcDoseInitBeam; - + % Determine lateral cutoff - matRad_cfg.dispInfo('\tmatRad: calculate lateral cutoff...'); - cutOffLevel = pln.propDoseCalc.lateralCutOff; + matRad_cfg.dispInfo('matRad: calculate lateral cutoff...'); visBoolLateralCutOff = 0; - machine = matRad_calcLateralParticleCutOff(machine,cutOffLevel,stf(i),visBoolLateralCutOff); - matRad_cfg.dispInfo('done.\n'); - + machine = matRad_calcLateralParticleCutOff(machine,pln.propDoseCalc.lateralCutOff,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); - + % calculate initial sigma for all bixel on current ray sigmaIniRay = matRad_calcSigmaIni(machine.data,stf(i).ray(j),stf(i).ray(j).SSD); - if strcmp(pbCalcMode, 'fineSampling') + if strcmp(pln.propDoseCalc.fineSampling.calcMode, 'fineSampling') % Ray tracing for beam i and ray j with explicit % lateral distances for fine sampling [ix,~,~,~,latDistsX,latDistsZ] = matRad_calcGeoDists(rot_coordsVdoseGrid, ... @@ -256,13 +254,13 @@ % function provides the weights for the sub-pencil beams, % their positions and their sigma used for dose calculation for k = 1:stf(i).numOfBixelsPerRay(j) % loop over all bixels per ray - if (fineSamplingSigmaSub < sigmaIniRay(k)) && (fineSamplingSigmaSub > 0) + if (pln.propDoseCalc.fineSampling.sigmaSub < sigmaIniRay(k)) && (pln.propDoseCalc.fineSampling.sigmaSub > 0) [finalWeight(:,k), sigmaSub(:,k), posX(:,k), posZ(:,k), numOfSub(:,k)] = ... - matRad_calcWeights(sigmaIniRay(k), fineSamplingMethod, fineSamplingN, fineSamplingSigmaSub); + matRad_calcWeights(sigmaIniRay(k), pln.propDoseCalc.fineSampling.method, pln.propDoseCalc.fineSampling.N, pln.propDoseCalc.fineSampling.sigmaSub); else - if (fineSamplingSigmaSub < 0) + if (pln.propDoseCalc.fineSampling.sigmaSub < 0) matRad_cfg.dispError('Chosen fine sampling sigma cannot be negative!'); - elseif (fineSamplingSigmaSub > sigmaIniRay(k)) + elseif (pln.propDoseCalc.fineSampling.sigmaSub > sigmaIniRay(k)) matRad_cfg.dispError('Chosen fine sampling sigma is too high for defined plan!'); end end @@ -281,23 +279,26 @@ % just use tissue classes of voxels found by ray tracer if pln.bioParam.bioOpt vTissueIndex_j = vTissueIndex(ix,:); - end - - for k = 1:stf(i).numOfBixelsPerRay(j) % loop over all bixels per ray + else + vTissueIndex_j = zeros(size(ix)); + end + + for k = 1:stf(i).numOfBixelsPerRay(j) % loop over all bixels per ray counter = counter + 1; bixelsPerBeam = bixelsPerBeam + 1; - + if matRad_cfg.logLevel > 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 - 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 + + % 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 end % remember beam and bixel number @@ -329,7 +330,7 @@ % find energy index in base data energyIx = find(round2(stf(i).ray(j).energy(k),4) == round2([machine.data.energy],4)); - + % 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 @@ -340,22 +341,21 @@ else dR = 0; end - + % 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 + dR); - % calculate projected coordinates for fine sampling of % each beamlet - if strcmp(pbCalcMode, 'fineSampling') + if strcmp(pln.propDoseCalc.fineSampling.calcMode, 'fineSampling') projCoords = matRad_projectOnComponents(VdoseGrid(ix), size(radDepthsMat{1}), stf(i).sourcePoint_bev,... stf(i).ray(j).targetPoint_bev, stf(i).isoCenter,... [dij.doseGrid.resolution.x dij.doseGrid.resolution.y dij.doseGrid.resolution.z],... -posX(:,k), -posZ(:,k), rotMat_system_T); - end + end % We do now loop over scenarios that alter voxel % values, e.g. range scenarios or ct phases, as we can @@ -364,7 +364,7 @@ for ctScen = 1:pln.multScen.numOfCtScen if any(any(pln.multScen.scenMask(ctScen,:,:))) %We don't need it if no scenario for this ct scenario is relevant % precomputations for fine-sampling - if strcmp(pbCalcMode, 'fineSampling') + if strcmp(pln.propDoseCalc.fineSampling.calcMode, 'fineSampling') % compute radial distances relative to pencil beam % component currRadialDist_sq = reshape(bsxfun(@plus,latDistsX,posX(:,k)'),[],1,numOfSub(k)).^2 + reshape(bsxfun(@plus,latDistsZ,posZ(:,k)'),[],1,numOfSub(k)).^2; @@ -391,13 +391,13 @@ end % find depth depended lateral cut off - if cutOffLevel >= 1 + if pln.propDoseCalc.lateralCutOff >= 1 currIx = currRadDepths <= machine.data(energyIx).depths(end) + offsetRadDepth; - elseif cutOffLevel < 1 && cutOffLevel > 0 + elseif pln.propDoseCalc.lateralCutOff < 1 && pln.propDoseCalc.lateralCutOff > 0 % perform rough 2D clipping currIx = currRadDepths <= machine.data(energyIx).depths(end) + offsetRadDepth & ... currRadialDist_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)',... @@ -424,7 +424,8 @@ %skip bixel continue; end - + + % adjust radDepth according to range shifter if pln.propDoseCalc.airOffsetCorrection currRadDepths(currIx) = currRadDepths(currIx) + stf(i).ray(j).rangeShifter(k).eqThickness + dR; @@ -439,17 +440,17 @@ % 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), ... stf(i).ray(j).rangeShifter(k), ... stf(i).ray(j).SSD); - + % add to initial sigma in quadrature sigmaIni_sq = sigmaIni_sq + sigmaRashi^2; end - if strcmp(pbCalcMode, 'fineSampling') + if strcmp(pln.propDoseCalc.fineSampling.calcMode, 'fineSampling') % initialise empty dose array totalDose = zeros(size(currIx,1),1); @@ -462,11 +463,13 @@ % run over components for c = 1:numOfSub(k) tmpDose = zeros(size(currIx,1),1); - bixelDose = finalWeight(c,k).*matRad_calcParticleDoseBixel(... - currRadDepths(currIx(:,:,c),1,c), ... - currRadialDist_sq(currIx(:,:,c),:,c), ... - sigmaSub(k)^2, ... - machine.data(energyIx)); + + bixelDose = finalWeight(c,k).*... + matRad_calcParticleDoseBixel(... + currRadDepths(currIx(:,:,c),1,c), ... + currRadialDist_sq(currIx(:,:,c),:,c), ... + sigmaSub(k)^2, ... + machine.data(energyIx)); tmpDose(currIx(:,:,c)) = bixelDose; totalDose = totalDose + tmpDose; @@ -518,36 +521,35 @@ bixelAlpha(isnan(bixelAlpha)) = 0; bixelBeta(isnan(bixelBeta)) = 0; - + alphaDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,shiftScen,rangeShiftScen} = sparse(VdoseGrid(ix(currIx)),1,bixelAlpha.*bixelDose,dij.doseGrid.numOfVoxels,1); betaDoseTmpContainer{mod(counter-1,numOfBixelsContainer)+1,ctScen,shiftScen,rangeShiftScen} = sparse(VdoseGrid(ix(currIx)),1,sqrt(bixelBeta).*bixelDose,dij.doseGrid.numOfVoxels,1); - end end end end - + matRad_calcDoseFillDij; - + end % end bixels per ray end - + end % end ray loop - + end % end beam loop - - + + % undo manipulation of isocenter for k = 1:length(stf) stf(k).isoCenter = stf(k).isoCenter - pln.multScen.isoShift(ixShiftScen,:); end - + end % end shift scenario loop dij = matRad_cleanDijScenarios(dij,pln,cst); -%Close Waitbar +% Close Waitbar if ishandle(figureWait) delete(figureWait); end diff --git a/matRad_calcParticleDoseMC.m b/matRad_calcParticleDoseMC.m index 4d3914fe3..a6513c9fe 100644 --- a/matRad_calcParticleDoseMC.m +++ b/matRad_calcParticleDoseMC.m @@ -1,4 +1,4 @@ -function dij = matRad_calcParticleDoseMC(ct,stf,pln,cst,nHistories,calcDoseDirect) +function dij = matRad_calcParticleDoseMC(ct,stf,pln,cst,calcDoseDirect) % matRad Monte Carlo particle dose calculation wrapper % Will call the appropriate subfunction for the respective % MC dose-calculation engine @@ -35,35 +35,25 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Instance of MatRad_Config class matRad_cfg = MatRad_Config.instance(); -if nargin < 6 +if nargin < 5 calcDoseDirect = false; + matRad_cfg.dispWarning('You have selected Monte Carlo dij calculation.'); end -if ~isfield(pln,'propMC') || ~isfield(pln.propMC,'proton_engine') - matRad_cfg.dispInfo('Using default proton MC engine "%s"\n',matRad_cfg.propMC.default_proton_engine); - engine = matRad_cfg.propMC.default_proton_engine; -else - engine = pln.propMC.proton_engine; -end - +% load appropriate config from pln or from class +pln = matRad_cfg.getDefaultClass(pln,'propMC'); -if nargin < 5 - if ~calcDoseDirect - nHistories = matRad_cfg.propMC.particles_defaultHistories; - matRad_cfg.dispInfo('Using default number of Histories per bixel: %d\n',nHistories); - else - nHistories = matRad_cfg.propMC.direct_defaultHistories; - matRad_cfg.dispInfo('Using default number of Histories for forward dose calculation: %d\n',nHistories); - end -end +% load default parameters in case they haven't been set yet +pln = matRad_cfg.getDefaultProperties(pln,'propDoseCalc'); -switch engine +switch pln.propMC.engine case 'MCsquare' - dij = matRad_calcParticleDoseMCsquare(ct,stf,pln,cst,nHistories,calcDoseDirect); + dij = matRad_calcParticleDoseMCsquare(ct,stf,pln,cst,calcDoseDirect); case 'TOPAS' - dij = matRad_calcParticleDoseMCtopas(ct,stf,pln,cst,nHistories,calcDoseDirect); + dij = matRad_calcParticleDoseMCtopas(ct,stf,pln,cst,calcDoseDirect); otherwise matRad_cfg.dispError('MC engine %s not known/supported!',engine); end diff --git a/matRad_calcParticleDoseMCsquare.m b/matRad_calcParticleDoseMCsquare.m index 2fe007eb6..d13b828ee 100644 --- a/matRad_calcParticleDoseMCsquare.m +++ b/matRad_calcParticleDoseMCsquare.m @@ -1,17 +1,15 @@ -function dij = matRad_calcParticleDoseMCsquare(ct,stf,pln,cst,nCasePerBixel,calcDoseDirect) +function dij = matRad_calcParticleDoseMCsquare(ct,stf,pln,cst,calcDoseDirect) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % matRad MCsquare Monte Carlo proton dose calculation wrapper % % call -% dij = matRad_calcParticleDoseMc(ct,stf,pln,cst,calcDoseDirect) +% dij = matRad_calcParticleDoseMCsquare(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 (nCasePerBixel > 1), -% max stat uncertainity (0 < nCasePerBixel < 1) % calcDoseDirect: binary switch to enable forward dose % calculation % output @@ -24,20 +22,25 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% 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/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. % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - +% Instance of MatRad_Config class matRad_cfg = MatRad_Config.instance(); +% Handle inputs +if nargin < 5 + calcDoseDirect = false; +end + % initialize waitbar figureWait = waitbar(0,'calculate dose influence matrix with MCsquare...'); % prevent closure of waitbar and show busy state @@ -45,69 +48,17 @@ % check if valid machine if ~strcmp(pln.radiationMode,'protons') - matRad_cfg.dispError('Wrong radiation modality . MCsquare only supports protons!'); + matRad_cfg.dispError('Wrong radiation modality. MCsquare only supports protons!'); 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 -% switch between either using max stat uncertainity or total number of -% cases -if (nCasePerBixel < 1) - maxStatUncertainty = true; -else - maxStatUncertainty = false; +% Load class variables in pln +% for calcDoseDirect, this is already done in superior function +if ~isfield(pln,'propMC') || ~isa(pln.propMC,'matRad_MCsquareConfig') + pln = matRad_cfg.getDefaultClass(pln,'propMC','matRad_MCsquareConfig'); 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 ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'calcLET') - pln.propDoseCalc.calcLET = matRad_cfg.propDoseCalc.defaultCalcLET; -end - -if isfield(pln,'propMC') && isfield(pln.propMC,'config') - if isa(pln.propMC.config,'MatRad_MCsquareConfig') - matRad_cfg.dispInfo('Using given MCSquare Configuration in pln.propMC.config!\n'); - MCsquareConfig = pln.propMC.config; - else - %Create a default instance of the configuration - MCsquareConfig = MatRad_MCsquareConfig(); - - %Overwrite parameters - %mc = metaclass(topasConfig); %get metaclass information to check if we can overwrite properties - - if isstruct(pln.propMC.config) - props = fieldnames(pln.propMC.config); - for fIx = 1:numel(props) - fName = props{fIx}; - if isprop(MCsquareConfig,fName) - %We use a try catch block to catch errors when trying - %to overwrite protected/private properties instead of a - %metaclass approach - try - MCsquareConfig.(fName) = pln.propMC.config.(fName); - catch - matRad_cfg.dispWarning('Property ''%s'' for MatRad_MCsquareConfig will be omitted due to protected/private access or invalid value.',fName); - end - else - matRad_cfg.dispWarning('Unkown property ''%s'' for MatRad_MCsquareConfig will be omitted.',fName); - end - end - else - matRad_cfg.dispError('Invalid Configuration in pln.propMC.config'); - end - end -else - MCsquareConfig = MatRad_MCsquareConfig -end +% load default parameters in case they haven't been set yet +pln = matRad_cfg.getDefaultProperties(pln,'propDoseCalc'); env = matRad_getEnvironment(); @@ -136,9 +87,9 @@ %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!'); + matRad_cfg.dispWarning('Compiled sparse reader interface not found. Trying to compile it on the fly!'); try - matRad_compileMCsquareSparseReader(); + 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 @@ -156,22 +107,12 @@ 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'); + matRad_cfg.dispInfo('Done!\n'); 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; @@ -186,18 +127,15 @@ 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); - % 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; +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; @@ -217,19 +155,28 @@ dij.doseGrid.x,dij.doseGrid.y',dij.doseGrid.z,'linear'); end -%BDL File -bdFile = [machine.meta.machine '.txt']; +% switch for using existing BDL file (e.g. to fit matRad basedata), +% or generate BDL file from matRad base data using MCsquareBDL +if isfield(pln,'loadExistingBDL') && ~isempty(pln.loadExistingBDL) + % use existing BDL file + bdFile = pln.loadExistingBDL; + +else + % fit and create BDL file using selected machine file + bdFile = [machine.meta.machine '.txt']; -% bdFile = 'BDL_matRad.txt'; %use for baseData fit -MCsquareBDL = MatRad_MCsquareBaseData(machine,stf); -%matRad_createMCsquareBaseDataFile(bdFile,machine,1); -% MCsquareBDL = MCsquareBDL.saveMatradMachine('test'); -MCsquareBDL = MCsquareBDL.writeMCsquareData([MCsquareFolder filesep 'BDL' filesep bdFile]); -%movefile(bdFile,[MCsquareFolder filesep 'BDL/' bdFile]); -% MCsquareBDL = MCsquareBDL.saveMatradMachine('testMachine'); + % Calculate MCsquare base data + % Argument stf is optional, if given, calculation only for energies given in stf + MCsquareBDL = matRad_MCsquareBaseData(machine); + + %matRad_createMCsquareBaseDataFile(bdFile,machine,1); + MCsquareBDL = MCsquareBDL.writeMCsquareData([MCsquareFolder filesep 'BDL' filesep bdFile]); + MCsquareBDL = MCsquareBDL.saveMatradMachine('savedMatRadMachine'); + +end for scenarioIx = 1:pln.multScen.totNumScen - + % manipulate isocenter for k = 1:length(stf) stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(scenarioIx,:); @@ -238,58 +185,44 @@ ctScen = pln.multScen.linearMask(scenarioIx,1); shiftScen = pln.multScen.linearMask(scenarioIx,2); rangeShiftScen = pln.multScen.linearMask(scenarioIx,3); - + if pln.multScen.scenMask(ctScen,shiftScen,rangeShiftScen) - + %For direct dose calculation totalWeights = 0; - + %Count the scenarios scenCount = scenCount + 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; - + % MCsquare settings MCsquareConfigFile = sprintf('MCsquareConfig.txt'); - - MCsquareConfig = MatRad_MCsquareConfig; - - MCsquareConfig.BDL_Machine_Parameter_File = ['BDL/' bdFile]; - MCsquareConfig.BDL_Plan_File = 'currBixels.txt'; - MCsquareConfig.CT_File = 'MC2patientCT.mhd'; - MCsquareConfig.Num_Threads = nbThreads; - MCsquareConfig.RNG_Seed = 1234; - MCsquareConfig.Num_Primaries = nCasePerBixel; - + + pln.propMC.BDL_Machine_Parameter_File = ['BDL/' bdFile]; + pln.propMC.BDL_Plan_File = 'currBixels.txt'; + pln.propMC.CT_File = 'MC2patientCT.mhd'; + pln.propMC.Num_Threads = nbThreads; + pln.propMC.RNG_Seed = 1234; % turn simulation of individual beamlets - MCsquareConfig.Beamlet_Mode = ~calcDoseDirect; + pln.propMC.Beamlet_Mode = ~calcDoseDirect; % turn of writing of full dose cube - MCsquareConfig.Dose_MHD_Output = calcDoseDirect; + pln.propMC.Dose_MHD_Output = calcDoseDirect; % turn on sparse output - MCsquareConfig.Dose_Sparse_Output = ~calcDoseDirect; + pln.propMC.Dose_Sparse_Output = ~calcDoseDirect; % set threshold of sparse matrix generation - MCsquareConfig.Dose_Sparse_Threshold = relDoseCutoff; - + pln.propMC.Dose_Sparse_Threshold = relDoseCutOff; + %Matrices for LET if pln.propDoseCalc.calcLET - MCsquareConfig.LET_MHD_Output = calcDoseDirect; - MCsquareConfig.LET_Sparse_Output = ~calcDoseDirect; + pln.propMC.LET_MHD_Output = calcDoseDirect; + pln.propMC.LET_Sparse_Output = ~calcDoseDirect; end - - - % write patient data - MCsquareBinCubeResolution = [dij.doseGrid.resolution.x ... - dij.doseGrid.resolution.y ... - dij.doseGrid.resolution.z]; - - matRad_writeMhd(HUcube{ctScen},MCsquareBinCubeResolution,MCsquareConfig.CT_File); - - - + counter = 0; for i = 1:length(stf) %Let's check if we have a unique or no range shifter, because MCsquare @@ -303,13 +236,13 @@ 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'; @@ -317,13 +250,13 @@ stfMCsquare(i).rangeShifterID = 0; stfMCsquare(i).rangeShifterType = 'binary'; end - + 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]); stfMCsquare(i).SAD = stf(i).SAD; - + % allocate empty target point container for j = 1:numel(stfMCsquare(i).energies) stfMCsquare(i).energyLayer(j).targetPoints = []; @@ -332,7 +265,7 @@ 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; @@ -340,52 +273,55 @@ 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)) - %Set up the ray geometries and add current ray to energy - %layer - 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 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)]; - - 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))*MCsquareConfig.Num_Primaries)]; - - 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 ... - MCsquareConfig.Num_Primaries]; - 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 + + %Check if ray has a spot in the current energy layer + if any(stf(i).ray(j).energy == stfMCsquare(i).energies(k)) + %Set up the ray geometries and add current ray to energy + %layer + 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 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))*pln.propMC.numHistories)]; + + 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))*pln.propMC.numHistories)]; + + 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 ... + pln.propMC.numHistories]; + + stfMCsquare(i).energyLayer(k).MU = [stfMCsquare(i).energyLayer(k).MU ... + pln.propMC.numHistories]; + 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); @@ -396,19 +332,28 @@ 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]; + + pln.propMC.writeMhd(HUcube{ctScen},MCsquareBinCubeResolution); + + % write config file + pln.propMC.writeMCsquareinputAllFiles(MCsquareConfigFile,stfMCsquare); + %% MC computation and dij filling - matRad_writeMCsquareinputAllFiles(MCsquareConfigFile,MCsquareConfig,stfMCsquare); - % run MCsquare mcSquareCall = [mcSquareBinary ' ' MCsquareConfigFile]; matRad_cfg.dispInfo(['Calling Monte Carlo Engine: ' mcSquareCall]); @@ -416,58 +361,66 @@ mask = false(dij.doseGrid.numOfVoxels,1); mask(VdoseGrid) = true; - + % read output if ~calcDoseDirect %Read Sparse Matrix dij.physicalDose{1} = absCalibrationFactorMC2 * matRad_sparseBeamletsReaderMCsquare ( ... - [MCsquareConfig.Output_Directory filesep 'Sparse_Dose.bin'], ... + [pln.propMC.Output_Directory filesep 'Sparse_Dose.bin'], ... dij.doseGrid.dimensions, ... dij.totalNumOfBixels, ... mask); - + %Read sparse LET if pln.propDoseCalc.calcLET dij.mLETDose{1} = absCalibrationFactorMC2 * matRad_sparseBeamletsReaderMCsquare ( ... - [MCsquareConfig.Output_Directory filesep 'Sparse_LET.bin'], ... + [pln.propMC.Output_Directory filesep 'Sparse_LET.bin'], ... dij.doseGrid.dimensions, ... dij.totalNumOfBixels, ... mask); - - dij.MC_tallies{1} = 'LET'; end else %Read dose cube - cube = matRad_readMhd(MCsquareConfig.Output_Directory,'Dose.mhd'); + cube = pln.propMC.readMhd('Dose.mhd'); dij.physicalDose{1} = absCalibrationFactorMC2 * totalWeights * ... sparse(VdoseGrid,ones(numel(VdoseGrid),1), ... - cube(VdoseGrid), ... - dij.doseGrid.numOfVoxels,1); - + cube(VdoseGrid), ... + dij.doseGrid.numOfVoxels,1); + %Read LET cube if pln.propDoseCalc.calcLET - cube = matRad_readMhd(MCsquareConfig.Output_Directory,'LET.mhd'); + cube = pln.propMC.readMhd('LET.mhd'); dij.mLETDose{1} = absCalibrationFactorMC2 * totalWeights * ... sparse(VdoseGrid,ones(numel(VdoseGrid),1), ... - cube(VdoseGrid), ... - dij.doseGrid.numOfVoxels,1); - - dij.MC_tallies{1} = 'LET'; + cube(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 - + % reorder influence matrix to comply with matRad default ordering - if MCsquareConfig.Beamlet_Mode + if pln.propMC.Beamlet_Mode dij.physicalDose{1} = dij.physicalDose{1}(:,MCsquareOrder); if pln.propDoseCalc.calcLET dij.mLETDose{1} = dij.mLETDose{1}(:,MCsquareOrder); end end - + matRad_cfg.dispInfo('Simulation finished!\n'); - + %% Clear data - delete([MCsquareConfig.CT_File(1:end-4) '.*']); + delete([pln.propMC.CT_File(1:end-4) '.*']); delete('currBixels.txt'); delete('MCsquareConfig.txt'); @@ -475,21 +428,24 @@ if strcmp(env,'OCTAVE') rmdirConfirmState = confirm_recursive_rmdir(0); end - rmdir(MCsquareConfig.Output_Directory,'s'); + rmdir(pln.propMC.Output_Directory,'s'); %Reset to old confirmatoin state if strcmp(env,'OCTAVE') confirm_recursive_rmdir(rmdirConfirmState); end - + end - + % manipulate isocenter for k = 1:length(stf) stf(k).isoCenter = stf(k).isoCenter - pln.multScen.isoShift(scenarioIx,:); - end + end end +% Order fields for easier comparison between different dijs +dij = orderfields(dij); + %% cd back cd(currFolder); diff --git a/matRad_calcParticleDoseMCtopas.m b/matRad_calcParticleDoseMCtopas.m index 765d0a546..6645d7419 100644 --- a/matRad_calcParticleDoseMCtopas.m +++ b/matRad_calcParticleDoseMCtopas.m @@ -1,19 +1,17 @@ -function dij = matRad_calcParticleDoseMCtopas(ct,stf,pln,cst,nCasePerBixel,calcDoseDirect) +function dij = matRad_calcParticleDoseMCtopas(ct,stf,pln,cst,calcDoseDirect) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % matRad TOPAS Monte Carlo proton dose calculation wrapper % This calls a TOPAS installation (not included in matRad due to % licensing model of TOPAS) for MC simulation % % call -% dij = matRad_calcParticleDoseMCtopas(ct,stf,pln,cst,nCasePerBixel,calcDoseDirect) +% dij = matRad_calcParticleDoseMCtopas(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 (nCasePerBixel > 1), -% max stat uncertainity (0 < nCasePerBixel < 1) % calcDoseDirect: binary switch to enable forward dose % calcualtion % output @@ -24,195 +22,220 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% 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/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. % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Instance of MatRad_Config class matRad_cfg = MatRad_Config.instance(); +% handle inputs if nargin < 5 - % set number of particles simulated per pencil beam - nCasePerBixel = matRad_cfg.propMC.particles_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 TOPAS not yet supported.'); +if ~isfield(pln.propStf,'useRangeShifter') + pln.propStf.useRangeShifter = false; end -if isfield(pln,'propMC') && isfield(pln.propMC,'config') - if isa(pln.propMC.config,'MatRad_TopasConfig') - matRad_cfg.dispInfo('Using given Topas Configuration in pln.propMC.config!\n'); - topasConfig = pln.propMC.config; - else - %Create a default instance of the configuration - topasConfig = MatRad_TopasConfig(); - - %Overwrite parameters - %mc = metaclass(topasConfig); %get metaclass information to check if we can overwrite properties - - if isstruct(pln.propMC.config) - props = fieldnames(pln.propMC.config); - for fIx = 1:numel(props) - fName = props{fIx}; - if isprop(topasConfig,fName) - %We use a try catch block to catch errors when trying - %to overwrite protected/private properties instead of a - %metaclass approach - try - topasConfig.(fName) = pln.propMC.config.(fName); - catch - matRad_cfg.dispWarning('Property ''%s'' for MatRad_TopasConfig will be omitted due to protected/private access or invalid value.',fName); - end - else - matRad_cfg.dispWarning('Unkown property ''%s'' for MatRad_TopasConfig will be omitted.',fName); - end - end - else - matRad_cfg.dispError('Invalid Configuration in pln.propMC.config'); - end +% Set parameters for full Dij calculation +if ~calcDoseDirect + pln.propMC.scorer.calcDij = true; + pln.propMC.numOfRuns = 1; + + % Load class variables in pln + % for calcDoseDirect, this is already done in superior function + if ~isa(pln.propMC,'matRad_TopasConfig') + pln = matRad_cfg.getDefaultClass(pln,'propMC','matRad_TopasConfig'); + end + + if pln.propMC.numHistories < 1e10 + matRad_cfg.dispWarning('Selected TOPAS dij calculation with fewer than normal histories (default 1e10), make sure you want to continue.'); end else - topasConfig = MatRad_TopasConfig(); + if ~isa(pln.propMC,'matRad_TopasConfig') + matRad_cfg.dispError('Run calcParticleDoseMCtopas through calcDoseDirectMC'); + end end - -if ~calcDoseDirect - matRad_cfg.dispError('matRad so far only supports direct dose calculation for TOPAS!\n'); -end +% load default parameters for doseCalc in case they haven't been set yet +pln = matRad_cfg.getDefaultProperties(pln,{'propDoseCalc'}); -if ~isfield(pln.propStf,'useRangeShifter') - pln.propStf.useRangeShifter = false; +% set nested folder structure if external calculation is turned on (this will put new simulations in subfolders) +if pln.propMC.externalCalculation + pln.propMC.workingDir = [pln.propMC.thisFolder filesep 'MCrun' filesep]; + pln.propMC.workingDir = [pln.propMC.workingDir pln.radiationMode,'_',pln.machine,'_',datestr(now, 'dd-mm-yy')]; end -env = matRad_getEnvironment(); +%% Initialize dose grid and dij -%% Initialize dose Grid as usual +% load calcDoseInit as usual matRad_calcDoseInit; -% for TOPAS we explicitly downsample the ct to the dose grid (might not -% be necessary in future versions with separated grids) - +% for TOPAS we explicitly downsample the ct to the dose grid (might not be necessary in future versions with separated grids) +[ctR,~,~] = matRad_resampleCTtoGrid(ct,cst,pln,stf); -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'); +% overwrite CT grid in dij in case of modulation. +if isfield(ctR,'ctGrid') + dij.ctGrid = ctR.ctGrid; end -%Allocate temporary resampled CT -ctR = ct; -ctR.cube = cell(1); -ctR.cubeHU = cell(1); -ctR.numOfCtScen = 1; -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; - %% sending data to topas -load([pln.radiationMode,'_',pln.machine]); - -topasBaseData = MatRad_TopasBaseData(machine,stf);%,TopasConfig); - -topasConfig.numHistories = nCasePerBixel; -topasConfig.numOfRuns = matRad_cfg.propMC.topas_defaultNumBatches; - -%Collect weights -w = zeros(sum([stf(:).totalNumOfBixels]),1); -ct = 1; -for i = 1:length(stf) - for j = 1:stf(i).numOfRays - rayBix = stf(i).numOfBixelsPerRay(j); - w(ct:ct+rayBix-1) = stf(i).ray(j).weight; - ct = ct + rayBix; +% Load and create TOPAS Base Data +load([pln.radiationMode,'_',pln.machine],'machine'); + +% Collect given weights +if 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); + w(counter:counter+rayBix-1,:) = stf(i).ray(j).weight; + counter = counter + rayBix; + end end end +% Get photon parameters for RBExD calculation +if isfield(pln,'bioParam') && strcmp(pln.bioParam.quantityOpt,'RBExD') + pln.propMC.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; -for scenarioIx = 1:pln.multScen.totNumScen +for shiftScen = 1:pln.multScen.totNumShiftScen + + %Find first instance of the shift to select the shift values + ixShiftScen = find(pln.multScen.linearMask(:,2) == shiftScen,1); % manipulate isocenter - for k = 1:length(stf) - stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(scenarioIx,:); + for k = 1:numel(stf) + stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(ixShiftScen,:); end - - ctScen = pln.multScen.linearMask(scenarioIx,1); - shiftScen = pln.multScen.linearMask(scenarioIx,2); - rangeShiftScen = pln.multScen.linearMask(scenarioIx,3); - if pln.multScen.scenMask(ctScen,shiftScen,rangeShiftScen) - - %Overwrite CT (TEMPORARY - we should use 4D calculation in - %TOPAS here) - ctR.cubeHU = cubeHUresampled(ctScen); - ctR.cube = cubeResampled(ctScen); - - topasConfig.writeAllFiles(ctR,pln,stf,topasBaseData,w); - - % Run simulation for current scenario - cd(topasConfig.workingDir); - - for beamIx = 1:numel(stf) - for runIx = 1:topasConfig.numOfRuns - fname = sprintf('%s_field%d_run%d',topasConfig.label,beamIx,runIx); - topasCall = sprintf('%s %s.txt > %s.out 2> %s.err',topasConfig.topasExecCommand,fname,fname,fname); - if topasConfig.parallelRuns - finishedFiles{runIx} = sprintf('%s.finished',fname); - delete(finishedFiles{runIx}); - topasCall = [topasCall '; touch ' finishedFiles{runIx} ' &']; + % Delete previous topas files so there is no mix-up + files = dir([pln.propMC.workingDir,'*']); + files = {files(~[files.isdir]).name}; + fclose('all'); + for i = 1:length(files) + delete([pln.propMC.workingDir,files{i}]) + end + + % Run simulations for each scenario + for ctScen = 1:pln.multScen.numOfCtScen + for rangeShiftScen = 1:pln.multScen.totNumRangeScen + if pln.multScen.scenMask(ctScen,shiftScen,rangeShiftScen) + + % Save ctScen and rangeShiftScen for file constructor + if ct.numOfCtScen > 1 + ctR.currCtScen = ctScen; + ctR.currRangeShiftScen = rangeShiftScen; end - matRad_cfg.dispInfo('Calling TOPAS: %s\n',topasCall); - [status,cmdout] = system(topasCall,'-echo'); - if status == 0 - matRad_cfg.dispInfo('TOPAS simulation completed succesfully\n'); + + % actually write TOPAS files + if calcDoseDirect + pln.propMC.writeAllFiles(ctR,cst,pln,stf,machine,w); else - matRad_cfg.dispError('TOPAS simulation exited with error code %d\n',status); + pln.propMC.writeAllFiles(ctR,cst,pln,stf,machine); end end - - if topasConfig.parallelRuns - runsFinished = false; - pause('on'); - while ~runsFinished - pause(1); - fin = cellfun(@(f) exist(f,'file'),finishedFiles); - runsFinished = all(fin); + end + end + + % change director back to original directory + cd(pln.propMC.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 pln.propMC.externalCalculation + matRad_cfg.dispInfo(['TOPAS simulation skipped for external calculation\nFiles have been written to: "',replace(pln.propMC.workingDir,'\','\\'),'"']); + else + for ctScen = 1:ct.numOfCtScen + for beamIx = 1:numel(stf) + for runIx = 1:pln.propMC.numOfRuns + if ct.numOfCtScen > 1 + fname = sprintf('%s_field%d_ct%d_run%d',pln.propMC.label,beamIx,ctScen,runIx); + else + fname = sprintf('%s_field%d_run%d',pln.propMC.label,beamIx,runIx); + end + + if isprop(pln.propMC,'verbosity') && strcmp(pln.propMC.verbosity,'full') + topasCall = sprintf('%s %s.txt',pln.propMC.topasExecCommand,fname); + else + topasCall = sprintf('%s %s.txt > %s.out > %s.log',pln.propMC.topasExecCommand,fname,fname,fname); + end + + % initiate parallel runs and delete previous files + if pln.propMC.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)); + 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 pln.propMC.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 - - cd(currDir); - - %% Simulation finished - read out volume scorers from topas simulation - - topasCubes = matRad_readTopasData(topasConfig.workingDir); - - fnames = fieldnames(topasCubes); - dij.MC_tallies = fnames; - for f = 1:numel(fnames) - dij.(fnames{f}){1} = sum(w)*reshape(topasCubes.(fnames{f}),[],1); end end - % manipulate isocenter back - for k = 1:length(stf) - stf(k).isoCenter = stf(k).isoCenter - pln.multScen.isoShift(scenarioIx,:); - end + % revert back to original directory + cd(currDir); + +end + +%% Simulation(s) finished - read out volume scorers from topas simulation +% Skip readout if external files were generated +if ~pln.propMC.externalCalculation + dij = pln.propMC.readFiles(pln.propMC.workingDir); + + % Order fields for easier comparison between different dijs + dij = orderfields(dij); +else + dij = struct([]); +end + +% manipulate isocenter back +for k = 1:length(stf) + stf(k).isoCenter = stf(k).isoCenter - pln.multScen.isoShift(ixShiftScen,:); +end end diff --git a/matRad_calcPhotonDose.m b/matRad_calcPhotonDose.m index af55e6579..081b68f22 100644 --- a/matRad_calcPhotonDose.m +++ b/matRad_calcPhotonDose.m @@ -33,7 +33,8 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -matRad_cfg = MatRad_Config.instance(); +% Instance of MatRad_Config class +matRad_cfg = MatRad_Config.instance(); % initialize matRad_calcDoseInit; @@ -56,32 +57,19 @@ % 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 +% load default parameters if not set +pln = matRad_cfg.getDefaultProperties(pln,'propDoseCalc'); % 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 +% set lateral cutoff value +lateralCutOff = pln.propDoseCalc.geometricCutOff; % [mm] -% 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; +if kernelCutoff < pln.propDoseCalc.geometricCutOff + 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 % 0 if field calc is bixel based, 1 if dose calc is field based @@ -128,7 +116,9 @@ % Create fluence matrix F = ones(floor(fieldWidth/intConvResolution)); - if ~useCustomPrimFluenceBool + % toggle custom primary fluence on/off. if 0 we assume a homogeneous + % primary fluence, if 1 we use measured radially symmetric data + if ~pln.propDoseCalc.useCustomPrimaryPhotonFluence % gaussian convolution of field to model penumbra F = real(ifft2(fft2(F,gaussConvSize,gaussConvSize).*fft2(gaussFilter,gaussConvSize,gaussConvSize))); end @@ -154,7 +144,7 @@ % 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); +pln.propDoseCalc.effectiveLateralCutOff = lateralCutOff + fieldWidth/sqrt(2); % book keeping - this is necessary since pln is not used in optimization or % matRad_calcCubes @@ -203,7 +193,7 @@ 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 + if ~pln.propDoseCalc.useCustomPrimaryPhotonFluence && ~isFieldBasedDoseCalc % Display console message matRad_cfg.dispInfo('\tUniform primary photon fluence -> pre-compute kernel convolution...\n'); @@ -233,7 +223,7 @@ bixelsPerBeam = bixelsPerBeam + 1; % convolution here if custom primary fluence OR field based dose calc - if useCustomPrimFluenceBool || isFieldBasedDoseCalc + if pln.propDoseCalc.useCustomPrimaryPhotonFluence || isFieldBasedDoseCalc % overwrite field opening if necessary if isFieldBasedDoseCalc @@ -306,7 +296,7 @@ stf(i).ray(j).targetPoint_bev, ... machine.meta.SAD, ... find(~isnan(radDepthVdoseGrid{ctScen})), ... - effectiveLateralCutoff); + pln.propDoseCalc.effectiveLateralCutOff); % empty bixels may happen during recalculation of error % scenarios -> skip to next bixel @@ -338,7 +328,7 @@ % sample dose only for bixel based dose calculation if ~isFieldBasedDoseCalc - r0 = 25; % [mm] sample beyond the inner core + r0 = 20 + stf(i).bixelWidth; % [mm] sample beyond the inner core Type = 'radius'; [ix,bixelDose] = matRad_DijSampling(ix,bixelDose,manipulatedRadDepthCube,rad_distancesSq,Type,r0); end diff --git a/matRad_calcPhotonDoseMC.m b/matRad_calcPhotonDoseMC.m index ef86912e9..2c6342060 100644 --- a/matRad_calcPhotonDoseMC.m +++ b/matRad_calcPhotonDoseMC.m @@ -1,364 +1,59 @@ -function dij = matRad_calcPhotonDoseMC(ct,stf,pln,cst,nCasePerBixel,visBool) -% matRad ompMC monte carlo photon dose calculation wrapper +function dij = matRad_calcPhotonDoseMC(ct,stf,pln,cst,calcDoseDirect) +% matRad Monte Carlo photon dose calculation wrapper +% Will call the appropriate subfunction for the respective +% MC dose-calculation engine % % call -% dij = matRad_calcPhotonDoseMc(ct,stf,pln,cst,visBool) +% dij = matRad_calcParticleDoseMc(ct,stf,pln,cst) +% 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 -% visBool: binary switch to enable visualization +% nHistories: number of histories per beamlet +% calcDoseDirect: (optional) binary switch to enable forward +% dose calcualtion (default: false) % 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 +% 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(); +matRad_cfg = MatRad_Config.instance(); -% 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:ct.numOfCtScen - - 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 - -%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; - -outputVariance = matRad_cfg.propMC.ompMC_defaultOutputVariance; - -if isfield(pln,'propMC') && isfield(pln.propMC,'outputVariance') - outputVariance = pln.propMC.outputVariance; + calcDoseDirect = false; end -%% Create beamlet source -useCornersSCD = true; %false -> use ISO corners - -scenCount = 0; +% load appropriate config from pln or from class +pln = matRad_cfg.getDefaultClass(pln,'propMC'); -for scenarioIx = 1:pln.multScen.totNumScen - - % manipulate isocenter - for k = 1:length(stf) - stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(scenarioIx,:); - end +% load default parameters in case they haven't been set yet +pln = matRad_cfg.getDefaultProperties(pln,'propDoseCalc'); - ctScen = pln.multScen.linearMask(scenarioIx,1); - shiftScen = pln.multScen.linearMask(scenarioIx,2); - rangeShiftScen = pln.multScen.linearMask(scenarioIx,3); - - if pln.multScen.scenMask(ctScen,shiftScen,rangeShiftScen) - scenCount = scenCount + 1; - - 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 - if pln.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,pln.multScen.totNumScen); - end - - ompMCgeo.isoCenter = [stf(:).isoCenter]; - - %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 outputVariance - [dij.physicalDose{ctScen,shiftScen,rangeShiftScen},dij.physicalDose_MCvar{ctScen,shiftScen,rangeShiftScen}] = omc_matrad(cubeRho{ctScen},cubeMatIx{ctScen},ompMCgeo,ompMCsource,ompMCoptions); - else - [dij.physicalDose{ctScen,shiftScen,rangeShiftScen}] = omc_matrad(cubeRho{ctScen},cubeMatIx{ctScen},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 - - dij.physicalDose{ctScen,shiftScen,rangeShiftScen} = dij.physicalDose{ctScen,shiftScen,rangeShiftScen} * absCalibrationFactor; - if isfield(dij,'physicalDose_MCvar') - dij.physicalDose_MCvar{s} = dij.physicalDose_MCvar{ctScen,shiftScen,rangeShiftScen} * absCalibrationFactor^2; - end - - ompMCgeo.isoCenter = [stf(:).isoCenter]; - - matRad_cfg.dispInfo('matRad: MC photon dose calculation done!\n'); - - try - % wait 0.1s for closing all waitbars - allWaitBarFigures = findall(0,'type','figure','tag','TMWWaitbar'); - delete(allWaitBarFigures); - pause(0.1); - catch - end - - end - - % manipulate isocenter back - for k = 1:length(stf) - stf(k).isoCenter = stf(k).isoCenter - pln.multScen.isoShift(scenarioIx,:); - end +switch pln.propMC.engine + case 'ompMC' + dij = matRad_calcPhotonDoseOmpMC(ct,stf,pln,cst,calcDoseDirect); + case 'TOPAS' + dij = matRad_calcPhotonDoseMCtopas(ct,stf,pln,cst,calcDoseDirect); + otherwise + matRad_cfg.dispError('MC engine %s not known/supported!',engine); end - end diff --git a/matRad_calcPhotonDoseMCtopas.m b/matRad_calcPhotonDoseMCtopas.m new file mode 100644 index 000000000..ed478a595 --- /dev/null +++ b/matRad_calcPhotonDoseMCtopas.m @@ -0,0 +1,218 @@ +function dij = matRad_calcPhotonDoseMCtopas(ct,stf,pln,cst,calcDoseDirect) +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% matRad TOPAS Monte Carlo proton dose calculation wrapper +% This calls a TOPAS installation (not included in matRad due to +% licensing model of TOPAS) for MC simulation +% +% call +% dij = matRad_calcParticleDoseMCtopas(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 +% calcDoseDirect: binary switch to enable forward dose +% calcualtion +% output +% dij: matRad dij 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(); + +if nargin < 5 + calcDoseDirect = false; +end + +% Set parameters for full Dij calculation +if ~calcDoseDirect + pln.propMC.scorer.calcDij = true; + pln.propMC.numOfRuns = 1; + + % Load class variables in pln + % for calcDoseDirect, this is already done in superior function + if ~isa(pln.propMC,'matRad_TopasConfig') + pln = matRad_cfg.getDefaultClass(pln,'propMC','matRad_TopasConfig'); + end +else + if ~isa(pln.propMC,'matRad_TopasConfig') + matRad_cfg.dispError('Run calcParticleDoseMCtopas through calcDoseDirectMC'); + end +end + +% load default parameters for doseCalc in case they haven't been set yet +pln = matRad_cfg.getDefaultProperties(pln,'propDoseCalc'); + +% set nested folder structure if external calculation is turned on (this will put new simulations in subfolders) +if pln.propMC.externalCalculation + pln.propMC.workingDir = [pln.propMC.thisFolder filesep 'MCrun' filesep]; + pln.propMC.workingDir = [pln.propMC.workingDir pln.radiationMode,'_',pln.machine,'_',datestr(now, 'dd-mm-yy')]; +end + +%% Initialize dose Grid as usual +matRad_calcDoseInit; + +% for TOPAS we explicitly downsample the ct to the dose grid (might not be necessary in future versions with separated grids) +[ctR,~,~] = matRad_resampleCTtoGrid(ct,cst,pln,stf); + +% overwrite CT grid in dij in case of modulation. +if isfield(ctR,'ctGrid') + dij.ctGrid = ctR.ctGrid; +end + +%% sending data to topas + +load([pln.radiationMode,'_',pln.machine]); + +%Collect weights +if calcDoseDirect + w = zeros(sum([stf(:).totalNumOfBixels]),1); + ct = 1; + for i = 1:length(stf) + for j = 1:stf(i).numOfRays + rayBix = stf(i).numOfBixelsPerRay(j); + w(ct:ct+rayBix-1) = stf(i).ray(j).weight; + ct = ct + rayBix; + end + end +else + w = ones(sum([stf(:).totalNumOfBixels]),1); +end + +currDir = cd; + +for shiftScen = 1:pln.multScen.totNumShiftScen + + % manipulate isocenter + for k = 1:length(stf) + stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(shiftScen,:); + end + + % Delete previous topas files so there is no mix-up + files = dir([pln.propMC.workingDir,'*']); + files = {files(~[files.isdir]).name}; + fclose('all'); + for i = 1:length(files) + delete([pln.propMC.workingDir,files{i}]) + end + + for ctScen = 1:pln.multScen.numOfCtScen + for rangeShiftScen = 1:pln.multScen.totNumRangeScen + if pln.multScen.scenMask(ctScen,shiftScen,rangeShiftScen) + + % Save ctScen and rangeShiftScen for file constructor + if ct.numOfCtScen > 1 + ctR.currCtScen = ctScen; + ctR.currRangeShiftScen = rangeShiftScen; + end + + % actually write TOPAS files + if calcDoseDirect + pln.propMC.writeAllFiles(ctR,cst,pln,stf,machine,w); + else + pln.propMC.writeAllFiles(ctR,cst,pln,stf,machine); + end + + end + end + end + + % change director back to original directory + cd(pln.propMC.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 pln.propMC.externalCalculation + matRad_cfg.dispInfo(['TOPAS simulation skipped for external calculation\nFiles have been written to: "',replace(pln.propMC.workingDir,'\','\\'),'"']); + else + for ctScen = 1:ct.numOfCtScen + for beamIx = 1:numel(stf) + for runIx = 1:pln.propMC.numOfRuns + if ct.numOfCtScen > 1 + fname = sprintf('%s_field%d_ct%d_run%d',pln.propMC.label,beamIx,ctScen,runIx); + else + fname = sprintf('%s_field%d_run%d',pln.propMC.label,beamIx,runIx); + end + + if isprop(pln.propMC,'verbosity') && strcmp(pln.propMC.verbosity,'full') + topasCall = sprintf('%s %s.txt',pln.propMC.topasExecCommand,fname); + else + topasCall = sprintf('%s %s.txt > %s.out 2> %s.log',pln.propMC.topasExecCommand,fname,fname,fname); + end + + % initiate parallel runs and delete previous files + if pln.propMC.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)); + if status == 0 + matRad_cfg.dispInfo('TOPAS simulation completed succesfully\n'); + else + if status == 139 + matRad_cfg.dispError('TOPAS segmentation fault: could also 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 pln.propMC.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); + +end + +%% Simulation(s) finished - read out volume scorers from topas simulation +% Skip readout if external files were generated +if ~pln.propMC.externalCalculation + dij = pln.propMC.readFiles(pln.propMC.workingDir); + + % Order fields for easier comparison between different dijs + dij = orderfields(dij); +else + dij = []; +end + +% manipulate isocenter back +for k = 1:length(stf) + stf(k).isoCenter = stf(k).isoCenter - pln.multScen.isoShift(shiftScen,:); +end +end diff --git a/matRad_calcPhotonDoseOmpMC.m b/matRad_calcPhotonDoseOmpMC.m new file mode 100644 index 000000000..221500f0f --- /dev/null +++ b/matRad_calcPhotonDoseOmpMC.m @@ -0,0 +1,198 @@ +function dij = matRad_calcPhotonDoseOmpMC(ct,stf,pln,cst,calcDoseDirect) +% matRad ompMC monte carlo photon dose calculation wrapper +% +% call +% dij = matRad_calcPhotonDoseMC(ct,stf,pln,cst) +% +% 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. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Instance of MatRad_Config class +matRad_cfg = MatRad_Config.instance(); + +% handle inputs +if nargin < 6 + calcDoseDirect = false; +end + +if calcDoseDirect + matRad_cfg.dispWarning('ompMC does not support forward dose calculation natively! Will compute full dij and then apply fluence vector.'); +end + +% load default parameters for matRad_OmpConfig class in case they haven't been set yet +if ~isa(pln.propMC,'matRad_OmpConfig') + pln = matRad_cfg.getDefaultClass(pln,'propMC','matRad_OmpConfig'); +end + +% load default parameters for doseCalc in case they haven't been set yet +pln = matRad_cfg.getDefaultProperties(pln,{'propDoseCalc'}); + +% compile ompMC interface +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_OmpConfig.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 + +%% Initialize dose grid and dij + +% load calcDoseInit as usual +matRad_calcDoseInit; + +% 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 = pln.propMC.numHistories; + +%% Setup OmpMC options / parameters +ompMCoptions = pln.propMC.getOmpMCoptions(machine); + +% conversion from HU to densities & materials +[~,cubeMatIx,cubeRho] = pln.propMC.materialConversion(dij.ctGrid,dij.doseGrid,ct); + +% Get ompMC geometry +ompMCgeo = pln.propMC.getOmpMCgeometry(dij.doseGrid); + + +%% debug visualization +if pln.propMC.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 + + +% Now we have to calibrate to the the beamlet width. +pln.propMC.absCalibrationFactor = pln.propMC.absCalibrationFactor * (pln.propStf.bixelWidth/50)^2; + + +%% Create beamlet source +scenCount = 0; + +for scenarioIx = 1:pln.multScen.totNumScen + + % manipulate isocenter + for k = 1:length(stf) + stf(k).isoCenter = stf(k).isoCenter + pln.multScen.isoShift(scenarioIx,:); + end + + ctScen = pln.multScen.linearMask(scenarioIx,1); + shiftScen = pln.multScen.linearMask(scenarioIx,2); + rangeShiftScen = pln.multScen.linearMask(scenarioIx,3); + + if pln.multScen.scenMask(ctScen,shiftScen,rangeShiftScen) + scenCount = scenCount + 1; + + % load ompMC source + ompMCsource = pln.propMC.getOmpMCsource(dij.numOfBeams,dij.totalNumOfBixels,stf); + + % Book keeping for dij + counter = 0; + for i = 1:dij.numOfBeams + for j = 1:stf(i).numOfRays + counter = counter + 1; + dij.beamNum(counter) = i; + dij.rayNum(counter) = j; + dij.bixelNum(counter) = j; + end + end + + %% Call the OmpMC interface + if pln.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,pln.multScen.totNumScen); + end + + ompMCgeo.isoCenter = [stf(:).isoCenter]; + + %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 pln.propMC.outputVariance + [dij.physicalDose{ctScen,shiftScen,rangeShiftScen},dij.physicalDose_MCvar{ctScen,shiftScen,rangeShiftScen}] = omc_matrad(cubeRho{ctScen},cubeMatIx{ctScen},ompMCgeo,ompMCsource,ompMCoptions); + else + [dij.physicalDose{ctScen,shiftScen,rangeShiftScen}] = omc_matrad(cubeRho{ctScen},cubeMatIx{ctScen},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 + + dij.physicalDose{ctScen,shiftScen,rangeShiftScen} = dij.physicalDose{ctScen,shiftScen,rangeShiftScen} * pln.propMC.absCalibrationFactor; + if isfield(dij,'physicalDose_MCvar') + dij.physicalDose_MCvar{s} = dij.physicalDose_MCvar{ctScen,shiftScen,rangeShiftScen} * pln.propMC.absCalibrationFactor^2; + end + + matRad_cfg.dispInfo('matRad: MC photon dose calculation done!\n'); + + try + % wait 0.1s for closing all waitbars + allWaitBarFigures = findall(0,'type','figure','tag','TMWWaitbar'); + delete(allWaitBarFigures); + pause(0.1); + catch + end + + % manipulate isocenter back + for k = 1:length(stf) + stf(k).isoCenter = stf(k).isoCenter - pln.multScen.isoShift(scenarioIx,:); + end + end +end + + +end diff --git a/matRad_calculateProbabilisticQuantities.m b/matRad_calculateProbabilisticQuantities.m index 18e344f64..4e0b8ccd9 100644 --- a/matRad_calculateProbabilisticQuantities.m +++ b/matRad_calculateProbabilisticQuantities.m @@ -94,7 +94,7 @@ (((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'); + matRad_cfg.dispInfo('Done!\n'); end matRad_cfg.dispInfo('\tFinalizing Omega...'); %Finalize Omega matrices diff --git a/matRad_fluenceOptimization.m b/matRad_fluenceOptimization.m index 9a56b6f7f..907678f52 100644 --- a/matRad_fluenceOptimization.m +++ b/matRad_fluenceOptimization.m @@ -1,6 +1,6 @@ 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) @@ -21,13 +21,13 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% 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 +% 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. % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -37,16 +37,16 @@ % consider VOI priorities cst = matRad_setOverlapPriorities(cst); -% check & adjust objectives and constraints internally for fractionation +% 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}; - + + 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 @@ -57,16 +57,16 @@ 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; + + cst{i,6}{j} = obj; end end -% resizing cst to dose cube resolution +% 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); + dij.doseGrid.x,dij.doseGrid.y,dij.doseGrid.z); % find target indices and described dose(s) for weight vector % initialization @@ -77,7 +77,7 @@ 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} @@ -87,7 +87,7 @@ %Add do dose list fDoses = [fDoses dParams]; end - + doseTarget = [doseTarget fDoses]; ixTarget = [ixTarget i*ones(1,length(fDoses))]; end @@ -95,13 +95,22 @@ [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'); + 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 @@ -110,74 +119,73 @@ end doseTmp = dij.physicalDose{1}*wOnes; - bixelWeight = (doseTarget)/(dij.RBE * mean(doseTmp(V))); + bixelWeight = (doseTarget)/(dij.RBE * mean(doseTmp(V))); wInit = wOnes * bixelWeight; - matRad_cfg.dispInfo('chosen uniform weight of %f!\n',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,1); + [ax,bx] = matRad_getPhotonLQMParameters(cst,dij.doseGrid.numOfVoxels,dij.numOfScenarios); 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'); + ~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.dispWarning('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'); + 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 - - dij.ixDose = dij.bx~=0; - + + dij.ixDose = dij.bx~=0; + 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; + 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 - dij.gamma = zeros(dij.doseGrid.numOfVoxels,1); - dij.gamma(dij.ixDose) = dij.ax(dij.ixDose)./(2*dij.bx(dij.ixDose)); - - - % 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; + %pre-calculations + dij.gamma = zeros(dij.doseGrid.numOfVoxels,dij.numOfScenarios); + dij.gamma(dij.ixDose) = dij.ax(dij.ixDose)./(2*dij.bx(dij.ixDose)); + + % 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; end - matRad_cfg.dispInfo('chosen weights adapted to biological dose calculation!\n'); -else + + 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); + 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 +% one robust objective is defined %Check how to use 4D data if isfield(pln,'propOpt') && isfield(pln.propOpt,'scen4D') @@ -199,12 +207,12 @@ 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 + 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 @@ -215,9 +223,9 @@ % 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; + ixForOpt = scen4D; else - ixForOpt = linIxDIJ; + ixForOpt = linIxDIJ; end switch pln.bioParam.quantityOpt @@ -250,7 +258,7 @@ %Get Bounds if ~isfield(pln.propOpt,'boundMU') pln.propOpt.boundMU = false; -end +end if pln.propOpt.boundMU if (isfield(dij,'minMU') || isfield(dij,'maxMU')) && ~isfield(dij,'numParticlesPerMU') @@ -285,7 +293,7 @@ warning(['Optimizer ''' pln.propOpt.optimizer ''' not known! Fallback to IPOPT!']); optimizer = matRad_OptimizerIPOPT; end - + optimizer = optimizer.optimize(wInit,optiProb,dij,cst); @@ -298,7 +306,7 @@ resultGUI.info = info; %Robust quantities -if FLAG_ROB_OPT || numel(ixForOpt) > 1 +if FLAG_ROB_OPT || numel(ixForOpt) > 1 Cnt = 1; for i = find(~cellfun(@isempty,dij.physicalDose))' tmpResultGUI = matRad_calcCubes(wOpt,dij,i); diff --git a/matRad_generateStf.m b/matRad_generateStf.m index ffff8d953..ce512827a 100644 --- a/matRad_generateStf.m +++ b/matRad_generateStf.m @@ -29,11 +29,14 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - +% Instance of MatRad_Config class matRad_cfg = MatRad_Config.instance(); matRad_cfg.dispInfo('matRad: Generating stf struct...\n'); +% load default parameters if not set +pln = matRad_cfg.getDefaultProperties(pln,{'propOpt','propStf'}); + if nargin < 4 visMode = 0; end @@ -409,7 +412,7 @@ % 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 + end stf(i).ray(j).focusIx = focusIx'; @@ -455,6 +458,9 @@ end + if ~isfield(stf(i).ray,'energy') + matRad_cfg.dispError('Error generating stf struct: no suitable energies found. Check if bixelwidth is too large.'); + end % store total number of rays for beam-i stf(i).numOfRays = size(stf(i).ray,2); diff --git a/matRad_generateStfSinglePencilBeam.m b/matRad_generateStfSinglePencilBeam.m new file mode 100644 index 000000000..a78309bdb --- /dev/null +++ b/matRad_generateStfSinglePencilBeam.m @@ -0,0 +1,95 @@ +function stf = matRad_generateStfSinglePencilBeam(ct,cst,pln) +% 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 +% +% 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 a single pencil beam stf struct...\n'); + +if (numel(pln.propStf.gantryAngles) ~= numel(pln.propStf.couchAngles)) && (numel(pln.propStf.gantryAngles) > 1) + matRad_cfg.dispError('Inconsistent number of gantry and couch angles.'); +end + +% 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 + +% Define steering file like struct. Prellocating for speed. +stf = struct; + +% Save meta information for treatment plan +stf.gantryAngle = pln.propStf.gantryAngles; +stf.couchAngle = pln.propStf.couchAngles; +stf.bixelWidth = pln.propStf.bixelWidth; +stf.radiationMode = pln.radiationMode; +stf.SAD = SAD; +stf.isoCenter = pln.propStf.isoCenter(1,:); +stf.numOfRays = 1; + +% Save ray and target position in beam eye's view (bev) +stf.ray.rayPos_bev = [0 0 0]; +stf.ray.targetPoint_bev = [0 SAD 0]; + +% source position in bev +stf.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,pln.propStf.couchAngles)); + + +stf.sourcePoint = stf.sourcePoint_bev*rotMat_vectors_T; + +% Save ray and target position in lps system. +stf.ray.rayPos = stf.ray.rayPos_bev*rotMat_vectors_T; +stf.ray.targetPoint = stf.ray.targetPoint_bev*rotMat_vectors_T; + + +% loop over all rays to determine meta information for each ray +stf.numOfBixelsPerRay = 1; + +stf.ray.energy = 100; + +stf.ray.rangeShifter.ID = 0; +stf.ray.rangeShifter.eqThickness = 0; +stf.ray.rangeShifter.sourceRashiDistance = 0; + +stf.ray.focusIx = 1; + + +% save total number of bixels +stf.totalNumOfBixels = 1; + +end diff --git a/ompMC/matRad_OmpConfig.m b/ompMC/matRad_OmpConfig.m new file mode 100644 index 000000000..78b16ee47 --- /dev/null +++ b/ompMC/matRad_OmpConfig.m @@ -0,0 +1,377 @@ +classdef matRad_OmpConfig < handle + % matRad_TopasConfig class definition + % + % + % 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/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 + % This parameter can be overwritten through MatRad_Config default parameters + numHistories = 1e6; %Number of histories to compute + + visBool = false; % disable visualiazation by default + + engine = 'ompMC'; %parameter for continuity + + %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! + + scale = 10; % to convert to cm + + outputVariance; + useCornersSCD = true; %false -> use ISO corners + end + + properties% (SetAccess = private) + omcFolder; + + end + + methods + function obj = matRad_OmpConfig() + matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class + + % Default execution paths are set here + obj.omcFolder = [matRad_cfg.matRadRoot filesep 'ompMC']; + + % Set default histories from MatRad_Config + if isfield(matRad_cfg.propMC,'ompMC_defaultHistories') + obj.numHistories = matRad_cfg.propMC.ompMC_defaultHistories; + end + + outputVariance = matRad_cfg.propMC.ompMC_defaultOutputVariance; + + end + + + function ompMCoptions = getOmpMCoptions(obj,machine) + matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class + + %display options + ompMCoptions.verbose = matRad_cfg.logLevel - 1; + + % start MC control + ompMCoptions.nHistories = obj.numHistories; + ompMCoptions.nSplit = 20; + ompMCoptions.nBatches = 10; + ompMCoptions.randomSeeds = [97 33]; + + %start source definition + ompMCoptions.spectrumFile = [obj.omcFolder filesep 'spectra' filesep 'mohan6.spectrum']; + ompMCoptions.monoEnergy = 0.1; + ompMCoptions.charge = 0; + ompMCoptions.sourceGeometry = 'gaussian'; + ompMCoptions.sourceGaussianWidth = 0.1 * obj.setGaussianPenumbra(machine); + + % start MC transport + ompMCoptions.dataFolder = [obj.omcFolder filesep 'data' filesep]; + ompMCoptions.pegsFile = [obj.omcFolder filesep 'pegs4' filesep '700icru.pegs4dat']; + ompMCoptions.pgs4formFile = [obj.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 = [obj.omcFolder filesep 'output' filesep]; + + end + + function ompMCgeo = getOmpMCgeometry(obj,doseGrid) + ompMCgeo.xBounds = (doseGrid.resolution.y * (0.5 + [0:doseGrid.dimensions(1)])) ./ obj.scale; + ompMCgeo.yBounds = (doseGrid.resolution.x * (0.5 + [0:doseGrid.dimensions(2)])) ./ obj.scale; + ompMCgeo.zBounds = (doseGrid.resolution.z * (0.5 + [0:doseGrid.dimensions(3)])) ./ obj.scale; + + % Create Material Density Cube + ompMCgeo.material = obj.setupMaterials(); + end + + function ompMCsource = getOmpMCsource(obj,numOfBeams,totalNumOfBixels,stf) + numOfBixels = [stf(:).numOfRays]; + beamSource = zeros(numOfBeams, 3); + + bixelCorner = zeros(totalNumOfBixels,3); + bixelSide1 = zeros(totalNumOfBixels,3); + bixelSide2 = zeros(totalNumOfBixels,3); + + beamNum = zeros(1,prod(sum(numOfBixels),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 + + ompMCsource.nBeams = numOfBeams; + ompMCsource.iBeam = 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 obj.visBool + plot3(ompMCsource.ySource,ompMCsource.xSource,ompMCsource.zSource,'rx') + end + + end + + function sigmaGauss = setGaussianPenumbra(~,machine) + % gaussian filter to model penumbra from (measured) machine output / see diploma thesis siggel 4.1.2 + matRad_cfg = MatRad_Config.instance(); + + 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] + + end + + function [HUcube,cubeMatIx,cubeRho] = materialConversion(obj,ctGrid,doseGrid,ct) + % conversion from HU to densities & materials + HUcube = cell(1,ct.numOfCtScen); + cubeMatIx = cell(1,ct.numOfCtScen); + cubeRho = cell(1,ct.numOfCtScen); + + % Create Material Density Cube + material = obj.setupMaterials(); + + for s = 1:ct.numOfCtScen + + HUcube{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(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(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 + end + + + end + + methods (Access = private) + 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 + + end + + methods (Static) + 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/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 = eval('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 = eval(['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/ompMC/matRad_compileOmpMCInterface.m b/ompMC/matRad_compileOmpMCInterface.m deleted file mode 100644 index 7f9229832..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 = eval('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 = eval(['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/projections/matRad_VariableRBEProjection.m b/optimization/projections/matRad_VariableRBEProjection.m index 2357fc6f5..615ead182 100644 --- a/optimization/projections/matRad_VariableRBEProjection.m +++ b/optimization/projections/matRad_VariableRBEProjection.m @@ -21,7 +21,7 @@ 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); + RBExD(dij.ixDose(:,scen)) = sqrt((effect(dij.ixDose(:,scen))./dij.bx(dij.ixDose(:,scen)))+(dij.gamma(dij.ixDose(:,scen)).^2)) - dij.gamma(dij.ixDose(:,scen)); end function wGrad = projectSingleScenarioGradient(obj,dij,doseGrad,scen,w) @@ -35,9 +35,9 @@ obj = obj.compute(dij,w); %Scaling vor variable RBExD - scaledEffect = obj.d{scen} + dij.gamma; + scaledEffect = obj.d{scen} + dij.gamma(:,scen); doseGradTmp = zeros(dij.doseGrid.numOfVoxels,1); - doseGradTmp(dij.ixDose) = doseGrad{scen}(dij.ixDose) ./ (2*dij.bx(dij.ixDose).*scaledEffect(dij.ixDose)); + doseGradTmp(dij.ixDose(:,scen)) = doseGrad{scen}(dij.ixDose(:,scen)) ./ (2*dij.bx(dij.ixDose(:,scen)).*scaledEffect(dij.ixDose(:,scen))); %Now modify the effect computation vBias = (doseGradTmp' * dij.mAlphaDose{scen})'; @@ -61,7 +61,7 @@ eExp = eExpLinTerm + eExpSqTerm.^2; RBExDexp = zeros(dij.doseGrid.numOfVoxels,1); - RBExDexp(dij.ixDose) = sqrt((eExp(dij.ixDose)./dij.bx(dij.ixDose))+(dij.gamma(dij.ixDose).^2)) - dij.gamma(dij.ixDose); + RBExDexp(dij.ixDose(:,scen)) = sqrt((eExp(dij.ixDose(:,scen))./dij.bx(dij.ixDose(:,scen)))+(dij.gamma(dij.ixDose(:,scen)).^2)) - dij.gamma(dij.ixDose(:,scen)); for i = 1:size(dij.physicalDoseOmega,2) dOmegaV{scen,i} = dij.mAlphaDoseOmega{scen,i} * w; @@ -80,7 +80,7 @@ %Scaling vor variable RBExD scaledEffect = obj.dExp{scen} + dij.gamma; doseGradTmp = zeros(dij.doseGrid.numOfVoxels,1); - doseGradTmp(dij.ixDose) = dExpGrad{scen}(dij.ixDose) ./ (2*dij.bx(dij.ixDose).*scaledEffect(dij.ixDose)); + doseGradTmp(dij.ixDose(:,scen)) = dExpGrad{scen}(dij.ixDose(:,scen)) ./ (2*dij.bx(dij.ixDose(:,scen)).*scaledEffect(dij.ixDose(:,scen))); %Now modify the effect computation vBias = (doseGradTmp' * dij.mAlphaDoseExp{scen})'; diff --git a/scenarios/scSlab01.mat b/scenarios/scSlab01.mat deleted file mode 100644 index 8418705ec..000000000 Binary files a/scenarios/scSlab01.mat and /dev/null differ diff --git a/test/matRad_runTests.m b/test/matRad_runTests.m index 71d226c43..cbe359ae2 100644 --- a/test/matRad_runTests.m +++ b/test/matRad_runTests.m @@ -72,9 +72,10 @@ else message = ME.message; end - errMsg = sprintf('Experiencd an error during testing of %s. Error-Message:\n %s',scriptName,message); + errMsg = sprintf('Experienced an error during testing of %s. Error-Message:\n %s',scriptName,message); warning(errMsg); errors{end+1} = errMsg; + cd([matRad_cfg.matRadRoot '/test']); %Make sure we don't get stuck in another folder end end @@ -82,4 +83,4 @@ if ~isempty(errors) error(strjoin(errors,'\n\n============================\n\n')); end - \ No newline at end of file + diff --git a/tools/matRad_fitBaseData.m b/tools/matRad_fitBaseData.m index 3b5f3d401..b5cd48173 100644 --- a/tools/matRad_fitBaseData.m +++ b/tools/matRad_fitBaseData.m @@ -19,7 +19,7 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% Copyright 2015 the matRad development team. +% 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 @@ -30,6 +30,9 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +matRad_cfg = MatRad_Config.instance(); + % save cube dimesions cubeDim = size(doseCube); @@ -80,49 +83,85 @@ 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)); - % 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); - + % 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; @@ -161,7 +200,7 @@ resSigma1 = interp1(depthsSigma, resSigma1, depthsIDD); resSigma2 = interp1(depthsSigma, resSigma2, depthsIDD); resWeight = interp1(depthsSigma, resWeight, depthsIDD); -resSigma2 = smoothdata(resSigma2, 'gaussian', 200); +%resSigma2 = smoothdata(resSigma2, 'gaussian', 200); % interpolate range at 80% dose after peak. [maxV, maxI] = max(IDD); diff --git a/tools/matRad_getAlphaBetaCurves.m b/tools/matRad_getAlphaBetaCurves.m index 5309f951a..8ce960ad1 100644 --- a/tools/matRad_getAlphaBetaCurves.m +++ b/tools/matRad_getAlphaBetaCurves.m @@ -72,7 +72,7 @@ modelName = 'HEL'; end end - pln.bioParam = matRad_bioModel(machine.meta.radiationMode,'RBExD',modelName); + 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') diff --git a/tools/matRad_plotParticleBaseDataEntry.m b/tools/matRad_plotParticleBaseDataEntry.m new file mode 100644 index 000000000..47ce1cfed --- /dev/null +++ b/tools/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_readBinData.m b/tools/matRad_readBinData.m new file mode 100644 index 000000000..3e7cd66ec --- /dev/null +++ b/tools/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/tools/matRad_readCsvData.m b/tools/matRad_readCsvData.m new file mode 100644 index 000000000..1f4bd62a0 --- /dev/null +++ b/tools/matRad_readCsvData.m @@ -0,0 +1,24 @@ +function dataOut = matRad_readCsvData(csvFile,cubeDim) + +% Instance of MatRad_Config class +matRad_cfg = MatRad_Config.instance(); + +% Read in csv file as table +dataTable = readtable(csvFile,'ReadVariableNames',false); + +% Check if consistent with cubeDim +if rem(size(dataTable,1),prod(cubeDim))==0 + % 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); + for i = 1:numOfReportQuantities + dataOut{i} = reshape(dataTable.(['Var' num2str(i+3)]),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 \ No newline at end of file diff --git a/tools/matRad_resampleCTtoGrid.m b/tools/matRad_resampleCTtoGrid.m new file mode 100644 index 000000000..d6281ebe5 --- /dev/null +++ b/tools/matRad_resampleCTtoGrid.m @@ -0,0 +1,77 @@ +function [ctR,cst,stf] = matRad_resampleCTtoGrid(ct,cst,pln,stf) +% function to resample the ct grid for example for faster MC computation +% +% call +% [ctR,cst,stf] = matRad_resampleGrid(ct,cst,stf) +% +% input +% ct: Path to folder where TOPAS files are in (as string) +% cst: matRad segmentation struct +% pln: matRad plan struct +% stf: matRad steering struct +% +% output +% ctR: resampled CT +% cst: updated ct struct (due to calcDoseInit) +% stf: updated stf 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/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(); %Instance of matRad configuration class + +% Load calcDoseInit as usual +% Note of warning, even though the pln is marked as unused, it is needed for calcDoseInit! +matRad_calcDoseInit; + +% 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.dispWarning('CT already resampled.'); +end +end diff --git a/tools/samplingAnalysis/matRad_calcStudy.m b/tools/samplingAnalysis/matRad_calcStudy.m index 63bc3c900..95da37f0c 100644 --- a/tools/samplingAnalysis/matRad_calcStudy.m +++ b/tools/samplingAnalysis/matRad_calcStudy.m @@ -90,7 +90,7 @@ function matRad_calcStudy(multScen,varargin) pln.bioOptimization = 'RBExD'; pln.model = 'LEM'; end - pln.bioParam = matRad_bioModel(pln.radiationMode, pln.bioOptimization, pln.model); + pln.bioParam = matRad_BioModel(pln.radiationMode, pln.bioOptimization, pln.model); end diff --git a/tools/samplingAnalysis/matRad_samplingAnalysis.m b/tools/samplingAnalysis/matRad_samplingAnalysis.m index 9b0b2aad6..1b1a13c4a 100644 --- a/tools/samplingAnalysis/matRad_samplingAnalysis.m +++ b/tools/samplingAnalysis/matRad_samplingAnalysis.m @@ -123,7 +123,7 @@ 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']); +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); diff --git a/topas/MatRad_TopasBaseData.m b/topas/MatRad_TopasBaseData.m deleted file mode 100644 index bd8eb1c66..000000000 --- a/topas/MatRad_TopasBaseData.m +++ /dev/null @@ -1,68 +0,0 @@ -classdef MatRad_TopasBaseData < MatRad_MCemittanceBaseData - % MatRad_TopasBaseData class for calculating TOPAS base data and - % writing it into a file, formatted for TOPAS to use - % - % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - % - % 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 - - end - - methods - function obj = MatRad_TopasBaseData(varargin) - % set default values for the simulation, if not specified - if nargin < 2 % input MatRad_TopasBaseData(machine) - TopasConfig = struct; - constArguments = 1; - elseif nargin < 3 && isfield(varargin{2}, 'gantryAngle') % input MatRad_TopasBaseData(machine,stf) - TopasConfig = struct; - constArguments = [1,2]; - else % input MatRad_TopasBaseData(machine,T) || (machine,stf,T) - TopasConfig = varargin{end}; - if nargin == 2 - constArguments = 1; - elseif nargin == 3 - constArguments = [1,2]; - end - end - - % Call MatRad_MCemmitanceBaseData constructor - obj = obj@MatRad_MCemittanceBaseData(varargin{constArguments}); - end - - function obj = writeTopasData(obj,ct,stf,pln,w) - %function that writes a data file containing stf specific data - %for a Monte Carlo simulation with TOPAS - - %look up focus indices - focusIndex = obj.selectedFocus(obj.energyIndex); - - % NozzleAxialDistance - nozzleAxialDistance_mm = 1500; - if isfield( obj.machine.meta,'nozzleAxialDistance') - disp('Using NAD from basedata') - nozzleAxialDistance_mm = obj.machine.meta.nozzleAxialDistance; - elseif isfield(obj.machine.meta,'BAMStoIsoDist') - disp('Using BAMStoIsoDist from basedata') - nozzleAxialDistance_mm = obj.machine.meta.BAMStoIsoDist; - else - disp('Using default nozzleAxialDistance') - end - - - end - - end -end - diff --git a/topas/MatRad_TopasConfig.m b/topas/MatRad_TopasConfig.m deleted file mode 100644 index 1b38cdf76..000000000 --- a/topas/MatRad_TopasConfig.m +++ /dev/null @@ -1,910 +0,0 @@ -classdef MatRad_TopasConfig < handle -% MatRad_TopasConfig 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/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 - topasExecCommand; %Defaults will be set during construction according to TOPAS installation instructions and used system - - parallelRuns = false; %Starts runs in parallel - - workingDir; %working directory for the simulation - - label = 'matRad_plan'; - - %Simulation parameters - numThreads = 0; %number of used threads, 0 = max number of threads (= num cores) - numOfRuns = 5; %Default number of runs / batches - modeHistories = 'num'; %'frac'; - fracHistories = 1e-4; %Fraction of histories to compute - numHistories = 1e6; %Number of histories to compute - verbosity = struct( 'timefeatures',0,... - 'cputime',true,... - 'run',0,... - 'event',0,... - 'tracking',0,... - 'material',0,... - 'maxinterruptedhistories',1000); - - 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' - - %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 (enables deflection) - - - %Image - materialConversion; - rsp_basematerial; - arrayOrdering = 'F'; %'C'; - - %Scoring - addVolumeScorers = true - scoreDose = true; - scoreTrackCount = false; - %scoreLET = true; - scoreReportQuantity = 'Sum'; - outputType = 'binary'; %'csv'; 'binary';% - - %Physics - electronProductionCut = 0.5; %in mm - 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_gamma = {'g4em-standard_opt4','g4h-phy_QGSP_BIC_HP','g4decay'}; - - %Geometry / World - worldMaterial = 'G4_AIR'; - - %filenames - outfilenames = struct( 'patientParam','matRad_cube.txt',... - 'patientCube','matRad_cube.dat'); - - infilenames = struct( 'geometry','TOPAS_matRad_geometry.txt.in',... - 'surfaceScorer','TOPAS_scorer_surfaceIC.txt.in',... - 'doseScorer','TOPAS_scorer_dose.txt.in',... - 'schneider_materialsOnly','TOPAS_SchneiderConverterMaterialsOnly.txt.in',... - 'schneider_full','TOPAS_SchneiderConverter.txt.in',... - 'beam_biGaussian','TOPAS_beamSetup_biGaussian.txt.in',... - 'beam_generic','TOPAS_beamSetup_generic.txt.in'... - ); - - - end - - properties (SetAccess = private) - thisFolder; - - MCparam; %Struct with parameters of last simulation to be saved to file - end - - methods - function obj = MatRad_TopasConfig() - %MatRad_MCsquareConfig Construct configuration Class for TOPAS - % Default execution paths are set here - - obj.thisFolder = fileparts(mfilename('fullpath')); - - obj.workingDir = [obj.thisFolder filesep 'MCrun' filesep]; - - %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) - obj.topasExecCommand = 'wsl export TOPAS_G4_DATA_DIR=~/G4Data; ~/topas/bin/topas'; - elseif ismac - obj.topasExecCommand = 'export TOPAS_G4_DATA_DIR=/Applications/G4Data; export QT_QPA_PLATFORM_PLUGIN_PATH=/Applications/topas/Frameworks; /Applications/topas/bin/topas'; - elseif isunix - obj.topasExecCommand = 'export TOPAS_G4_DATA_DIR=~/G4Data; ~/topas/bin/topas'; - else - obj.topasExecCommand = ''; - end - - %Read some default values from configuration class - matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class - - obj.materialConversion = matRad_cfg.propMC.topas_materialConversion; - obj.rsp_basematerial = matRad_cfg.propMC.topas_rsp_basematerial; - - end - - - - function writeAllFiles(obj,ct,pln,stf,topasBaseData,w) - matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class - - %Reset MCparam structure - obj.MCparam = struct(); - obj.MCparam.tallies = {}; - obj.MCparam.nbRuns = obj.numOfRuns; - obj.MCparam.simLabel = obj.label; - - - if ~exist(obj.workingDir,'dir') - mkdir(obj.workingDir); - matRad_cfg.dispInfo('Created TOPAS working directory %s\n',obj.workingDir); - end - - obj.MCparam.workingDir = obj.workingDir; - - matRad_cfg.dispInfo('Writing parameter files to %s\n',obj.workingDir); - - obj.writePatient(ct,pln); - obj.writeStfFields(ct,stf,topasBaseData,w); - - matRad_cfg.dispInfo('Successfully written TOPAS setup files!\n') - - obj.writeMCparam(); - end - - end - - %Private sub functions for writing (private so the state of the configuration - %can not be corrupted) - methods (Access = private) - - function writeRunHeader(obj,fID,fieldIx,runIx) - - fprintf(fID,'s:Sim/PlanLabel = "%s"\n',obj.label); - fprintf(fID,'s:Sim/ScoreLabel = "score_%s_field%d_run%d"\n',obj.label,fieldIx,runIx); - 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,'Ts/NumberOfThreads = %d\n',obj.numThreads); - fprintf(fID,'i:Ts/ShowHistoryCountAtInterval = %d\n',10^(floor(log10(1/obj.numOfRuns * obj.numHistories))-1)); - fprintf(fID,'\n'); - - - fprintf(fID,'s:Sim/DoseScorerOutputType = "%s"\n',obj.outputType); - fprintf(fID,'sv:Sim/DoseScorerReport = 1 "%s"\n',obj.scoreReportQuantity); - fprintf(fID,'\n'); - - %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,fieldIx) - matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class - - 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'); - - fprintf(fID,'d:Sim/ElectronProductionCut = %f mm\n',obj.electronProductionCut); - fprintf(fID,'s:Sim/WorldMaterial = "%s"\n',obj.worldMaterial); - fprintf(fID,'\n'); - - fprintf(fID,'includeFile = %s\n',obj.outfilenames.patientParam); - fprintf(fID,'\n'); - - fname = fullfile(obj.thisFolder,obj.infilenames.geometry); - matRad_cfg.dispInfo('Reading Geometry from %s\n',fname); - world = fileread(fname); - fprintf(fID,'%s\n\n',world); - end - - function writeScorers(obj,fID) - matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class - - obj.MCparam.outputType = obj.outputType; - - if obj.scoreDose - fname = fullfile(obj.thisFolder,obj.infilenames.doseScorer); - matRad_cfg.dispInfo('Reading Dose Scorer from %s\n',fname); - scorer = fileread(fname); - fprintf(fID,'%s\n',scorer); - obj.MCparam.tallies{end+1} = 'physicalDose'; - end - - if obj.addVolumeScorers - fileList = dir(fullfile(obj.thisFolder,'TOPAS_scorer_volume_*.in')); - for fileIx=1:length(fileList) - fname = fullfile(obj.thisFolder,fileList(fileIx).name); - matRad_cfg.dispInfo('Reading Volume Scorer from %s\n',fname); - scorer = fileread(fname); - fprintf(fID,'%s\n',scorer); - tallyLabel = regexprep(fileList(fileIx).name,'TOPAS_scorer_volume_',''); - tallyLabel = regexprep(tallyLabel,'.txt.in',''); - obj.MCparam.tallies{end+1} = tallyLabel; - end - end - - if obj.scoreTrackCount - fname = fullfile(obj.thisFolder,obj.infilenames.surfaceScorer); - matRad_cfg.dispInfo('Reading surface scorer from %s\n',fname); - scorer = fileread(fname); - fprintf(fID,'%s\n',scorer); - end - end - - function writeStfFields(obj,ct,stf,baseData,w) - matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class - - %snippet for future dij simulation - %if ~exist('w','var') - % numBixels = sum([stf(:).totalNumOfBixels)]; - % w = ones(numBixels,1); - %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 - - nozzleToAxisDistance = baseData.nozzleToIso; - - nParticlesTotalBixel = round(1e6*w); - %nParticlesTotal = sum(nParticlesTotalBixel); - maxParticlesBixel = 1e6*max(w(:)); - minParticlesBixel = round(max([obj.minRelWeight*maxParticlesBixel,1])); - - switch obj.modeHistories - case 'num' - obj.fracHistories = obj.numHistories ./ sum(nParticlesTotalBixel); - case 'frac' - obj.numHistories = sum(nParticlesTotalBixel); - otherwise - matRad_cfg.dispError('Invalid history setting!'); - end - - nParticlesTotal = 0; - - %Preread beam setup - switch obj.beamProfile - case 'biGaussian' - fname = fullfile(obj.thisFolder,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.thisFolder,obj.infilenames.beam_generic); - TOPAS_beamSetup = fileread(fname); - matRad_cfg.dispInfo('Reading ''%s'' Beam Characteristics from ''%s''\n',obj.beamProfile,fname); - end - - for beamIx = 1:length(stf) - - SAD = stf(beamIx).SAD; - - sourceToNozzleDistance = SAD - nozzleToAxisDistance; - - %Selection of base data given the energies - 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); - for i = 1:numel(focusIndex) - selectedData = [selectedData, baseData.monteCarloData(focusIndex(i), i)]; - end - energies = [selectedData.NominalEnergy]; - end - - %Get Range Shifters in field ifpresent - 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 - - - - %get beamlet properties for each bixel in the stf and write - %it into dataTOPAS - currentBixel = 1; - cutNumOfBixel = 0; - nBeamParticlesTotal(beamIx) = 0; - - collectBixelIdx = []; - - %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); - [~,ixTmp,~] = intersect(energies, bixelEnergy); - - voxel_x = -stf(beamIx).ray(rayIx).rayPos_bev(3); - voxel_y = stf(beamIx).ray(rayIx).rayPos_bev(1); - - dataTOPAS(cutNumOfBixel).posX = -1.*voxel_x; - dataTOPAS(cutNumOfBixel).posY = voxel_y; - - 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 - dataTOPAS(cutNumOfBixel).angleX = atan(dataTOPAS(cutNumOfBixel).posY / SAD); - dataTOPAS(cutNumOfBixel).angleY = atan(-dataTOPAS(cutNumOfBixel).posX ./ (SAD ./ cos(dataTOPAS(cutNumOfBixel).angleX))); - dataTOPAS(cutNumOfBixel).posX = (dataTOPAS(cutNumOfBixel).posX / SAD)*(SAD-nozzleToAxisDistance); - dataTOPAS(cutNumOfBixel).posY = (dataTOPAS(cutNumOfBixel).posY / SAD)*(SAD-nozzleToAxisDistance); - end - - 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).energySpread = selectedData(ixTmp).EnergySpread; - dataTOPAS(cutNumOfBixel).spotSize = selectedData(ixTmp).SpotSize1x; - dataTOPAS(cutNumOfBixel).divergence = selectedData(ixTmp).Divergence1x; - dataTOPAS(cutNumOfBixel).correlation = selectedData(ixTmp).Correlation1x; - dataTOPAS(cutNumOfBixel).focusFWHM = selectedData(ixTmp).FWHMatIso; - end - - %Add RangeShifterState - if ~isempty(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 - - nParticlesTotal = nParticlesTotal + nBeamParticlesTotal(beamIx); - - % discard data if the current has unphysical values - idx = find([dataTOPAS.current] < 1); - dataTOPAS(idx) = []; - cutNumOfBixel = length(dataTOPAS(:)); - - historyCount(beamIx) = uint32(obj.fracHistories * nBeamParticlesTotal(beamIx) / obj.numOfRuns); - - if historyCount < cutNumOfBixel || cutNumOfBixel == 0 - matRad_cfg.dispError('Insufficient number of histories!') - end - - while sum([dataTOPAS.current]) ~= historyCount(beamIx) - % Randomly pick an index with the weigth given by the current - idx = 1:length(dataTOPAS); - % Note: as of Octave version 5.1.0, histcounts is not yet implemented - % using histc instead for compatibility with MATLAB and Octave - %[~,~,R] = histcounts(rand(1),cumsum([0;double(transpose([dataTOPAS(:).current]))./double(sum([dataTOPAS(:).current]))])); - [~,R] = histc(rand(1),cumsum([0;double(transpose([dataTOPAS(:).current]))./double(sum([dataTOPAS(:).current]))])); - randIx = idx(R); - - if (sum([dataTOPAS(:).current]) > historyCount(beamIx)) - if dataTOPAS(randIx).current > 1 - dataTOPAS(randIx).current = dataTOPAS(randIx).current-1; - end - else - dataTOPAS(randIx).current = dataTOPAS(randIx).current+1; - end - end - - historyCount(beamIx) = historyCount(beamIx) * obj.numOfRuns; - - - %sort dataTOPAS according to energy - [~,ixSorted] = sort([dataTOPAS(:).energy]); - dataTOPAS = dataTOPAS(ixSorted); - - %write TOPAS data base file - fieldSetupFileName = sprintf('beamSetup_%s_field%d.txt',obj.label,beamIx); - fileID = fopen(fullfile(obj.workingDir,fieldSetupFileName),'w'); - obj.writeFieldHeader(fileID,beamIx); - - %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 = 16; - - modules = obj.modules_GenericIon; - %{ - case 'photons' %This modality is not yet used! - - fprintf(fileID,'s:Sim/ParticleName = "gamma"\n'); - fprintf(fileID,'u:Sim/ParticleMass = 0\n'); - - particleA = 0; - particleZ = 0; - - obj.modules_gamma; - %} - otherwise - matRad_cfg.dispError('Invalid radiation mode %s!',stf.radiationMode) - end - - fprintf(fileID,'d:Sim/GantryAngle = %f deg\n',stf(beamIx).gantryAngle); - fprintf(fileID,'d:Sim/CouchAngle = %f deg\n',stf(beamIx).couchAngle); - - 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,num2str(linspace(10,cutNumOfBixel*10,cutNumOfBixel))); - fprintf(fileID,' ms\n'); - %fprintf(fileID,'uv:Tf/Beam/Spot/Values = %i %s\n',cutNumOfBixel,num2str(collectBixelIdx)); - 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); - - fprintf(fileID,num2str(particleA*[dataTOPAS.energy])); %Transform total energy with atomic number - fprintf(fileID,' MeV\n'); - - switch obj.beamProfile - case 'biGaussian' - 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'); - - 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,num2str([dataTOPAS.spotSize])); - 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,num2str([dataTOPAS.divergence])); - 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,num2str([dataTOPAS.correlation])); - fprintf(fileID,'\n'); - - 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,num2str([dataTOPAS.spotSize])); - 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,num2str([dataTOPAS.divergence])); - 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,num2str([dataTOPAS.correlation])); - 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,num2str([dataTOPAS.focusFWHM])); - fprintf(fileID,' mm\n'); - end - - 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,num2str([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,num2str([dataTOPAS.angleY])); - fprintf(fileID,' rad\n'); - end - - 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,num2str([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,num2str([dataTOPAS.posY])); - fprintf(fileID,' mm\n'); - - 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,num2str([dataTOPAS.current])); - fprintf(fileID,'\n\n'); - - %Range shifter in/out - if ~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,num2str([dataTOPAS.raShiOut])); - fprintf(fileID,'\n\n'); - end - end - - - % NozzleAxialDistance - fprintf(fileID,'d:Ge/Nozzle/TransZ = -%f mm\n', nozzleToAxisDistance); - 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'); - end - - %Range Shifter Definition - for r = 1:numel(raShis) - obj.writeRangeShifter(fileID,raShis(r),sourceToNozzleDistance); - end - - - fprintf(fileID,'%s\n',TOPAS_beamSetup); - - %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,'# 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 - runFileName = sprintf('%s_field%d_run%d.txt',obj.label,beamIx,runIx); - fileID = fopen(fullfile(obj.workingDir,runFileName),'w'); - obj.writeRunHeader(fileID,beamIx,runIx); - fprintf(fileID,['i:Ts/Seed = ',num2str(runIx),'\n']); - fprintf(fileID,'includeFile = ./%s\n',fieldSetupFileName); - obj.writeScorers(fileID); - fclose(fileID); - end - end - - %Bookkeeping - obj.MCparam.nbParticlesTotal = nParticlesTotal; - 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 - % matRad_exportCtTOPAS(ct, path, material) - % - % input - % ct: ct cube - % runsPath: path where to save the files for MC simulation - % basematerial: base material to be scaled to corresponding RSP - % - % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class - - medium = obj.rsp_basematerial; - - if isequal(obj.arrayOrdering,'C') - matRad_cfg.dispInfo('Exporting cube in C ordering...\n') - permutation = [3 1 2]; - else - matRad_cfg.dispInfo('Exporting cube in FORTRAN ordering...\n') - permutation = [2 1 3]; - end - - cubeExport = obj.materialConversion; %'RSP'; %'HUSchneiderToWater'; - checkMaterial = false; - - paramFile = obj.outfilenames.patientParam; - dataFile = obj.outfilenames.patientCube; - - %Bookkeeping - obj.MCparam.imageCubeOrdering = obj.arrayOrdering; - obj.MCparam.imageCubeConversionType = obj.materialConversion; - obj.MCparam.imageCubeFile = obj.outfilenames.patientCube; - obj.MCparam.imageCubeDim = ct.cubeDim; - obj.MCparam.imageVoxelDimension = ct.resolution; - - outfile = fullfile(obj.workingDir, paramFile); - matRad_cfg.dispInfo('Writing data to %s\n',outfile) - fID = fopen(outfile,'w+'); - - switch cubeExport - case 'RSP' - rspHlut = matRad_loadHLUT(ct,pln); - min_HU = rspHlut(1,1); - max_HU = rspHlut(end,1); - - huCube = int32(permute(ct.cubeHU{1},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(['materials/' medium '.txt'],'r'); - while ~feof(fbase) - strLine = fgets(fbase); %# read line by line - fprintf(fID,'%s',strLine); - end - fclose(fbase); - unique_materials = []; - 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' - huCube = int32(permute(ct.cubeHU{1},permutation)); - rspHlut = matRad_loadHLUT(ct,pln); - - densityCorrection = []; - for i = 1:size(rspHlut,1)-1 - startVal = rspHlut(i,1); - endVal = rspHlut(i+1,1); - range = startVal:1:endVal-1; - densityCorrection(end+1:end+numel(range)) = matRad_interp1(rspHlut(:,1),rspHlut(:,2),range); - end - densityCorrection(end+1) = rspHlut(end,2); %add last missing value - - - %Write the Schneider Converter - fprintf(fID,'dv:Ge/Patient/DensityCorrection = %d %s %s\n',numel(densityCorrection),num2str(densityCorrection,'%f '),'g/cm3'); - fprintf(fID,'iv:Ge/Patient/SchneiderHounsfieldUnitSections = 2 %d %d\n',rspHlut(1,1),rspHlut(end,1)+1); - fprintf(fID,'uv:Ge/Patient/SchneiderDensityOffset = 1 1\n'); - fprintf(fID,'uv:Ge/Patient/SchneiderDensityFactor = 1 0\n'); - fprintf(fID,'uv:Ge/Patient/SchneiderDensityFactorOffset = 1 %d\n\n',-rspHlut(1,1)); - fprintf(fID,'i:Ge/Patient/MinImagingValue = %d\n',rspHlut(1,1)); - - %fprintf(h,'iv:Ge/Patient/SchneiderHUToMaterialSections = 2 %d %d\n',rspHlut(1,1),rspHlut(end,1)+1); - %fprintf(h,'sv:Ge/Patient/SchneiderElements = 2 "Hydrogen" "Oxygen"\n'); - %fprintf(h,'uv:Ge/Patient/SchneiderMaterialsWeight1 = 2 0.111894 0.888106\n'); - - %At least include air? - fprintf(fID,'iv:Ge/Patient/SchneiderHUToMaterialSections = 3 %d %d %d\n',rspHlut(1,1),rspHlut(2,1),rspHlut(end,1)+1); - fprintf(fID,'sv:Ge/Patient/SchneiderElements = 4 "Hydrogen" "Oxygen" "Nitrogen" "Carbon"\n'); - fprintf(fID,'uv:Ge/Patient/SchneiderMaterialsWeight1 = 4 0.0 0.23479269 0.76508170 0.00012561\n'); - fprintf(fID,'uv:Ge/Patient/SchneiderMaterialsWeight2 = 4 0.111894 0.888106 0.0 0.0\n'); - fprintf(fID,'dv:Ge/Patient/SchneiderMaterialMeanExcitationEnergy = 2 85.7 78.0 eV\n'); - - - %Write the Patient - 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'); - %fprintf(h,'includeFile = HUtoMaterialSchneiderWater.txt'); - fclose(fID); - - % write data - fID = fopen(fullfile(obj.workingDir, dataFile),'w'); - fwrite(fID,huCube,'short'); - fclose(fID); - cube = huCube; - - case 'HUSchneider' %Uses a pre-defined full Schneider Converter in TOPAS format - huCube = int32(permute(ct.cubeHU{1},permutation)); - - fname = fullfile(obj.thisFolder,obj.infilenames.schneider_full); - converter = fileread(fname); - matRad_cfg.dispInfo('Reading Schneider Conversion Definitino from ''%s''\n',fname); - - fprintf(fID,'%s\n\n',converter); - - 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,'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(h,'includeFile = HUtoMaterialSchneiderWater.txt'); - fclose(fID); - - fID = fopen(fullfile(obj.workingDir, dataFile),'w'); - fwrite(fID,huCube,'short'); - fclose(fID); - cube = huCube; - - case 'HUSchneiderDensity' %Uses a Schneider Material Look-Up from HU sections, but uses the density from a Look-up table - - - huCube = int32(permute(ct.cubeHU{1},permutation)); - densityHlut = matRad_loadHLUT(ct,pln); - - hu = -1000:2995; - densityCorrection = matRad_interp1(densityHlut(:,1),densityHlut(:,2),hu'); - - %densityCorrection(end+1) = densityHlut(end,2); %add last missing value - - %Write the Schneider Converter - fprintf(fID,'dv:Ge/Patient/DensityCorrection = %d %s %s\n',numel(densityCorrection),num2str(densityCorrection','%f '),'g/cm3'); - fprintf(fID,'iv:Ge/Patient/SchneiderHounsfieldUnitSections = 2 %d %d\n',hu(1),hu(end)+1); - fprintf(fID,'uv:Ge/Patient/SchneiderDensityOffset = 1 1\n'); - fprintf(fID,'uv:Ge/Patient/SchneiderDensityFactor = 1 0\n'); - fprintf(fID,'uv:Ge/Patient/SchneiderDensityFactorOffset = 1 %d\n\n',-hu(1)); - fprintf(fID,'i:Ge/Patient/MinImagingValue = %d\n',hu(1)); - - fname = fullfile(obj.thisFolder,obj.infilenames.schneider_materialsOnly); - converterMaterials = fileread(fname); - matRad_cfg.dispInfo('Reading Schneider Material Definition from ''TOPAS_SchneiderConverterMaterialsOnly.txt.in''\n'); - fprintf(fID,'%s\n\n',converterMaterials); - - %Write the Patient - 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'); - %fprintf(h,'includeFile = HUtoMaterialSchneiderWater.txt'); - fclose(fID); - - fID = fopen(fullfile(obj.workingDir, dataFile),'w'); - fwrite(fID,huCube,'short'); - fclose(fID); - cube = huCube; - - otherwise - matRad_cfg.dispError('Material Conversion rule "%s" not implemented (yet)!\n',cubeExport); - end - - obj.MCparam.imageCube = cube; - - - end - - function writeRangeShifter(obj,fID,rangeShifter,sourceToNozzleDistance) - - %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) - %write MCparam file with basic parameters - - MCparam = obj.MCparam; - save(fullfile(obj.workingDir,'MCparam.mat'),'MCparam','-v7'); - end - - end -end - diff --git a/topas/TOPAS_scorer_dose.txt.in b/topas/TOPAS_scorer_dose.txt.in deleted file mode 100644 index c160042b9..000000000 --- a/topas/TOPAS_scorer_dose.txt.in +++ /dev/null @@ -1,10 +0,0 @@ -# s:Sim/DoseScorerOutputType = "bin" -# sv:Sim/DoseScorerReport = 1 "Sum" -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" - diff --git a/topas/TOPAS_scorer_surfaceIC.txt.in b/topas/TOPAS_scorer_surfaceIC.txt.in deleted file mode 100644 index 0bf403581..000000000 --- a/topas/TOPAS_scorer_surfaceIC.txt.in +++ /dev/null @@ -1,8 +0,0 @@ -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/topas/TOPAS_beamSetup_biGaussian.txt.in b/topas/beamSetup/TOPAS_beamSetup_biGaussian.txt.in similarity index 82% rename from topas/TOPAS_beamSetup_biGaussian.txt.in rename to topas/beamSetup/TOPAS_beamSetup_biGaussian.txt.in index 9c73912ee..1a7fbdf73 100644 --- a/topas/TOPAS_beamSetup_biGaussian.txt.in +++ b/topas/beamSetup/TOPAS_beamSetup_biGaussian.txt.in @@ -1,3 +1,8 @@ +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 diff --git a/topas/TOPAS_beamSetup_generic.txt.in b/topas/beamSetup/TOPAS_beamSetup_generic.txt.in similarity index 85% rename from topas/TOPAS_beamSetup_generic.txt.in rename to topas/beamSetup/TOPAS_beamSetup_generic.txt.in index ab7b70806..da4458724 100644 --- a/topas/TOPAS_beamSetup_generic.txt.in +++ b/topas/beamSetup/TOPAS_beamSetup_generic.txt.in @@ -1,5 +1,9 @@ +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 MeV 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 diff --git a/topas/beamSetup/TOPAS_beamSetup_mlc.txt.in b/topas/beamSetup/TOPAS_beamSetup_mlc.txt.in new file mode 100644 index 000000000..16ee09185 --- /dev/null +++ b/topas/beamSetup/TOPAS_beamSetup_mlc.txt.in @@ -0,0 +1,9 @@ +s:Ge/MultiLeafCollimatorA/Type = "TsMultiLeafCollimator" +s:Ge/MultiLeafCollimatorA/Parent = "Nozzle" +s:Ge/MultiLeafCollimatorA/Material = "Aluminum" +d:Ge/MultiLeafCollimatorA/TransX = 0.0 cm +d:Ge/MultiLeafCollimatorA/TransY = 0.0 cm +d:Ge/MultiLeafCollimatorA/TransZ = 0.5 * Ge/MultiLeafCollimatorA/Thickness cm +d:Ge/MultiLeafCollimatorA/RotX = 0.0 deg +d:Ge/MultiLeafCollimatorA/RotY = 0.0 deg +d:Ge/MultiLeafCollimatorA/RotZ = 90.0 deg diff --git a/topas/beamSetup/TOPAS_beamSetup_phasespace.txt.in b/topas/beamSetup/TOPAS_beamSetup_phasespace.txt.in new file mode 100644 index 000000000..fd0da7aa0 --- /dev/null +++ b/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 = 5 +i:So/Phasespace/PreCheckShowParticleCountAtInterval = 100000 +b:So/Phasespace/PhaseSpacePreCheck = "False" diff --git a/topas/beamSetup/TOPAS_beamSetup_uniform.txt.in b/topas/beamSetup/TOPAS_beamSetup_uniform.txt.in new file mode 100644 index 000000000..9a36fac51 --- /dev/null +++ b/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/topas/beamSetup/TOPAS_beamSetup_virtualGaussian.txt.in b/topas/beamSetup/TOPAS_beamSetup_virtualGaussian.txt.in new file mode 100644 index 000000000..f777af0ba --- /dev/null +++ b/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/topas/beamSetup/phasespace/SIEMENS_PRIMUS_6.0_0.10_15.0x15.0.header b/topas/beamSetup/phasespace/SIEMENS_PRIMUS_6.0_0.10_15.0x15.0.header new file mode 100644 index 000000000..f52767d87 --- /dev/null +++ b/topas/beamSetup/phasespace/SIEMENS_PRIMUS_6.0_0.10_15.0x15.0.header @@ -0,0 +1,178 @@ +$IAEA_INDEX: +111 // IAEA website: www-nds.iaea.org/phsp/photon/ + +$TITLE: +Siemens PRIMUS 6MV photon beam 15.0 x 15.0 cm2 field + +$FILE_TYPE: +0 + +$CHECKSUM: +248573181 + +$RECORD_CONTENTS: + 1 // X is stored ? + 1 // Y is stored ? + 0 // Z is stored ? + 1 // U is stored ? + 1 // V is stored ? + 1 // W is stored ? + 1 // Weight is stored ? + 0 // Extra floats stored ? + 1 // Extra longs stored ? + 2 // LATCH EGS variable stored in the extralong array [ 0] + +$RECORD_CONSTANT: + 90.0000 // Constant Z + +$RECORD_LENGTH: +29 + +$BYTE_ORDER: +1234 + +$ORIG_HISTORIES: +2000000 + +$PARTICLES: +8571489 + +$PHOTONS: +8553819 + +$ELECTRONS: +17417 + +$POSITRONS: +253 + +$TRANSPORT_PARAMETERS: +Photon cross sections->PEGS4 +Photon transport cutoff(MeV)->0.010 +Pair angular sampling->KM +Pair cross sections->BH +Triplet production->Off +Bound Compton scattering->ON +Radiative Compton corrections->Off +Rayleigh scattering->ON +Atomic relaxations->ON +Photoelectron angular sampling->ON +Electron transport cutoff(MeV)->0.001 +Bremsstrahlung cross sections->NIST +Bremsstrahlung angular sampling->KM +Spin effects->On +Electron Impact Ionization->OFF +Maxium electron step in cm (SMAX)->0.1000E+11 +Maximum fractional energy loss/step(ESTEPE)->0.2500 +Maximum 1st elastic moment/step (XIMAX)->0.5000 +Boundary crossing algorithm->EXACT +Skin-depth for boundary crossing (MFP)->3.000 +Electron-step algorithm->PRESTA-II + +$MACHINE_TYPE: +SIEMENS PRIMUS 6MV + +$MONTE_CARLO_CODE_VERSION: +version V1 of BEAMnrc (Rev 1.78 last edited 2004-01-12 11:44:06-05) + +$GLOBAL_PHOTON_ENERGY_CUTOFF: + 0.010 +$GLOBAL_PARTICLE_ENERGY_CUTOFF: + 0.001 +$COORDINATE_SYSTEM_DESCRIPTION: +Cartesian Right-Handed, origin: z=0->bottom of the target, +x=y=0->central position of the target, z-axis parallel to beam direction, +incresing towards the patient, x-axis parallel to MLC leaf movement + +// OPTIONAL INFORMATION + +$BEAM_NAME: +6MV photon beam + +$FIELD_SIZE: +15.0 cm x 15.0 cm + +$NOMINAL_SSD: +100 cm + +$MC_INPUT_FILENAME: + +$VARIANCE_REDUCTION_TECHNIQUES: +Bremsstrahlung splitting->DIRECTIONAL +splitting field radius->12.000 cm +splitting field SSD->100.000 cm +splitting no. in field->1500 +Photon force interaction switch->OFF +Range rejection switch->ON +(IREJCT_GLOBAL = 2 => ECUTRR = ECUT(region) , ESAVE = 2.0 MeV) + +$INITIAL_SOURCE_DESCRIPTION: +INITIAL PARTICLES->Electrons +PARALLEL BEAM WITH 2-D GAUSSIAN X-Y DISTRIBUTION ON FRONT FACE at Z->0.0000 cm +BEAM FWHM->0.10 cm +INCIDENT ENERGY SPECTRUM READ FROM .specdata FILE +============================================================================================ +Energy spectrum: gaussian centred at 6 MeV, FWHM =0.84 MeV +30, 4.90, 1 +4.98, 1.4181119E-02 +5.05, 2.5347747E-02 +5.12, 4.3465299E-02 +5.20, 7.1502697E-02 +5.27, 1.1284451E-01 +5.34, 1.7085137E-01 +5.41, 2.4816358E-01 +5.49, 3.4581247E-01 +5.56, 4.6230390E-01 +5.63, 5.9292519E-01 +5.71, 7.2955606E-01 +5.78, 8.6120131E-01 +5.85, 9.7530087E-01 +5.93, 1.0596464E+00 +6.00, 1.1045161E+00 +6.07, 1.1045161E+00 +6.15, 1.0596464E+00 +6.22, 9.7530087E-01 +6.29, 8.6120131E-01 +6.37, 7.2955606E-01 +6.44, 5.9292519E-01 +6.51, 4.6230390E-01 +6.59, 3.4581247E-01 +6.66, 2.4816358E-01 +6.73, 1.7085137E-01 +6.80, 1.1284451E-01 +6.88, 7.1502697E-02 +6.95, 4.3465299E-02 +7.02, 2.5347747E-02 +7.10, 1.4181119E-02 +============================================================================================ +MEAN ENERGY->6.0 MeV +MINIMUM KINETIC ENERGY OF SPECTRUM->4.900 MeV +MAXIMUM KINETIC ENERGY OF SPECTRUM->7.100 MeV +NUMBER OF BINS IN SPECTRUM->30 + +$PUBLISHED_REFERENCE: +1->Automatic determination of primary electron beam parameters in Monte Carlo simulation. 2007, Med. Phys. 34, 1076-1084 +2->A new method for output factor determination in MLC shaped narrow beams. Physica Medica 23 (2007) 58-66 +3->Monte Carlo correction factors in small field absolute dosimetry: a feasibility study (Unpublished, available from the phsp web site) + +$AUTHORS: +J PENA, D M GONZALEZ-CASTANO, F SANCHEZ-DOBLADO, G HARTMANN + +$INSTITUTION: +UNIVERSIDAD DE SANTIAGO DE COMPOSTELA (SPAIN) + +$LINK_VALIDATION: + +$ADDITIONAL_NOTES: +This is IAEA header as defined in the technical +report IAEA(NDS)-0484, Vienna, 2006 + +$STATISTICAL_INFORMATION_PARTICLES: +// Weight Wmin Wmax Emin Emax Particle + 9007.17 1E-06 1 1.638 0.01016 7.034 PHOTONS + 40.394 0.001 1 1.611 0.1891 6.661 ELECTRONS + 0.253 0.001 0.001 1.595 0.1892 4.507 POSITRONS + +$STATISTICAL_INFORMATION_GEOMETRY: + -29.9604 29.9926 + -29.9533 29.9851 diff --git a/topas/matRad_TopasConfig.m b/topas/matRad_TopasConfig.m new file mode 100644 index 000000000..050bf1fc7 --- /dev/null +++ b/topas/matRad_TopasConfig.m @@ -0,0 +1,2108 @@ +classdef matRad_TopasConfig < handle + % matRad_TopasConfig 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/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 + % This parameter can be overwritten through MatRad_Config default parameters + numHistories = 1e6; %Number of histories to compute + + topasExecCommand; %Defaults will be set during construction according to TOPAS installation instructions and used system + + parallelRuns = false; %Starts runs in parallel + externalCalculation = false; %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'}; + bioParam = 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'); + + end + + properties% (SetAccess = private) + thisFolder; + + MCparam; %Struct with parameters of last simulation to be saved to file + end + + methods + function obj = matRad_TopasConfig() + matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class + + % Default execution paths are set here + obj.thisFolder = fileparts(mfilename('fullpath')); + obj.workingDir = [obj.thisFolder filesep 'MCrun' filesep]; + + % Set default histories from MatRad_Config + if isfield(matRad_cfg.propMC,'defaultNumHistories') + obj.numHistories = matRad_cfg.propMC.defaultNumHistories; + 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) + obj.topasExecCommand = 'wsl export TOPAS_G4_DATA_DIR=~/G4Data; ~/topas/bin/topas'; + elseif ismac + obj.topasExecCommand = 'export TOPAS_G4_DATA_DIR=/Applications/G4Data; export QT_QPA_PLATFORM_PLUGIN_PATH=/Applications/topas/Frameworks; /Applications/topas/bin/topas'; + elseif isunix + obj.topasExecCommand = 'export TOPAS_G4_DATA_DIR=~/G4Data; ~/topas/bin/topas'; + else + obj.topasExecCommand = ''; + end + end + + + function writeAllFiles(obj,ct,cst,pln,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 + % + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % + % 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/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(); %Instance of matRad configuration class + + % prepare biological parameters + if isfield(pln,'prescribedDose') + obj.bioParam.PrescribedDose = pln.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(pln.bioParam.AvailableAlphaXBetaX) + if ~isempty(strfind(lower(pln.bioParam.AvailableAlphaXBetaX{i,2}),'default')) + break + end + end + obj.bioParam.AlphaX = pln.bioParam.AvailableAlphaXBetaX{5,1}(1); + obj.bioParam.BetaX = pln.bioParam.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,pln); + + % 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,pln,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 + % + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % + % 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/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 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 pln.multScen.totNumScen ~= 1 + % resultGUI.accPhysicalDose = zeros(size(resultGUI.phaseDose{1})); + % for i = 1:pln.multScen.totNumScen + % resultGUI.accPhysicalDose = resultGUI.accPhysicalDose + resultGUI.phaseDose{i}; + % end + % end + end + + function resultGUI = 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') + % + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % + % 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/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 in TOPAS files in dij + dij = obj.readFiles(folder); + + % Postprocessing + resultGUI = obj.getResultGUI(dij); + + 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 + % + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % + % 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/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(); %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' + files = dir([folder filesep 'score_matRad_plan_field1_run1_*.csv']); + obj.MCparam.tallies = cellfun(@(x) extractBetween(x,'run1_','.csv') ,{files(:).name}); + case 'binary' + files = dir([folder filesep 'score_matRad_plan_field1_*run1_*.bin']); + obj.MCparam.tallies = cellfun(@(x) extractBetween(x,'run1_','.bin') ,{files(:).name}); + end + + obj.MCparam.tallies = unique(obj.MCparam.tallies); + talliesCut = replace(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) + + 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) + + 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.numHistories))-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']); + + %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) + + 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.thisFolder,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) + + 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.thisFolder,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.thisFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_RBE_MCN); + elseif ~isempty(strfind(lower(obj.scorer.RBE_model{i}),'wed')) + fname = fullfile(obj.thisFolder,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.thisFolder,filesep,obj.scorerFolder,filesep,obj.infilenames.Scorer_RBE_libamtrack); + elseif ~isempty(strfind(lower(obj.scorer.RBE_model{i}),'lem')) + fname = fullfile(obj.thisFolder,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.bioParam.PrescribedDose); + fprintf(fID,'b:Sc/SimultaneousExposure = %s\n',obj.bioParam.SimultaneousExposure); + fprintf(fID,'d:Sc/AlphaX = %.4f /Gy\n',obj.bioParam.AlphaX); + fprintf(fID,'d:Sc/BetaX = %.4f /Gy2\n',obj.bioParam.BetaX); + fprintf(fID,'d:Sc/AlphaBetaX = %.4f Gy\n',obj.bioParam.AlphaX/obj.bioParam.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.thisFolder,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.thisFolder,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.thisFolder,filesep,obj.scorerFolder,filesep,'TOPAS_scorer_volume_*.in')); + for fileIx=1:length(fileList) + fname = fullfile(obj.thisFolder,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.thisFolder,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,pln,w,baseData) + + matRad_cfg = MatRad_Config.instance(); %Instance of matRad configuration class + + isPhoton = false; + + switch pln.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,matRad_cfg.propMC.default_beamProfile_photons); + obj.beamProfile = matRad_cfg.propMC.default_beamProfile_photons; + 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,matRad_cfg.propMC.default_beamProfile_particles); + obj.beamProfile = matRad_cfg.propMC.default_beamProfile_particles; + 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.numHistories ./ sum(nParticlesTotalBixel); + case 'frac' + obj.numHistories = sum(nParticlesTotalBixel); + otherwise + matRad_cfg.dispError('Invalid history setting!'); + end + + nParticlesTotal = 0; + + %Preread beam setup + switch obj.beamProfile + case 'biGaussian' + fname = fullfile(obj.thisFolder,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.thisFolder,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.thisFolder,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.thisFolder,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.thisFolder,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); + for i = 1:numel(focusIndex) + 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).spotSize = selectedData(ixTmp).SpotSize1x; + dataTOPAS(cutNumOfBixel).divergence = selectedData(ixTmp).Divergence1x; + dataTOPAS(cutNumOfBixel).correlation = selectedData(ixTmp).Correlation1x; + 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', 1000 + ct.cubeDim(3)*ct.resolution.z);%Not sure if this is correct,100 cm is SSD and probably distance from surface to isocenter needs to be added + 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.spotSize]); + 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.divergence]); + 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.correlation]); + 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.spotSize]); + 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.divergence]); + 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.correlation]); + 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(pln.propStf, 'collimation') + % Use field width for now + fprintf(fileID,'d:So/PencilBeam/BeamPositionSpreadX = %d mm\n', pln.propStf.collimation.fieldWidth); + fprintf(fileID,'d:So/PencilBeam/BeamPositionSpreadY = %d mm\n', pln.propStf.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(pln.propStf, 'collimation') + % Use field width for now + fprintf(fileID,'d:So/PencilBeam/BeamPositionCutoffX = %d mm\n', pln.propStf.collimation.fieldWidth/2); + fprintf(fileID,'d:So/PencilBeam/BeamPositionCutoffY = %d mm\n', pln.propStf.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 + phaseSpaceFileName = 'SIEMENS_PRIMUS_6.0_0.10_15.0x15.0'; + if obj.externalCalculation + matRad_cfg.dispWarning(['External calculation and phaseSpace selected, manually place ' phaseSpaceFileName '.header and ' phaseSpaceFileName '.phsp into your simulation directory.']); + else + if length(dir([obj.thisFolder filesep 'beamSetup' filesep 'phasespace' filesep phaseSpaceFileName '*'])) < 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', phasespaceStr); + + 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') + fname = fullfile(obj.thisFolder,obj.infilenames.beam_mlc); + TOPAS_mlcSetup = fileread(fname); + + fprintf(fileID,'%s\n',TOPAS_mlcSetup); + % For now only for one ray + [numOfLeaves,leafTimes]=size([stf(beamIx).ray.shapes(:).leftLeafPos]); %there are #numOfLeaves leaves and #leafTimes times/shapes + leftLeafPos = [stf(beamIx).ray.shapes(:).leftLeafPos]; + rightLeafPos = [stf(beamIx).ray.shapes(:).rightLeafPos]; + % Set MLC paramters as in TOPAS example file https://topas.readthedocs.io/en/latest/parameters/geometry/specialized.html#multi-leaf-collimator + fprintf(fileID,'d:Ge/MultiLeafCollimatorA/MaximumLeafOpen = %f cm\n',15); + fprintf(fileID,'d:Ge/MultiLeafCollimatorA/Thickness = %f cm\n',15); + fprintf(fileID,'d:Ge/MultiLeafCollimatorA/Length = %f cm\n',6); + fprintf(fileID,'dv:Ge/MultiLeafCollimatorA/Widths = %i ', numOfLeaves); + fprintf(fileID,num2str(pln.propStf.collimation.leafWidth*ones(1,numOfLeaves),' % 2d')); + fprintf(fileID,' mm \n'); + fprintf(fileID,'dv:Ge/MultiLeafCollimatorA/XPlusLeavesOpen = %i ',numOfLeaves); + for i = 1:numOfLeaves + fprintf( fileID,'Tf/LeafXPlus%i/Value ',i); + end + fprintf(fileID,'mm \n'); + fprintf(fileID,'dv:Ge/MultiLeafCollimatorA/XMinusLeavesOpen = %i ',numOfLeaves); + for i = 1:numOfLeaves + fprintf( fileID,'Tf/LeafXMinus%i/Value ',i); + end + fprintf(fileID,'mm \n'); + + 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,num2str([1:leafTimes]*10,' % 2d')); + fprintf(fileID,' ms\n'); + fprintf(fileID,'dv:Tf/LeafXMinus%i/Values = %i ', i,leafTimes); + fprintf(fileID,num2str(leftLeafPos(i,:),' % 2d')); + 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,num2str([1:leafTimes]*10,' % 2d')); + fprintf(fileID,' ms\n'); + fprintf(fileID,'dv:Tf/LeafXPlus%i/Values = %i ', i,leafTimes); + fprintf(fileID,num2str(rightLeafPos(i,:),' % 2d')); + fprintf(fileID,' mm\n\n'); + end + 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 + % 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',strjoin(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,pln); + 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,pln); + + 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.thisFolder,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 + % 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.thisFolder,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.thisFolder,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) + + %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) + %write MCparam file with basic parameters + MCparam = obj.MCparam; + save(fullfile(obj.workingDir,'MCparam.mat'),'MCparam','-v7'); + end + + end +end + diff --git a/topas/matRad_readTopasData.m b/topas/matRad_readTopasData.m deleted file mode 100644 index 442e4d05b..000000000 --- a/topas/matRad_readTopasData.m +++ /dev/null @@ -1,62 +0,0 @@ -function [topasCube] = matRad_readTopasData(folder) -%UNTITLED2 Summary of this function goes here -% Detailed explanation goes here -load([folder filesep 'MCparam.mat']); - -%Normalize with histories and particles/weight -correctionFactor = 1e6 / double(MCparam.nbHistoriesTotal); %double(MCparam.nbParticlesTotal) / double(MCparam.nbHistoriesTotal); - -cubeDim = MCparam.imageCubeDim; - -for t = 1:length(MCparam.tallies) - tname = MCparam.tallies{t}; - topasTally = zeros(cubeDim(1),cubeDim(2),cubeDim(3)); - - for f = 1:MCparam.nbFields - topasSum = zeros(cubeDim(1),cubeDim(2),cubeDim(3)); - for k = 1:MCparam.nbRuns - genFileName = sprintf('score_%s_field%d_run%d_%s',MCparam.simLabel,f,k,tname); - switch MCparam.outputType - case 'csv' - genFullFile = fullfile(folder,[genFileName '.csv']); - data = readCsvData(genFullFile,cubeDim); - case 'binary' - genFullFile = fullfile(folder,[genFileName '.bin']); - data = readBinData(genFullFile,cubeDim); - otherwise - error('Not implemented!'); - end - topasSum = topasSum + data; - end - - topasSum = correctionFactor.*topasSum; - - % Tally per field - topasCube.([tname '_beam' num2str(f)]) = topasSum; - - % Accumulate over the fields - topasTally = topasTally + topasSum; - end - - topasCube.(tname) = topasTally; -end - -end - -function data = readCsvData(csvFile,cubeDim) - data = zeros(cubeDim(2),cubeDim(1),cubeDim(3)); - fID = fopen(csvFile,'r'); - dataCsv = textscan(fID,'%d %d %d %f','Delimiter',',','CommentStyle','#','CollectOutput',true); - fclose(fID); - ix = sub2ind([cubeDim(1) cubeDim(2) cubeDim(3)],dataCsv{1}(:,2)+1,dataCsv{1}(:,1)+1,dataCsv{1}(:,3)+1); - data(ix) = dataCsv{2}; -end - -function data = readBinData(binFile,cubeDim) - fID = fopen(binFile); - data = fread(fID,prod(cubeDim),'double'); - fclose(fID); - data = reshape(data,cubeDim(2),cubeDim(1),cubeDim(3)); - data = permute(data,[2 1 3]); -end - diff --git a/topas/TOPAS_SchneiderConverter.txt.in b/topas/materialConverter/TOPAS_SchneiderConverter.txt.in similarity index 61% rename from topas/TOPAS_SchneiderConverter.txt.in rename to topas/materialConverter/TOPAS_SchneiderConverter.txt.in index 79737d4bb..dcb5a4af1 100644 --- a/topas/TOPAS_SchneiderConverter.txt.in +++ b/topas/materialConverter/TOPAS_SchneiderConverter.txt.in @@ -1,2 +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/topas/materials/G4_WATER.txt b/topas/materialConverter/definedMaterials/G4_WATER.txt similarity index 50% rename from topas/materials/G4_WATER.txt rename to topas/materialConverter/definedMaterials/G4_WATER.txt index d3f5a12fa..8b1378917 100644 --- a/topas/materials/G4_WATER.txt +++ b/topas/materialConverter/definedMaterials/G4_WATER.txt @@ -1 +1 @@ - + diff --git a/topas/TOPAS_SchneiderConverterMaterialsOnly.txt.in b/topas/materialConverter/definedMaterials/MCsquare.txt.in similarity index 80% rename from topas/TOPAS_SchneiderConverterMaterialsOnly.txt.in rename to topas/materialConverter/definedMaterials/MCsquare.txt.in index 678c82e6a..6deb183f1 100644 --- a/topas/TOPAS_SchneiderConverterMaterialsOnly.txt.in +++ b/topas/materialConverter/definedMaterials/MCsquare.txt.in @@ -1,8 +1,7 @@ -# 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. +# -- 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" -iv:Ge/Patient/SchneiderHUToMaterialSections = 26 -1000 -950 -120 -82 -52 -22 8 19 80 120 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 2995 2996 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.104 0.106 0.031 0.757 0.002 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 @@ -14,7 +13,7 @@ uv:Ge/Patient/SchneiderMaterialsWeight10 = 7 0.095 0.457 0.025 0.357 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.071 0.336 0.032 0.3885 0.054 0.1175 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 diff --git a/topas/materials/Water.txt b/topas/materialConverter/definedMaterials/Water.txt similarity index 97% rename from topas/materials/Water.txt rename to topas/materialConverter/definedMaterials/Water.txt index 1e8e77ce9..5a7981061 100644 --- a/topas/materials/Water.txt +++ b/topas/materialConverter/definedMaterials/Water.txt @@ -1,5 +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 +# 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/topas/materialConverter/definedMaterials/advanced.txt.in b/topas/materialConverter/definedMaterials/advanced.txt.in new file mode 100644 index 000000000..247140abd --- /dev/null +++ b/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/topas/materialConverter/definedMaterials/default.txt.in b/topas/materialConverter/definedMaterials/default.txt.in new file mode 100644 index 000000000..f24940bf2 --- /dev/null +++ b/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/topas/materialConverter/densityCorrection/Schneider_TOPAS.dat b/topas/materialConverter/densityCorrection/Schneider_TOPAS.dat new file mode 100644 index 000000000..658600b16 --- /dev/null +++ b/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/topas/materialConverter/densityCorrection/Schneider_matRad.dat b/topas/materialConverter/densityCorrection/Schneider_matRad.dat new file mode 100644 index 000000000..3529f1d90 --- /dev/null +++ b/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/topas/materialConverter/densityCorrection/readme.md b/topas/materialConverter/densityCorrection/readme.md new file mode 100644 index 000000000..71bceeade --- /dev/null +++ b/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/topas/TOPAS_generic_RS_PMMA.txt.in b/topas/rangeShifter/TOPAS_generic_RS_PMMA.txt.in similarity index 100% rename from topas/TOPAS_generic_RS_PMMA.txt.in rename to topas/rangeShifter/TOPAS_generic_RS_PMMA.txt.in diff --git a/topas/scorer/TOPAS_scorer_doseRBE_LEM1.txt.in b/topas/scorer/TOPAS_scorer_doseRBE_LEM1.txt.in new file mode 100644 index 000000000..f0426a8fd --- /dev/null +++ b/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/topas/scorer/TOPAS_scorer_doseRBE_McNamara.txt.in b/topas/scorer/TOPAS_scorer_doseRBE_McNamara.txt.in new file mode 100644 index 000000000..1a2eba6c9 --- /dev/null +++ b/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/topas/scorer/TOPAS_scorer_doseRBE_Wedenberg.txt.in b/topas/scorer/TOPAS_scorer_doseRBE_Wedenberg.txt.in new file mode 100644 index 000000000..fdd2bb861 --- /dev/null +++ b/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/topas/scorer/TOPAS_scorer_doseRBE_libamtrack.txt.in b/topas/scorer/TOPAS_scorer_doseRBE_libamtrack.txt.in new file mode 100644 index 000000000..76e83e4f5 --- /dev/null +++ b/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/topas/scorer/TOPAS_scorer_doseToMedium.txt.in b/topas/scorer/TOPAS_scorer_doseToMedium.txt.in new file mode 100644 index 000000000..5cbd25532 --- /dev/null +++ b/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/topas/scorer/TOPAS_scorer_doseToWater.txt.in b/topas/scorer/TOPAS_scorer_doseToWater.txt.in new file mode 100644 index 000000000..b4f827118 --- /dev/null +++ b/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/topas/scorer/TOPAS_scorer_surfaceIC.txt.in b/topas/scorer/TOPAS_scorer_surfaceIC.txt.in new file mode 100644 index 000000000..7404a2a2d --- /dev/null +++ b/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/topas/scorer/TOPAS_subscorer_LET.txt.in b/topas/scorer/TOPAS_subscorer_LET.txt.in new file mode 100644 index 000000000..8b6da0cac --- /dev/null +++ b/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/topas/TOPAS_matRad_geometry.txt.in b/topas/world/TOPAS_matRad_geometry.txt.in similarity index 82% rename from topas/TOPAS_matRad_geometry.txt.in rename to topas/world/TOPAS_matRad_geometry.txt.in index db5fb19ea..4eb3c522e 100644 --- a/topas/TOPAS_matRad_geometry.txt.in +++ b/topas/world/TOPAS_matRad_geometry.txt.in @@ -4,11 +4,6 @@ d:Ge/World/HLY=200.0 cm d:Ge/World/HLZ=200.0 cm b:Ge/World/Invisible = "TRUE" -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:Ge/Nozzle/Parent = "Isocenter" s:Ge/Nozzle/Type = "Group"