diff --git a/examples/matRad_example8_photonsVMAT.m b/examples/matRad_example8_photonsVMAT.m
new file mode 100644
index 000000000..9835232b7
--- /dev/null
+++ b/examples/matRad_example8_photonsVMAT.m
@@ -0,0 +1,125 @@
+%% Example Photon Treatment Plan with VMAT direct aperture optimization
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2017 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%
+% In this example we will show
+% (i) how to load patient data into matRad
+% (ii) how to input necessary parameters in the pln structure
+% (iii) how to setup a photon dose calculation
+% (iv) how to inversely optimize fluence directly from command window in MatLab.
+% (v) how to apply a sequencing algorithm
+% (vi) how to run a VMAT direct aperture optimization
+% (vii) how to visually and quantitatively evaluate the result
+
+%% Patient Data Import
+% Let's begin with a clear Matlab environment and import the TG119 patient
+% into your workspace
+matRad_rc
+
+load TG119.mat
+
+%% Treatment Plan
+% The next step is to define your treatment plan labeled as 'pln'. This
+% structure requires input from the treatment planner and defines
+% the most important cornerstones of your treatment plan.
+
+% meta information for treatment plan
+pln.numOfFractions = 30;
+pln.radiationMode = 'photons'; % either photons / protons / helium / carbon / brachy
+pln.machine = 'Generic'; % generic for RT / LDR or HDR for BT
+
+pln.bioModel = 'none'; % none: for photons, protons, carbon, brachy % constRBE: constant RBE for photons and protons
+ % MCN: McNamara-variable RBE model for protons % WED: Wedenberg-variable RBE model for protons
+ % LEM: Local Effect Model for carbon ions % HEL: data-driven RBE parametrization for helium
+
+pln.multScen = 'nomScen'; % scenario creation type 'nomScen' 'wcScen' 'impScen' 'rndScen'
+
+% beam geometry settings
+pln.propStf.bixelWidth = 5; % [mm] / also corresponds to lateral spot spacing for particles
+pln.propStf.maxGantryAngleSpacing = 30; % [°] / max gantry angle spacing for dose calculation
+pln.propStf.maxDAOGantryAngleSpacing = 60; % [°] / max gantry angle spacing for DAO
+pln.propStf.maxFMOGantryAngleSpacing = 180; % [°] / max gantry angle spacing for FMO
+pln.propStf.startingAngle = -180; % [°] / starting angle for VMAT
+pln.propStf.finishingAngle = 180; % [°] / finishing angle for VMAT
+pln.propStf.couchAngle = 0; % [°]
+pln.propStf.isoCenter = matRad_getIsoCenter(cst,ct,0);
+pln.propStf.generator = 'PhotonVMAT';
+pln.propStf.continuousAperture = false;
+
+% dose calculation settings
+pln.propDoseCalc.doseGrid.resolution.x = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.y = 5; % [mm]
+pln.propDoseCalc.doseGrid.resolution.z = 5; % [mm]
+
+% sequencing settings
+pln.propSeq.runSequencing = true; % true: run sequencing, false: don't / will be ignored for particles and also triggered by runDAO below
+pln.propSeq.sequencer = 'siochi';
+pln.propSeq.numLevels = 7;
+
+% optimization settings
+pln.propOpt.quantityOpt = 'physicalDose'; % Quantity to optimizer (could also be RBExDose, BED, effect)
+pln.propOpt.optimizer = 'IPOPT'; % We can also utilize 'fmincon' from Matlab's optimization toolbox
+pln.propOpt.runDAO = true; % 1/true: run DAO, 0/false: don't / will be ignored for particles
+pln.propOpt.runVMAT = true;
+pln.propOpt.preconditioner = true;
+
+%pln.propOpt.VMAToptions.machineConstraintFile = [pln.radiationMode '_' pln.machine];
+
+%% Generate Beam Geometry STF
+stf = matRad_generateStf(ct,cst,pln);
+
+%% Dose Calculation
+% Lets generate dosimetric information by pre-computing dose influence
+% matrices for unit beamlet intensities. Having dose influences available
+% allows for subsequent inverse optimization.
+dij = matRad_calcDoseInfluence(ct, cst, stf, pln);
+
+%% Inverse Planning for IMRT
+% The goal of the fluence optimization is to find a set of beamlet weights
+% which yield the best possible dose distribution according to the
+% predefined clinical objectives and constraints underlying the radiation
+% treatment. In VMAT, FMO is done only at the angles in the
+% FMOGantryAngles set. Once the optimization has finished, trigger once the GUI to
+% visualize the optimized dose cubes.
+resultGUI = matRad_fluenceOptimization(dij,cst,pln,stf);
+matRadGUI;
+
+%% Sequencing
+% This is a multileaf collimator leaf sequencing algorithm that is used in
+% order to modulate the intensity of the beams with multiple static
+% segments, so that translates each intensity map into a set of deliverable
+% aperture shapes. The fluence map at each angle in the initGantryAngles
+% set is sequenced, with the resulting apertures spread to neighbouring
+% angles from the optGantryAngles set.
+resultGUI = matRad_sequencing(resultGUI,stf,dij,pln);
+
+%% DAO - Direct Aperture Optimization
+% The Direct Aperture Optimization is an optimization approach where we
+% directly optimize aperture shapes and weights at the angles in the
+% optGantryAngles set. The gantry angle speed, leaf speed, and MU rate are
+% constrained by the min and max values specified by the user.
+resultGUI = matRad_directApertureOptimization(dij,cst,resultGUI.apertureInfo,resultGUI,pln);
+
+%% Aperture visualization
+% Use a matrad function to visualize the resulting aperture shapes
+matRad_visApertureInfo(resultGUI.apertureInfo);
+
+%% Indicator Calculation and display of DVH and QI
+resultGUI = matRad_planAnalysis(resultGUI,ct,cst,stf,pln);
+
+%% Calculate delivery metrics
+
+resultGUI = matRad_calcDeliveryMetrics(resultGUI,pln,stf);
+
diff --git a/matRad/MatRad_Config.m b/matRad/MatRad_Config.m
index 06e12316b..bbf9ae9b9 100644
--- a/matRad/MatRad_Config.m
+++ b/matRad/MatRad_Config.m
@@ -249,6 +249,7 @@ function setDefaultProperties(obj)
%Sequencing Options
obj.defaults.propSeq.sequencer = 'siochi';
+ obj.defaults.propSeq.numLevels = 5;
@@ -324,12 +325,12 @@ function setDefaultGUIProperties(obj)
if ispc
light = logical(winqueryreg('HKEY_CURRENT_USER','Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize','AppsUseLightTheme'));
elseif ismac
- out = system('defaults read -g AppleInterfaceStyle');
- if ~strcmp(out,'Dark')
+ [~,out] = system('defaults read -g AppleInterfaceStyle');
+ if ~strcmp(out(1:end-1),'Dark')
light = true;
end
else
- out = system('gsettings get org.gnome.desktop.interface color-scheme');
+ [~,out] = system('gsettings get org.gnome.desktop.interface color-scheme');
if strcmp(out,'prefer-light')
light = true;
end
diff --git a/matRad/basedata/photons_Generic.mat b/matRad/basedata/photons_Generic.mat
index 7f0b7ee35..79b003550 100644
Binary files a/matRad/basedata/photons_Generic.mat and b/matRad/basedata/photons_Generic.mat differ
diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m
index c591699f2..88a3d8b20 100644
--- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m
+++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m
@@ -47,7 +47,16 @@
% set necessary steering information
obj.stf(i).gantryAngle = UniqueComb(i,1);
obj.stf(i).couchAngle = UniqueComb(i,2);
- obj.stf(i).isoCenter = obj.pln.propStf.isoCenter(i,:);
+
+ %Handle possibility of multiple isocenters
+ if size(obj.pln.propStf.isoCenter,1) == 1
+ obj.stf(i).isoCenter = obj.pln.propStf.isoCenter;
+ elseif size(pln.propStf.isoCenter,1) == obj.pln.propStf.numOfBeams
+ obj.stf(i).isoCenter = obj.pln.propStf.isoCenter(i,:);
+ else
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispError('Invalid number of isocenters - should either be one or as many as beams!');
+ end
% bixelWidth = 'field' as keyword for whole field dose calc
obj.stf(i).bixelWidth = 'field';
diff --git a/matRad/doseCalc/+DoseEngines/matRad_PhotonPencilBeamSVDEngine.m b/matRad/doseCalc/+DoseEngines/matRad_PhotonPencilBeamSVDEngine.m
index dd273c235..adb8fde38 100644
--- a/matRad/doseCalc/+DoseEngines/matRad_PhotonPencilBeamSVDEngine.m
+++ b/matRad/doseCalc/+DoseEngines/matRad_PhotonPencilBeamSVDEngine.m
@@ -159,6 +159,17 @@ function setDefaults(this)
matRad_cfg.dispWarning('Kernel Cut-Off ''%f mm'' cannot be smaller than geometric lateral cutoff ''%f mm''. Using ''%f mm''!',this.kernelCutOff,this.geometricLateralCutOff,this.geometricLateralCutOff);
this.kernelCutOff = this.geometricLateralCutOff;
end
+
+ % TODO: calculate and add weightToMU for the generic photon
+ % machine. Typical calibration: 100 cGy/100 MU in a 10x10 cm^2
+ % field, 100 cm SSD, depth of dose maximum for the given beam
+ % quality.
+ if isfield(this.machine.data,'weightToMU')
+ dij.weightToMU = this.machine.data.weightToMU;
+ else
+ dij.weightToMU = 100;
+ matRad_cfg.dispWarning('photon machine file does not contain weight to MU scaling factor. Assuming %.1f.',dij.weightToMU);
+ end
%% kernel convolution
% set up convolution grid
diff --git a/matRad/doseCalc/matRad_calcPhotonDoseVmc.m b/matRad/doseCalc/matRad_calcPhotonDoseVmc.m
new file mode 100644
index 000000000..65543d8ba
--- /dev/null
+++ b/matRad/doseCalc/matRad_calcPhotonDoseVmc.m
@@ -0,0 +1,343 @@
+function dij = matRad_calcPhotonDoseVmc(ct,stf,pln,cst,calcDoseDirect)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad vmc++ photon dose calculation wrapper
+%
+% call
+% dij = matRad_calcPhotonDoseVmc(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: boolian switch to bypass dose influence matrix
+% computation and directly calculate dose; only makes
+% sense in combination with matRad_calcDoseDirect.m%
+% output
+% dij: matRad dij struct
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dij.radiationMode = pln.radiationMode;
+
+% default: dose influence matrix computation
+if ~exist('calcDoseDirect','var')
+ calcDoseDirect = false;
+end
+
+% set output level. 0 = no vmc specific output. 1 = print to matlab cmd.
+% 2 = open in terminal(s)
+verbose = 0;
+
+if ~isdeployed % only if _not_ running as standalone
+ % add path for optimization functions
+ matRadRootDir = fileparts(mfilename('fullpath'));
+ addpath(fullfile(matRadRootDir,'vmc++'))
+end
+
+% meta information for dij
+dij.numOfBeams = pln.propStf.numOfBeams;
+dij.numOfVoxels = prod(ct.cubeDim);
+dij.resolution = ct.resolution;
+dij.dimensions = ct.cubeDim;
+dij.numOfScenarios = 1;
+dij.numOfRaysPerBeam = [stf(:).numOfRays];
+dij.weightToMU = 100;
+dij.scaleFactor = 1;
+dij.totalNumOfBixels = sum([stf(:).totalNumOfBixels]);
+dij.totalNumOfRays = sum(dij.numOfRaysPerBeam);
+
+% check if full dose influence data is required
+if calcDoseDirect
+ numOfColumnsDij = length(stf);
+ numOfBixelsContainer = 1;
+else
+ numOfColumnsDij = dij.totalNumOfBixels;
+ numOfBixelsContainer = ceil(dij.totalNumOfBixels/10);
+end
+
+% set up arrays for book keeping
+dij.bixelNum = NaN*ones(numOfColumnsDij,1);
+dij.rayNum = NaN*ones(numOfColumnsDij,1);
+dij.beamNum = NaN*ones(numOfColumnsDij,1);
+
+bixelNum = NaN*ones(dij.totalNumOfBixels,1);
+rayNum = NaN*ones(dij.totalNumOfBixels,1);
+beamNum = NaN*ones(dij.totalNumOfBixels,1);
+
+doseTmpContainer = cell(numOfBixelsContainer,dij.numOfScenarios);
+doseTmpContainerError = cell(numOfBixelsContainer,dij.numOfScenarios);
+
+% Allocate space for dij.physicalDose sparse matrix
+for i = 1:dij.numOfScenarios
+ dij.physicalDose{i} = spalloc(prod(ct.cubeDim),numOfColumnsDij,1);
+ dij.physicalDoseError{i} = spalloc(prod(ct.cubeDim),numOfColumnsDij,1);
+end
+
+% set environment variables for vmc++
+cd(fileparts(mfilename('fullpath')))
+
+if exist(['vmc++' filesep 'bin'],'dir') ~= 7
+ error(['Could not locate vmc++ environment. ' ...
+ 'Please provide the files in the correct folder structure at matRadroot' filesep 'vmc++.']);
+else
+ VMCPath = fullfile(pwd , 'vmc++');
+ switch pln.propDoseCalc.vmcOptions.version
+ case 'Carleton'
+ runsPath = fullfile(VMCPath, 'run');
+ case 'dkfz'
+ runsPath = fullfile(VMCPath, 'runs');
+ end
+ phantomPath = fullfile(runsPath, 'phantoms');
+
+ setenv('vmc_home',VMCPath);
+ setenv('vmc_dir',runsPath);
+ setenv('xvmc_dir',VMCPath);
+
+ if isunix
+ system(['chmod a+x ' VMCPath filesep 'bin' filesep 'vmc_Linux.exe']);
+ end
+
+end
+
+% set consistent random seed (enables reproducibility)
+rng(0);
+
+% get default vmc options
+VmcOptions = matRad_vmcOptions(pln,ct);
+
+% export CT cube as binary file for vmc++
+matRad_exportCtVmc(ct, fullfile(phantomPath, 'matRad_CT.ct'));
+
+% take only voxels inside patient
+V = [cst{:,4}];
+V = unique(vertcat(V{:}));
+
+writeCounter = 0;
+readCounter = 0;
+maxNumOfParMCSim = 0;
+
+% initialize waitbar
+figureWait = waitbar(0,'calculate dose influence matrix for photons (vmc++)...');
+% show busy state
+set(figureWait,'pointer','watch');
+
+fprintf('matRad: VMC++ photon dose calculation...\n');
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+for i = 1:dij.numOfBeams % loop over all beams
+
+ fprintf('Beam %d of %d ...',i,dij.numOfBeams);
+
+ % remember beam and bixel number
+ if calcDoseDirect
+ dij.beamNum(i) = i;
+ dij.rayNum(i) = i;
+ dij.bixelNum(i) = i;
+ end
+
+ if strcmp(pln.propDoseCalc.vmcOptions.source,'phsp')
+ % set angle-specific vmc++ parameters
+
+ % phsp starts off pointed in the +z direction, with source at -z
+ % phsp source gets translated, then rotated (-z, +y, -x) around
+ % 0, then pushed to isocenter
+
+ % correct for the source to collimator distance and change units mm -> cm
+ translation = stf(i).isoCenter/10+[0 0 pln.propDoseCalc.vmcOptions.SCD + stf(i).sourcePoint_bev(2)]/10;
+
+ % enter in isocentre
+ isocenter = stf(i).isoCenter/10;
+
+ % determine vmc++ rotation angles from gantry and couch
+ % angles
+ angles = matRad_matRad2vmcSourceAngles(stf(i).gantryAngle,stf(i).couchAngle);
+
+ % set vmc++ parameters
+ VmcOptions.source.translation = translation;
+ VmcOptions.source.isocenter = isocenter;
+ VmcOptions.source.angles = angles;
+ end
+
+ % use beam-specific CT name
+ VmcOptions.geometry.XyzGeometry.CtFile = strrep(fullfile(runsPath,'phantoms','matRad_CT.ct'),'\','/'); % path of density matrix (only needed if input method is 'CT-PHANTOM')
+
+ for j = 1:stf(i).numOfRays % loop over all rays / for photons we only have one bixel per ray!
+
+ writeCounter = writeCounter + 1;
+
+ % create different seeds for every bixel
+ VmcOptions.McControl.rngSeeds = [randi(30000),randi(30000)];
+
+ % remember beam and bixel number
+ if ~calcDoseDirect
+ dij.beamNum(writeCounter) = i;
+ dij.rayNum(writeCounter) = j;
+ dij.bixelNum(writeCounter) = j;
+ end
+ beamNum(writeCounter) = i;
+ rayNum(writeCounter) = j;
+ bixelNum(writeCounter) = j;
+
+ % set ray specific vmc++ parameters
+ switch pln.propDoseCalc.vmcOptions.source
+ case 'beamlet'
+ % a) change coordinate system (Isocenter cs-> physical cs) and units mm -> cm
+ rayCorner1 = (stf(i).ray(j).rayCorners_SCD(1,:) + stf(i).isoCenter)/10;
+ rayCorner2 = (stf(i).ray(j).rayCorners_SCD(2,:) + stf(i).isoCenter)/10;
+ rayCorner3 = (stf(i).ray(j).rayCorners_SCD(3,:) + stf(i).isoCenter)/10; %vmc needs only three corners (counter-clockwise)
+ beamSource = (stf(i).sourcePoint + stf(i).isoCenter)/10;
+
+ % b) swap x and y (CT-standard = [y,x,z])
+ rayCorner1 = rayCorner1([2,1,3]);
+ rayCorner2 = rayCorner2([2,1,3]);
+ rayCorner3 = rayCorner3([2,1,3]);
+ beamSource = beamSource([2,1,3]);
+
+ % c) set vmc++ parameters
+ VmcOptions.source.monoEnergy = stf(i).ray(j).energy; % photon energy
+ %VmcOptions.source.monoEnergy = [] ; % use photon spectrum
+ VmcOptions.source.beamletEdges = [rayCorner1,rayCorner2,rayCorner3]; % counter-clockwise beamlet edges
+ VmcOptions.source.virtualPointSourcePosition = beamSource; % virtual beam source position
+
+ case 'phsp'
+ % use ray-specific file name for the phsp source (bixelized
+ % phsp)
+ VmcOptions.source.file_name = strrep(stf(i).ray(j).phspFileName,'\','/');
+ end
+
+
+ %% create input file with vmc++ parameters
+ outfile = ['MCpencilbeam_temp_',num2str(mod(writeCounter-1,VmcOptions.run.numOfParMCSim)+1)];
+ matRad_createVmcInput(VmcOptions,fullfile(runsPath, [outfile,'.vmc']));
+
+ % parallelization: only run this block for every numOfParallelMCSimulations!!!
+ if mod(writeCounter,VmcOptions.run.numOfParMCSim) == 0 || writeCounter == dij.totalNumOfBixels
+
+ % create batch file (enables parallel processes)
+ if writeCounter == dij.totalNumOfBixels && mod(writeCounter,VmcOptions.run.numOfParMCSim) ~= 0
+ currNumOfParMCSim = mod(writeCounter,VmcOptions.run.numOfParMCSim);
+ else
+ currNumOfParMCSim = VmcOptions.run.numOfParMCSim;
+ end
+ matRad_createVmcBatchFile(currNumOfParMCSim,fullfile(VMCPath,'run_parallel_simulations.bat'),verbose);
+
+ % save max number of executed parallel simulations
+ if currNumOfParMCSim > maxNumOfParMCSim
+ maxNumOfParMCSim = currNumOfParMCSim;
+ end
+
+ %% perform vmc++ simulation
+ current = pwd;
+ cd(VMCPath);
+ if verbose > 0 % only show output if verbose level > 0
+ dos('run_parallel_simulations.bat');
+ fprintf(['Completed ' num2str(writeCounter) ' of ' num2str(dij.totalNumOfBixels) ' beamlets...\n']);
+ else
+ [dummyOut1,dummyOut2] = dos('run_parallel_simulations.bat'); % supress output by assigning dummy output arguments
+ end
+ cd(current);
+
+ for k = 1:currNumOfParMCSim
+ readCounter = readCounter+1;
+
+ % update waitbar
+ waitbar(writeCounter/dij.totalNumOfBixels);
+
+ %% import calculated dose
+ idx = regexp(outfile,'_');
+ switch pln.propDoseCalc.vmcOptions.version
+ case 'Carleton'
+ filename = sprintf('%s%d.dos',outfile(1:idx(2)),k);
+ case 'dkfz'
+ filename = sprintf('%s%d_%s.dos',outfile(1:idx(2)),k,VmcOptions.scoringOptions.outputOptions.name);
+ end
+ [bixelDose,bixelDoseError] = matRad_readDoseVmc(fullfile(runsPath,filename),VmcOptions);
+
+ %{
+ %%% Don't do any sampling, since the correct error is
+ difficult to figure out. We also don't really need it on
+ the Graham cluster.
+
+ if ~calcDoseDirect
+ % if not calculating dose directly, sample dose
+
+ % determine cutoff
+ doseCutoff = VmcOptions.run.relDoseCutoff*max(bixelDose);
+
+ % determine which voxels to sample
+ indSample = bixelDose < doseCutoff & bixelDose ~= 0;
+ r = rand(nnz(indSample),1);
+
+ % sample them
+ thresRand = bixelDose(indSample)./doseCutoff;
+ indKeepSampled = r < thresRand;
+ indKeep = indSample;
+ indKeep(indKeep) = indKeepSampled;
+
+ bixelDose(indKeep) = doseCutoff;
+ bixelDose(indSample & ~indKeep) = 0;
+
+ end
+ %}
+
+ % apply absolute calibration factor
+ bixelDoseError = sqrt((VmcOptions.run.absCalibrationFactorVmc.*bixelDoseError).^2+(bixelDose.*VmcOptions.run.absCalibrationFactorVmc_err).^2);
+ bixelDose = bixelDose*VmcOptions.run.absCalibrationFactorVmc;
+
+ % Save dose for every bixel in cell array
+ doseTmpContainer{mod(readCounter-1,numOfBixelsContainer)+1,1} = sparse(V,1,bixelDose(V),dij.numOfVoxels,1);
+ doseTmpContainerError{mod(readCounter-1,numOfBixelsContainer)+1,1} = sparse(V,1,bixelDoseError(V),dij.numOfVoxels,1);
+
+ % save computation time and memory by sequentially filling the
+ % sparse matrix dose.dij from the cell array
+ if mod(readCounter,numOfBixelsContainer) == 0 || readCounter == dij.totalNumOfBixels
+ if calcDoseDirect
+ if isfield(stf(beamNum(readCounter)).ray(rayNum(readCounter)),'weight')
+ % score physical dose
+ dij.physicalDose{1}(:,i) = dij.physicalDose{1}(:,i) + stf(beamNum(readCounter)).ray(rayNum(readCounter)).weight{1} * doseTmpContainer{1,1};
+ dij.physicalDoseError{1}(:,i) = sqrt(dij.physicalDoseError{1}(:,i).^2 + (stf(beamNum(readCounter)).ray(rayNum(readCounter)).weight{1} * doseTmpContainerError{1,1}).^2);
+ else
+ error(['No weight available for beam ' num2str(beamNum(readCounter)) ', ray ' num2str(rayNum(readCounter))]);
+ end
+ else
+ % fill entire dose influence matrix
+ dij.physicalDose{1}(:,(ceil(readCounter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:readCounter) = ...
+ [doseTmpContainer{1:mod(readCounter-1,numOfBixelsContainer)+1,1}];
+
+ dij.physicalDoseError{1}(:,(ceil(readCounter/numOfBixelsContainer)-1)*numOfBixelsContainer+1:readCounter) = ...
+ [doseTmpContainerError{1:mod(readCounter-1,numOfBixelsContainer)+1,1}];
+ end
+ end
+ end
+
+ end
+
+ end
+
+ fprintf('Done!\n');
+end
+
+%% delete temporary files
+delete(fullfile(VMCPath, 'run_parallel_simulations.bat')); % batch file
+delete(fullfile(phantomPath, 'matRad_CT.ct')); % phantom file
+for j = 1:maxNumOfParMCSim
+ delete(fullfile(runsPath, ['MCpencilbeam_temp_',num2str(mod(j-1,VmcOptions.run.numOfParMCSim)+1),'.vmc'])); % vmc inputfile
+ switch pln.propDoseCalc.vmcOptions.version
+ case 'Carleton'
+ filename = sprintf('%s%d.dos','MCpencilbeam_temp_',j);
+ case 'dkfz'
+ filename = sprintf('%s%d_%s.dos','MCpencilbeam_temp_',j,VmcOptions.scoringOptions.outputOptions.name);
+ end
+ delete(fullfile(runsPath,filename)); % vmc outputfile
+end
+
+try
+ % wait 0.1s for closing all waitbars
+ allWaitBarFigures = findall(0,'type','figure','tag','TMWWaitbar');
+ delete(allWaitBarFigures);
+ pause(0.1);
+catch
+end
diff --git a/matRad/doseCalc/vmc++/matRad_bixelPhspVmc.m b/matRad/doseCalc/vmc++/matRad_bixelPhspVmc.m
new file mode 100644
index 000000000..251b1a853
--- /dev/null
+++ b/matRad/doseCalc/vmc++/matRad_bixelPhspVmc.m
@@ -0,0 +1,240 @@
+function stf = matRad_bixelPhspVmc(stf,masterRayPosBEV,vmcOptions)
+
+% after everything is done and working, add in code to verify if files
+% already exist
+
+
+
+switch vmcOptions.version
+ case 'Carleton'
+ phspPath = fullfile(fileparts(mfilename('fullpath')), 'run', 'phsp');
+ case 'dkfz'
+ phspPath = fullfile(fileparts(mfilename('fullpath')), 'runs', 'phsp');
+end
+
+fname_full = fullfile(phspPath,sprintf('%s.egsphsp1',vmcOptions.phspBaseName));
+fid_full = fopen(fname_full,'r');
+
+SAD2SCD = vmcOptions.SCD./stf(1).SAD;
+% in cm
+bixelWidth = stf(1).bixelWidth.*SAD2SCD/10;
+X = masterRayPosBEV(:,1).*SAD2SCD/10;
+Y = -masterRayPosBEV(:,3).*SAD2SCD/10; % minus sign necessary since to get from BEAM coord. to DICOM, we do a rotation, NOT reflection
+
+% determine file names, check for existence
+numBixels = size(masterRayPosBEV,1);
+fname_bixels = cell(numBixels,1);
+writeFiles = false;
+for i = 1:numBixels
+
+ % file name
+ fname_bixels{i} = fullfile(phspPath,sprintf('%s_bixelWidth%f_X%f_Y%f.egsphsp1',vmcOptions.phspBaseName,bixelWidth,X(i),Y(i)));
+
+ if ~exist(fname_bixels{i},'file')
+ % if any file doesn't exist, then we want to write new phsp file
+ writeFiles = true;
+ end
+end
+
+% give file name to ray
+for i = 1:numel(stf)
+
+ for j = 1:stf(i).numOfRays
+
+ % find correct bixel
+ bixelInd = all(repelem(stf(i).ray(j).rayPos_bev,numBixels,1) == masterRayPosBEV,2);
+ % write filename
+ stf(i).ray(j).phspFileName = fname_bixels{bixelInd};
+ end
+end
+
+% FIX THIS TO GENERATE PHSP FILES FOR ALL BIXELS IN FIELD
+
+if writeFiles
+ % only do read/write files if they don't already exist
+
+ %% extract information from full phsp file, write to bixel files
+
+ % set up arrays for the bixel phsp files
+ fid_bixels = cell(numBixels,1);
+ header_bixels = cell(numBixels,1);
+ firstParticle_bixels = false(numBixels,1);
+
+ % open header of full phsp
+ [fid_full, header_full] = getHeader(fid_full);
+ mode = char(header_full.MODE_RW(5));
+
+ % loop through each record in full phsp
+ fprintf('matRad: creating bixel phsp files... ');
+ for i = 1:header_full.NPPHSP
+
+ % extract record
+ [fid_full, record] = getRecord(fid_full,mode);
+
+ % sort into correct bixel
+ bixelInd = find(sum(abs([X Y]-repelem([record.X record.Y],numBixels,1)) < repelem(bixelWidth/2,numBixels,2),2) == 2,1,'first');
+
+ if ~isempty(bixelInd)
+
+ if isempty(fid_bixels{bixelInd})
+ % if not previously opened, open the file, write the header
+ % the header is just a dummy for now
+ header_bixels{bixelInd} = header_full;
+ % these variables will change throughout the read/write process
+ header_bixels{bixelInd}.NPPHSP = 0;
+ header_bixels{bixelInd}.NPHOTPHSP = 0;
+ header_bixels{bixelInd}.EKMAXPHSP = 0;
+ header_bixels{bixelInd}.EKMINPHSPE = 1000;
+
+ % open file, write header
+ fid_bixels{bixelInd} = fopen(fname_bixels{bixelInd},'W'); % turn this to 'W'?
+ writeHeader(fid_bixels{bixelInd},header_bixels{bixelInd});
+ end
+
+ %% bixel header
+
+ % increment number of particles
+ header_bixels{bixelInd}.NPPHSP = header_bixels{bixelInd}.NPPHSP+1;
+
+ % modify max/min energies, increment number of photons
+ % must determine particle type using LATCH
+ LATCH = de2bi(record.LATCH,32);
+ if LATCH(30:31) == [0 0]
+ % photon
+ header_bixels{bixelInd}.EKMAXPHSP = max(header_bixels{bixelInd}.EKMAXPHSP,abs(record.E));
+ header_bixels{bixelInd}.NPHOTPHSP = header_bixels{bixelInd}.NPHOTPHSP+1;
+
+ elseif LATCH(30:31) == [0 1]
+ % electron
+ header_bixels{bixelInd}.EKMINPHSPE = min(header_bixels{bixelInd}.EKMINPHSPE,abs(record.E)-0.511);
+ header_bixels{bixelInd}.EKMAXPHSP = max(header_bixels{bixelInd}.EKMAXPHSP,abs(record.E)-0.511);
+
+ elseif LATCH(30:31) == [1 0]
+ % positron
+ header_bixels{bixelInd}.EKMINPHSPE = min(header_bixels{bixelInd}.EKMINPHSPE,abs(record.E)-0.511);
+ header_bixels{bixelInd}.EKMAXPHSP = max(header_bixels{bixelInd}.EKMAXPHSP,abs(record.E)-0.511);
+
+ elseif LATCH(30:31) == [1 1]
+ error('Electron and positron???')
+
+ end
+
+ %% bixel record
+
+ % is this the first particle scored from a new primary history?
+ if record.E < 0
+ % if it is, then we want ALL bixel phsp files to reflect this
+ % i.e., the next particle in all bixel phsp files should have
+ % a negative energy (first particle scored from a new primary
+ % history)
+
+ firstParticle_bixels(:) = true;
+ end
+
+ if firstParticle_bixels(bixelInd)
+ % this is the first from a new primary history
+ % make the energy negative
+ record.E = -abs(record.E);
+ % make sure next particle is not negative
+ firstParticle_bixels(bixelInd) = false;
+ else
+ % these particles should already have positive energy
+ if record.E < 0
+ % SHOULDN'T HAPPEN
+ warning('NEGATIVE ENERGY')
+ end
+ end
+
+ % write the record
+ writeRecord(fid_bixels{bixelInd},record,mode);
+ end
+
+ % display progress
+ if mod(i,max(1,round(header_full.NPPHSP/200))) == 0
+ matRad_progress(i/max(1,round(header_full.NPPHSP/200)),...
+ floor(header_full.NPPHSP/max(1,round(header_full.NPPHSP/200))));
+ end
+ end
+
+
+ %% clean up bixel phsp headers
+ fprintf('matRad: updating bixel phsp file headers... ');
+ for i = 1:numBixels
+
+ if header_bixels{i}.EKMINPHSPE == 1000
+ header_bixels{i}.EKMINPHSPE = 0;
+ end
+
+ % seek to beginning of file
+ fseek(fid_bixels{i},0,'bof');
+
+ % write updated header
+ writeHeader(fid_bixels{i},header_bixels{i});
+
+ % close file
+ fclose(fid_bixels{i});
+ end
+ fprintf('Done!\n')
+
+end
+
+end
+
+
+% read/write functions
+
+function [fid, header] = getHeader(fid)
+
+header.MODE_RW = fread(fid,5,'uint8');
+header.NPPHSP = fread(fid,1,'int32');
+header.NPHOTPHSP = fread(fid,1,'int32');
+header.EKMAXPHSP = fread(fid,1,'float32');
+header.EKMINPHSPE = fread(fid,1,'float32');
+header.NINCPHSP = fread(fid,1,'float32');
+header.garbage = fread(fid,3,'int8');
+
+end
+
+function [fid, record] = getRecord(fid,mode)
+
+record.LATCH = fread(fid,1,'uint32');
+record.E = fread(fid,1,'float32');
+record.X = fread(fid,1,'float32');
+record.Y = fread(fid,1,'float32');
+record.U = fread(fid,1,'float32');
+record.V = fread(fid,1,'float32');
+record.WT = fread(fid,1,'float32');
+
+if mode == 2
+ record.ZLAST = fread(fid,1,'float32');
+end
+
+end
+
+function writeHeader(fid,header)
+
+fwrite(fid,header.MODE_RW,'uint8');
+fwrite(fid,header.NPPHSP,'int32');
+fwrite(fid,header.NPHOTPHSP,'int32');
+fwrite(fid,header.EKMAXPHSP,'float32');
+fwrite(fid,header.EKMINPHSPE,'float32');
+fwrite(fid,header.NINCPHSP,'float32');
+fwrite(fid,header.garbage,'int8');
+
+end
+
+function writeRecord(fid,record,mode)
+
+fwrite(fid,record.LATCH,'uint32');
+fwrite(fid,record.E,'float32');
+fwrite(fid,record.X,'float32');
+fwrite(fid,record.Y,'float32');
+fwrite(fid,record.U,'float32');
+fwrite(fid,record.V,'float32');
+fwrite(fid,record.WT,'float32');
+
+if mode == 2
+ fwrite(fid,record.ZLAST,'float32');
+end
+
+end
\ No newline at end of file
diff --git a/matRad/doseCalc/vmc++/matRad_createVmcBatchFile.m b/matRad/doseCalc/vmc++/matRad_createVmcBatchFile.m
new file mode 100644
index 000000000..4ead8a258
--- /dev/null
+++ b/matRad/doseCalc/vmc++/matRad_createVmcBatchFile.m
@@ -0,0 +1,74 @@
+function matRad_createVmcBatchFile(parallelSimulations,filepath,verboseLevel)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad batchfile creation
+%
+% call
+% matRad_createVmcBatchFile(parallelSimulations,filepath,verboseLevel)
+%
+% input
+% parallelSimulations: no of parallel simulations
+% filepath: path where batchfile is created (this has to be the
+% path of the vmc++ folder)
+% verboseLevel: optional. number specifying the amount of output
+% printed to the command prompt
+%
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% set verbose level
+if nargin < 3
+ verboseString = '/B'; % to not open terminals by default
+else
+ if verboseLevel > 1 % open terminals is verboseLevel > 1
+ verboseString = '';
+ else
+ verboseString = '/B';
+ end
+end
+
+if ispc % parallelization only possible on windows systems
+
+ parallelProcesses = cell(1,parallelSimulations);
+ for i = 1:parallelSimulations
+ parallelProcesses{1,i} = ['start "" 9>"%lock%',num2str(i),'" ' verboseString ' .\bin\vmc_Windows.exe -i MCpencilbeam_temp_',num2str(i)];
+ end
+
+ batchFile = {...
+ ['@echo off'],...
+ ['setlocal'],...
+ ['set "lock=%temp%\wait%random%.lock"'],...
+ [''],...
+ parallelProcesses{:},...
+ [''],...
+ [':Wait for all processes to finish (wait until lock files are no longer locked)'],...
+ ['1>nul 2>nul ping /n 2 ::1'],...
+ ['for %%N in (',strjoin(arrayfun(@(x) num2str(x),(1:parallelSimulations),'UniformOutput',false),' '),') do ('],...
+ [' (call ) 9>"%lock%%%N" || goto :Wait'],...
+ [') 2>nul'],...
+ [''],...
+ ['del "%lock%*"'],...
+ ['']...
+ %,['echo Done - ready to continue processing']
+ };
+
+elseif isunix
+
+ batchFile = {'./bin/vmc_Linux.exe MCpencilbeam_temp_1'};
+
+end
+
+% write batch file
+fid = fopen(filepath,'wt');
+for i = 1 : length(batchFile)
+ fprintf(fid,'%s\n',batchFile{i});
+end
+fclose(fid);
+
+if isunix
+ system(['chmod a+x ' filepath]);
+end
+
+end
diff --git a/matRad/doseCalc/vmc++/matRad_createVmcInput.m b/matRad/doseCalc/vmc++/matRad_createVmcInput.m
new file mode 100644
index 000000000..99b03058e
--- /dev/null
+++ b/matRad/doseCalc/vmc++/matRad_createVmcInput.m
@@ -0,0 +1,153 @@
+function matRad_createVmcInput(VmcOptions,filename)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad vmc++ inputfile creation
+%
+% call
+% matRad_createVmcInput(VmcOptions,filename)
+%
+% input
+% VmcOptions: structure set with VMC options
+% filename: full file name of generated vmc input file (has to be
+% located in the runs path in the vmc++ folder)
+%
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+% define a cell array which is bigger than necessary
+% some parts of the input (e.g., the source) are variable
+% then delete the empty elements
+VmcInput = cell(100,1);
+offset = 0;
+
+% define the scoring options
+VmcInput(offset+(1:11)) = {...
+ [' :start scoring options: ' ] ,...
+ [' start in geometry: ' VmcOptions.scoringOptions.startInGeometry ] ,...
+ [' :start dose options: ' ] ,...
+ [' score in geometries: ' VmcOptions.scoringOptions.doseOptions.scoreInGeometries ] ,...
+ [' score dose to water: ' VmcOptions.scoringOptions.doseOptions.scoreDoseToWater ] ,...
+ [' :stop dose options: ' ] ,...
+ [' :start output options ' VmcOptions.scoringOptions.outputOptions.name ':' ] ,...
+ [' dump dose: ' num2str(VmcOptions.scoringOptions.outputOptions.dumpDose) ] ,...
+ [' :stop output options ' VmcOptions.scoringOptions.outputOptions.name ':' ] ,...
+ [' :stop scoring options: ' ] ,...
+ [' ' ] ...
+};
+offset = offset+11;
+
+% define the geometry
+VmcInput(offset+(1:8)) = {...
+ [' :start geometry: ' ] ,...
+ [' :start XYZ geometry: ' ] ,...
+ [' my name = ' VmcOptions.geometry.XyzGeometry.Ct ] ,...
+ [' method of input = ' VmcOptions.geometry.XyzGeometry.methodOfInput ] ,...
+ [' phantom file = ' VmcOptions.geometry.XyzGeometry.CtFile ] ,...
+ [' :stop XYZ geometry: ' ] ,...
+ [' :stop geometry: ' ] ,...
+ [' ' ] ...
+};
+offset = offset+8;
+
+% define the source
+if strcmp(VmcOptions.source.type,'beamlet')
+
+ VmcInput(offset+(1:3)) = {...
+ [' :start beamlet source: ' ] ,...
+ [' my name = ' VmcOptions.source.myName ] ,...
+ [' monitor units ' VmcOptions.source.myName ' = ' num2str(VmcOptions.source.monitorUnits) ] ...
+ };
+ offset = offset+3;
+
+ if ~isempty(VmcOptions.source.monoEnergy) && VmcOptions.source.monoEnergy>0
+ VmcInput(offset+1) = {...
+ [' mono energy = ' num2str(VmcOptions.source.monoEnergy) ] ...
+ };
+ offset = offset+1;
+ end
+
+ VmcInput(offset+(1:6)) = {...
+ [' spectrum = ' VmcOptions.source.spectrum ] ,...
+ [' charge = ' num2str(VmcOptions.source.charge) ] ,...
+ [' beamlet edges = ' num2str(VmcOptions.source.beamletEdges, '%8.5f ') ] ,...
+ [' virtual point source position = ' num2str(VmcOptions.source.virtualPointSourcePosition, '%8.5f ') ] ,...
+ [' :stop beamlet source: ' ] ,...
+ [' ' ] ...
+ };
+ offset = offset+6;
+
+elseif strcmp(VmcOptions.source.type,'phsp')
+
+ VmcInput(offset+(1:12)) = {...
+ [' :start general source: ' ] ,...
+ [' monitor units ' VmcOptions.source.myName ' = ' num2str(VmcOptions.source.monitorUnits) ] ,...
+ [' translation ' VmcOptions.source.myName ' = ' num2str(VmcOptions.source.translation) ] ,...
+ [' isocenter ' VmcOptions.source.myName ' = ' num2str(VmcOptions.source.isocenter) ] ,...
+ [' angles ' VmcOptions.source.myName ' = ' num2str(VmcOptions.source.angles) ] ,...
+ [' :start phsp source: ' ] ,...
+ [' my name = ' VmcOptions.source.myName ] ,...
+ [' file name = ' VmcOptions.source.file_name ] ,...
+ [' particle type = ' num2str(VmcOptions.source.particleType) ] ,...
+ [' :stop phsp source: ' ] ,...
+ [' :stop general source: ' ] ,...
+ [' ' ] ...
+ };
+offset = offset+12;
+end
+
+% define the MC parameters
+VmcInput(offset+(1:5)) = {...
+ [' :start MC Parameter: ' ] ,...
+ [' automatic parameter = ' VmcOptions.McParameter.automatic_parameter ] ,...
+ [' spin = ' num2str(VmcOptions.McParameter.spin) ] ,...
+ [' :stop MC Parameter: ' ] ,...
+ [' ' ] ...
+ };
+offset = offset+5;
+
+% define the MC control
+VmcInput(offset+(1:6)) = {...
+ [' :start MC Control: ' ] ,...
+ [' ncase = ' num2str(VmcOptions.McControl.ncase) ] ,...
+ [' nbatch = ' num2str(VmcOptions.McControl.nbatch) ] ,...
+ [' rng seeds = ' num2str(VmcOptions.McControl.rngSeeds) ] ,...
+ [' :stop MC Control: ' ] ,...
+ [' ' ] ...
+};
+offset = offset+6;
+
+% define the variance reduction
+VmcInput(offset+(1:6)) = {...
+ [' :start variance reduction: ' ] ,...
+ [' repeat history = ' num2str(VmcOptions.varianceReduction.repeatHistory) ] ,...
+ [' split photons = ' num2str(VmcOptions.varianceReduction.splitPhotons) ] ,...
+ [' photon split factor = ' num2str(VmcOptions.varianceReduction.photonSplitFactor) ] ,...
+ [' :stop variance reduction: ' ] ,...
+ [' ' ] ...
+};
+offset = offset+6;
+
+% define the quasi
+VmcInput(offset+(1:5)) = {...
+ [' :start quasi: ' ] ,...
+ [' base = ' num2str(VmcOptions.quasi.base) ] ,...
+ [' dimension = ' num2str(VmcOptions.quasi.dimension) ] ,...
+ [' skip = ' num2str(VmcOptions.quasi.skip) ] ,...
+ [' :stop quasi: ' ] ...
+};
+offset = offset+5;
+
+% delete empty elements
+VmcInput((offset+1):end) = [];
+
+% write input file
+fid = fopen(filename,'wt');
+for i = 1 : length(VmcInput)
+ fprintf(fid,'%s\n',VmcInput{i});
+end
+fclose(fid);
+
+end
\ No newline at end of file
diff --git a/matRad/doseCalc/vmc++/matRad_exportCtVmc.m b/matRad/doseCalc/vmc++/matRad_exportCtVmc.m
new file mode 100644
index 000000000..01b4e3830
--- /dev/null
+++ b/matRad/doseCalc/vmc++/matRad_exportCtVmc.m
@@ -0,0 +1,39 @@
+function matRad_exportCtVmc(ct,filename)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad binary CT export for vmc++
+%
+% call
+% matRad_exportCtVmc(ct,filename)
+%
+% input
+% ct: matRad ct struct
+% filename: path where CTfile is created
+%
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+fid = fopen(filename,'w');
+
+% write ct dimensions
+fwrite(fid,ct.cubeDim([2 1 3]),'int32');
+
+% write voxel corner location in cm in physical cs with ct cube corner at [.5 .5 .5]
+X = (0.5:(ct.cubeDim(2)+0.5))*ct.resolution.x/10;
+Y = (0.5:(ct.cubeDim(1)+0.5))*ct.resolution.y/10;
+Z = (0.5:(ct.cubeDim(3)+0.5))*ct.resolution.z/10;
+
+fwrite(fid,X,'float32');
+fwrite(fid,Y,'float32');
+fwrite(fid,Z,'float32');
+
+% write voxel densities
+% first permute indices y <-> x
+ctVMC = permute(ct.cube{1},[2 1 3]);
+% then reshape into single column
+ctVMC = reshape(ctVMC,[],1);
+fwrite(fid,ctVMC,'float32');
+
+fclose(fid);
diff --git a/matRad/doseCalc/vmc++/matRad_matRad2vmcSourceAngles.m b/matRad/doseCalc/vmc++/matRad_matRad2vmcSourceAngles.m
new file mode 100644
index 000000000..3c1614ab1
--- /dev/null
+++ b/matRad/doseCalc/vmc++/matRad_matRad2vmcSourceAngles.m
@@ -0,0 +1,100 @@
+function angles = matRad_matRad2vmcSourceAngles(gantryAngle,couchAngle)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad convert gantry and couch angles to angles used by vmc++
+%
+% call
+% matRad_matRad2mvcSourceAngles(gantryAngle,couchAngle)
+%
+% input
+% gantryAngle: gantry angle
+% couchAngle: couch angle
+%
+%
+% References
+% Notes 25 July 2018
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+switch -cosd(couchAngle).*sind(gantryAngle)
+ % do special cases first, when cosd(thetaY) == 0
+ % -cosd(couchAngle)*sind(gantryAngle) = sind(thetaY)
+
+ case 1
+ thetaY = 90;
+ thetaZ = 0;
+
+ if couchAngle == 0
+ % then gantryAngle == 270
+ thetaX = 90;
+ else
+ % then couchAngle == 180, gantryAngle == 90
+ thetaX = 270;
+ end
+
+ case -1
+ thetaY = 270;
+ thetaZ = 0;
+ if couchAngle == 0
+ % then gantryAngle == 90
+ thetaX = 90;
+ else
+ % then couchAngle == 180, gantryAngle == 270
+ thetaX = 270;
+ end
+
+ otherwise
+ % general case, cosd(thetaY) ~= 0
+
+ % first determine thetaY; note that we may have to take
+ % supplementary angle later
+ thetaY = asind(-cosd(couchAngle).*sind(gantryAngle));
+
+ % now determine thetaX and thetaZ from the x and y components
+ thetaX = atan2d(cosd(gantryAngle)./cosd(thetaY),sind(couchAngle).*sind(gantryAngle)./cosd(thetaY));
+ thetaZ = atan2d(-sind(couchAngle)./cosd(thetaY),cosd(couchAngle).*cosd(gantryAngle)./cosd(thetaY));
+
+ % verify that the remaining angular relations are satisfied
+ % if not, take thetaY = 180-thetaY and recalculate thetaX and
+ % thetaZ
+ if ~verifiedRelations(gantryAngle,couchAngle,thetaX,thetaY,thetaZ)
+ thetaY = 180-thetaY;
+ thetaX = atan2d(cosd(gantryAngle)./cosd(thetaY),sind(couchAngle).*sind(gantryAngle)./cosd(thetaY));
+ thetaZ = atan2d(-sind(couchAngle)./cosd(thetaY),cosd(couchAngle).*cosd(gantryAngle)./cosd(thetaY));
+ end
+
+end
+
+% now verify for a final time that the remaining angular relations are satisfied
+if ~verifiedRelations(gantryAngle,couchAngle,thetaX,thetaY,thetaZ)
+ error('Angular relations are not satisfied for some reason');
+end
+
+angles = [thetaX thetaY thetaZ];
+
+end
+
+
+function ver = verifiedRelations(gantryAngle,couchAngle,thetaX,thetaY,thetaZ)
+
+ver = true;
+
+% need to verify four relations
+% if any of them fail, then fail the test
+if abs(sind(gantryAngle) + sind(thetaX).*sind(thetaY).*cosd(thetaZ) + cosd(thetaX).*sind(thetaZ)) > eps*10e3
+ ver = false;
+end
+
+if abs(-sind(couchAngle).*cosd(gantryAngle) + cosd(thetaX).*sind(thetaY).*cosd(thetaZ) - sind(thetaX).*sind(thetaZ)) > eps*10e3
+ ver = false;
+end
+
+if abs(sind(thetaX).*sind(thetaY).*sind(thetaZ)-cosd(thetaX).*cosd(thetaZ)) > eps*10e3
+ ver = false;
+end
+
+if abs(-cosd(couchAngle)+cosd(thetaX).*sind(thetaY).*sind(thetaZ)+sind(thetaX).*cosd(thetaZ)) > eps*10e3
+ ver = false;
+end
+
+end
\ No newline at end of file
diff --git a/matRad/doseCalc/vmc++/matRad_readDoseVmc.m b/matRad/doseCalc/vmc++/matRad_readDoseVmc.m
new file mode 100644
index 000000000..e47ce5364
--- /dev/null
+++ b/matRad/doseCalc/vmc++/matRad_readDoseVmc.m
@@ -0,0 +1,60 @@
+function [bixelDose,bixelDoseError] = matRad_readDoseVmc(filename,VmcOptions)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad binary dose import from vmc++
+%
+% call
+% [bixelDose,bixelDoseError] = matRad_readDoseVmc(filename)
+%
+% input
+% filename: path of input file
+%
+% output
+% bixelDose = vector of imported dose values, [D] = 10^-(10) Gy cm^2
+% bixelDoseError = vector of imported dose errors, [deltaD] = 10^-(10) Gy cm^2
+%
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+fid = fopen(filename,'r');
+
+% read header (no regions, no histories, no batches, no beamlets, format specifier (dump_dose))
+switch VmcOptions.run.version
+ case 'Carleton'
+ Header = fread(fid,1,'int32');
+ no_regions = Header(1);
+ dump_dose = VmcOptions.scoringOptions.outputOptions.dumpDose;
+ case 'dkfz'
+ Header = fread(fid,5,'int32');
+ no_regions = Header(1);
+ dump_dose = Header(5);
+end
+
+% read dose array
+if dump_dose == 2
+ dmax = fread(fid, 1, 'double');
+ bixelDose = fread(fid, no_regions, 'uint16');
+ bixelDose = bixelDose/65534*dmax; % conversion short integers to floating numbers
+ bixelDoseError = zeros(size(bixelDose));
+elseif dump_dose == 1
+ bixelDose = fread(fid, no_regions, 'float32');
+ bixelDoseError = fread(fid, no_regions, 'float32');
+end
+fclose(fid);
+
+% reshape into array, permute y <-> x, reshape back into column
+bixelDose = reshape(bixelDose,VmcOptions.geometry.dimensions([2 1 3]));
+bixelDose = permute(bixelDose,[2 1 3]);
+bixelDose = reshape(bixelDose,[],1);
+
+bixelDoseError = reshape(bixelDoseError,VmcOptions.geometry.dimensions([2 1 3]));
+bixelDoseError = permute(bixelDoseError,[2 1 3]);
+bixelDoseError = reshape(bixelDoseError,[],1);
+
+end
+
+
+
+
diff --git a/matRad/doseCalc/vmc++/matRad_vmcOptions.m b/matRad/doseCalc/vmc++/matRad_vmcOptions.m
new file mode 100644
index 000000000..7e2c730c6
--- /dev/null
+++ b/matRad/doseCalc/vmc++/matRad_vmcOptions.m
@@ -0,0 +1,106 @@
+function VmcOptions = matRad_vmcOptions(pln,ct)
+
+%% run options
+
+% number of paralle MC simulations
+if isfield(pln.propDoseCalc.vmcOptions,'numOfParMCSim')
+ VmcOptions.run.numOfParMCSim = pln.propDoseCalc.vmcOptions.numOfParMCSim;
+else
+ VmcOptions.run.numOfParMCSim = 4;
+end
+if isunix && VmcOptions.run.numOfParMCSim > 1
+ VmcOptions.run.numOfParMCSim = 1;
+end
+
+% number of histories per bixel
+if isfield(pln.propDoseCalc.vmcOptions,'nCasePerBixel')
+ VmcOptions.run.nCasePerBixel = pln.propDoseCalc.vmcOptions.nCasePerBixel;
+else
+ VmcOptions.run.nCasePerBixel = 5000;
+end
+
+% relative dose cutoff
+VmcOptions.run.relDoseCutoff = 10^(-3);
+
+% version (Carleton, dkfz, etc.)
+VmcOptions.run.version = pln.propDoseCalc.vmcOptions.version;
+
+% set absolute calibration factor
+% CALCULATION
+% absolute_calibration_factor = 1/D(depth = 100,5mm) -> D(depth = 100,5mm) = 1Gy
+% SETUP
+% SAD = 1000mm, SCD = 500mm, bixelWidth = 5mm, IC = [240mm,240mm,240mm]
+% fieldsize@IC = 105mm x 105mm, phantomsize = 81 x 81 x 81 = 243mm x 243mm x 243mm
+% rel_Dose_cutoff = 10^(-3), ncase = 500000/bixel
+switch pln.propDoseCalc.vmcOptions.version
+ case 'Carleton'
+ switch pln.propDoseCalc.vmcOptions.source
+ case 'phsp'
+
+ d_50mm = 9.351001892810018e-07;
+ d_50mm_error = 7.668474434354598e-09;
+
+ VmcOptions.run.absCalibrationFactorVmc = 1./d_50mm;
+ VmcOptions.run.absCalibrationFactorVmc_err = d_50mm_error./(d_50mm.^2);
+ end
+ case 'dkfz'
+ VmcOptions.run.absCalibrationFactorVmc = 99.818252282632300;
+end
+
+%% source
+
+VmcOptions.source.myName = 'some_source'; % name of source
+VmcOptions.source.monitorUnits = 1;
+switch pln.propDoseCalc.vmcOptions.source
+ case 'beamlet'
+ VmcOptions.source.spectrum = fullfile(runsPath,'spectra','var_6MV.spectrum'); % energy spectrum source (only used if no mono-Energy given)
+ VmcOptions.source.charge = 0; % charge (-1,0,1)
+ VmcOptions.source.type = 'beamlet';
+
+ case 'phsp'
+ VmcOptions.source.particleType = 2;
+ VmcOptions.source.type = 'phsp';
+end
+
+%% transport parameters
+
+VmcOptions.McParameter.automatic_parameter = 'yes'; % if yes, automatic transport parameters are used
+VmcOptions.McParameter.spin = 0; % 0: spin effects ignored; 1: simplistic; 2: full treatment
+
+%% MC control
+
+VmcOptions.McControl.ncase = VmcOptions.run.nCasePerBixel; % number of histories
+VmcOptions.McControl.nbatch = 10; % number of batches
+
+%% variance reduction
+
+VmcOptions.varianceReduction.repeatHistory = 0.041;
+VmcOptions.varianceReduction.splitPhotons = 1;
+VmcOptions.varianceReduction.photonSplitFactor = -80;
+
+%% quasi random numbers
+
+VmcOptions.quasi.base = 2;
+VmcOptions.quasi.dimension = 60;
+VmcOptions.quasi.skip = 1;
+
+%% geometry
+switch pln.propDoseCalc.vmcOptions.version
+ case 'Carleton'
+ VmcOptions.geometry.XyzGeometry.methodOfInput = 'MMC-PHANTOM'; % input method ('CT-PHANTOM', 'individual', 'groups')
+ case 'dkfz'
+ VmcOptions.geometry.XyzGeometry.methodOfInput = 'CT-PHANTOM'; % input method ('CT-PHANTOM', 'individual', 'groups')
+end
+VmcOptions.geometry.dimensions = ct.cubeDim;
+VmcOptions.geometry.XyzGeometry.Ct = 'CT'; % name of geometry
+
+%% scoring manager
+VmcOptions.scoringOptions.startInGeometry = 'CT'; % geometry in which partciles start their transport
+VmcOptions.scoringOptions.doseOptions.scoreInGeometries = 'CT'; % geometry in which dose is recorded
+VmcOptions.scoringOptions.doseOptions.scoreDoseToWater = 'yes'; % if yes output is dose to water
+VmcOptions.scoringOptions.outputOptions.name = 'CT'; % geometry for which dose output is created (geometry has to be scored)
+VmcOptions.scoringOptions.outputOptions.dumpDose = pln.propDoseCalc.vmcOptions.dumpDose; % output format (1: format=float, Dose + deltaDose; 2: format=short int, Dose)
+
+
+
+end
\ No newline at end of file
diff --git a/matRad/gui/widgets/matRad_PlanWidget.m b/matRad/gui/widgets/matRad_PlanWidget.m
index e0b3a2a20..1c71f12f8 100644
--- a/matRad/gui/widgets/matRad_PlanWidget.m
+++ b/matRad/gui/widgets/matRad_PlanWidget.m
@@ -821,6 +821,10 @@
set(handles.popUpMenuDoseEngine,'String',{availableEngines(:).shortName});
selectedEngineIx = get(handles.popUpMenuDoseEngine,'Value');
selectedEngine = availableEngines(selectedEngineIx);
+
+ if ~isfield(pln,'propStf') || ~isfield(pln.propStf,'numOfBeams')
+ pln.propStf.numOfBeams = numel(stfGen.gantryAngles);
+ end
if isfield(pln.propStf,'isoCenter')
% sanity check of isoCenter
diff --git a/matRad/gui/widgets/matRad_ViewingWidget.m b/matRad/gui/widgets/matRad_ViewingWidget.m
index 2f8920e94..9e37be7a5 100644
--- a/matRad/gui/widgets/matRad_ViewingWidget.m
+++ b/matRad/gui/widgets/matRad_ViewingWidget.m
@@ -1150,7 +1150,18 @@ function initValues(this)
if isfield(pln,'propStf') && isfield(pln.propStf,'isoCenter')
isoCoordinates = matRad_world2cubeIndex(pln.propStf.isoCenter(1,:), ct);
planeCenters = ceil(isoCoordinates);
- this.numOfBeams=pln.propStf.numOfBeams;
+ if ~isfield(pln.propStf,'numOfBeams')
+ if evalin('base','exist(''stf'')')
+ stf = evalin('base','stf');
+ this.numOfBeams = numel(stf);
+ elseif isfield(pln.propStf,'gantryAngles')
+ this.numOfBeams = numel(pln.propStf.gantryAngles);
+ else
+ this.numOfBeams = 1;
+ end
+ else
+ this.numOfBeams = 1;
+ end
end
end
diff --git a/matRad/matRad_calcDeliveryMetrics.m b/matRad/matRad_calcDeliveryMetrics.m
new file mode 100644
index 000000000..851a0ef6e
--- /dev/null
+++ b/matRad/matRad_calcDeliveryMetrics.m
@@ -0,0 +1,186 @@
+function result = matRad_calcDeliveryMetrics(result,pln,stf)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad delivery metric calculation
+%
+% call
+% matRad_calcDeliveryMetrics(result,pln)
+%
+% input
+% result: result struct from fluence optimization/sequencing
+% pln: matRad plan meta information struct
+%
+% output
+% All plans: total MU
+% VMAT plans: total time, leaf speed, MU rate, and gantry rotation speed
+% distributions
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+apertureInfo = result.apertureInfo;
+
+l = 0;
+if pln.propOpt.runVMAT
+
+ machine = matRad_loadMachine(pln);
+
+ apertureInfo.planMU = 0;
+ apertureInfo.planTime = 0;
+
+ %All of these are vectors
+ %Each entry corresponds to a beam angle
+ %Later, we will convert these to histograms, find max, mean, min, etc.
+ gantryRot = zeros(1,result.apertureInfo.totalNumOfShapes);
+ MURate = gantryRot;
+ times = gantryRot;
+ angles = gantryRot;
+ maxLeafSpeed = gantryRot;
+
+ for i = 1:size(apertureInfo.beam,2)
+
+ apertureInfo.planMU = apertureInfo.planMU+apertureInfo.beam(i).shape(1).MU;
+ apertureInfo.planTime = apertureInfo.planTime+apertureInfo.beam(i).time; %time until next optimized beam
+
+ if apertureInfo.beam(i).numOfShapes %only optimized beams have their time in the data struct
+ l = l+1;
+ gantryRot(l) = apertureInfo.beam(i).gantryRot;
+ MURate(l) = apertureInfo.beam(i).shape(1).MURate*60;
+ times(l) = apertureInfo.beam(i).time;
+ maxLeafSpeed(l) = apertureInfo.beam(i).maxLeafSpeed/10;
+ angles(l) = apertureInfo.beam(i).gantryAngle;
+ end
+ end
+
+
+ apertureInfoVec = apertureInfo.apertureVector;
+ if pln.propStf.continuousAperture
+ leftLeafPos = apertureInfoVec([1:apertureInfo.totalNumOfLeafPairs]+apertureInfo.totalNumOfShapes);
+ rightLeafPos = apertureInfoVec(1+apertureInfo.totalNumOfLeafPairs+apertureInfo.totalNumOfShapes:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2);
+
+ timeOptBorderAngles = apertureInfoVec((1+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2):end);
+ timeDoseBorderAngles = timeOptBorderAngles.*[apertureInfo.propVMAT.beam([apertureInfo.propVMAT.beam.DAOBeam]).timeFacCurr]';
+
+ leftLeafDiff = diff(reshape(leftLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,[]),1,2);
+ rightLeafDiff = diff(reshape(rightLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,[]),1,2);
+
+ leftLeafDiff = reshape(leftLeafDiff(repmat([apertureInfo.propVMAT.beam.DAOBeam],apertureInfo.beam(1).numOfActiveLeafPairs,1)),apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes);
+ rightLeafDiff = reshape(rightLeafDiff(repmat([apertureInfo.propVMAT.beam.DAOBeam],apertureInfo.beam(1).numOfActiveLeafPairs,1)),apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes);
+
+ lfspd = reshape([leftLeafDiff rightLeafDiff]./ ...
+ repmat(timeDoseBorderAngles',apertureInfo.beam(1).numOfActiveLeafPairs,2),2*apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeDoseBorderAngles),1);
+
+ optAngles = [apertureInfo.beam([apertureInfo.propVMAT.beam.DAOBeam]).gantryAngle];
+ optAnglesMat = reshape(repmat(optAngles,apertureInfo.beam(1).numOfActiveLeafPairs,2),2*apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeDoseBorderAngles),1);
+ else
+ leftLeafPos = apertureInfoVec([1:apertureInfo.totalNumOfLeafPairs]+apertureInfo.totalNumOfShapes);
+ rightLeafPos = apertureInfoVec(1+apertureInfo.totalNumOfLeafPairs+apertureInfo.totalNumOfShapes:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2);
+
+ optInd = [apertureInfo.propVMAT.beam.DAOBeam];
+ timeOptBorderAngles = apertureInfoVec((1+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2):end);
+
+ i = repelem(1:(apertureInfo.totalNumOfShapes-1),2);
+ j = repelem(1:(apertureInfo.totalNumOfShapes),2);
+ j(1) = [];
+ j(end) = [];
+
+ timeFac = [apertureInfo.propVMAT.beam(optInd).timeFac]';
+ timeFac(1) = [];
+ timeFac(end) = [];
+ %timeFac(timeFac == 0) = [];
+
+ timeFacMatrix = sparse(i,j,timeFac,(apertureInfo.totalNumOfShapes-1),apertureInfo.totalNumOfShapes);
+ timeBNOptAngles = timeFacMatrix*timeOptBorderAngles;
+
+ lfspd = reshape([abs(diff(reshape(leftLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2)) ...
+ abs(diff(reshape(rightLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2))]./ ...
+ repmat(timeBNOptAngles',apertureInfo.beam(1).numOfActiveLeafPairs,2),2*apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles),1);
+ end
+
+ if pln.propStf.continuousAperture
+
+ %FMOBorders = zeros(1,2*numel(pln.propStf.FMOGantryAngles));
+ counter = 1;
+ for i = 1:numel(stf)
+ if stf(i).propVMAT.FMOBeam
+ FMOBorders(counter) = stf(i).propVMAT.FMOAngleBorders(1);
+ FMOBorders(counter+1) = stf(i).propVMAT.FMOAngleBorders(2);
+ counter = counter+2;
+ else
+ continue
+ end
+ end
+ FMOBorders = unique(FMOBorders);
+ forwardDir = 1-2*mod(1:(numel(FMOBorders)-1),2);
+ numForward = zeros(numel(forwardDir),1);
+ numBackward = zeros(numel(forwardDir),1);
+ timeInInit = zeros(numel(forwardDir),1);
+
+ plot(optAnglesMat,lfspd,'.')
+ hold on
+ counter = 1;
+ for border = FMOBorders
+ plot([border border],[-machine.constraints.leafSpeed(2) machine.constraints.leafSpeed(2)],'r-')
+
+ if border < FMOBorders(end)
+ curr_lfspd = lfspd(FMOBorders(counter) <= optAnglesMat & optAnglesMat <= FMOBorders(counter+1));
+
+ numForward(counter) = nnz(curr_lfspd*forwardDir(counter) >= 0);
+ numBackward(counter) = nnz(curr_lfspd*forwardDir(counter) < 0);
+ timeInInit(counter) = sum(times(FMOBorders(counter) <= angles & angles <= FMOBorders(counter+1)));
+
+ counter = counter+1;
+ end
+ end
+
+ figure
+ plot([min(FMOBorders)-5 max(FMOBorders)+5],[0 0],'k--')
+ xlim([min(FMOBorders)-5 max(FMOBorders)+5])
+ ylim([-machine.constraints.leafSpeed(2)-5 machine.constraints.leafSpeed(2)+5])
+ xlabel('gantry angle (^\circ)')
+ ylabel('leaf speed (cm/s)')
+
+ figure
+ plot(optAngles,gantryRot,'.')
+ xlim([min(FMOBorders)-5 max(FMOBorders)+5])
+ ylim([0 machine.constraints.gantryRotationSpeed(2)+1])
+ xlabel('gantry angle (^\circ)')
+ ylabel('gantry rotation speed (^\circ/s)')
+
+ figure
+ plot(optAngles,MURate,'.')
+ xlim([min(FMOBorders)-5 max(FMOBorders)+5])
+ ylim([0 60*machine.constraints.monitorUnitRate(2)+5])
+ xlabel('gantry angle (^\circ)')
+ ylabel('MU rate (MU/min)')
+
+ apertureInfo.fracMaxMURate = sum(times(MURate > 60*machine.constraints.monitorUnitRate(2)*(1-1e-5)))./sum(times);
+ apertureInfo.fracMinMURate = sum(times(MURate < 60*machine.constraints.monitorUnitRate(1)*(1+1e-5)))./sum(times);
+ apertureInfo.fracMaxGantryRot = sum(times(gantryRot > machine.constraints.gantryRotationSpeed(2)*(1-1e-5)))./sum(times);
+ apertureInfo.fracMaxLeafSpeed = sum(times(maxLeafSpeed > machine.constraints.leafSpeed(2)/10*(1-1e-5)))./sum(times);
+ apertureInfo.fracHalfMaxLeafSpeed = sum(times(maxLeafSpeed > machine.constraints.leafSpeed(2)/10*(1-1e-5)/2))./sum(times);
+
+ apertureInfo.fracForward = numForward./(numForward+numBackward);
+ apertureInfo.fracBackward = 1-apertureInfo.fracForward;
+ apertureInfo.totalFracForward = mean(apertureInfo.fracForward);
+ %apertureInfo.totalFracForward = sum(apertureInfo.fracForward.*timeInInit)./sum(timeInInit);
+ apertureInfo.totalFracBackward = 1-apertureInfo.totalFracForward;
+ end
+ %}
+
+end
+
+result.apertureInfo = apertureInfo;
+
diff --git a/matRad/matRad_directApertureOptimization.m b/matRad/matRad_directApertureOptimization.m
index 1e909924f..aa4616eb5 100644
--- a/matRad/matRad_directApertureOptimization.m
+++ b/matRad/matRad_directApertureOptimization.m
@@ -1,4 +1,4 @@
-function [optResult,optimizer] = matRad_directApertureOptimization(dij,cst,apertureInfo,optResult,pln)
+function [resultGUI,optimizer] = matRad_directApertureOptimization(dij,cst,apertureInfo,resultGUI,pln)
% matRad function to run direct aperture optimization
%
% call
@@ -83,12 +83,46 @@
options.model = pln.bioModel.model;
% update aperture info vector
-apertureInfo = matRad_OptimizationProblemDAO.matRad_daoVec2ApertureInfo(apertureInfo,apertureInfo.apertureVector);
+if isfield(apertureInfo,'scaleFacRx')
+ %weights were scaled to acheive 95% PTV coverage
+ %scale back to "optimal" weights
+ apertureInfo.apertureVector(1:apertureInfo.totalNumOfShapes) = apertureInfo.apertureVector(1:apertureInfo.totalNumOfShapes)/apertureInfo.scaleFacRx;
+end
+
+if ~isfield(pln.propOpt,'preconditioner')
+ pln.propOpt.preconditioner = false;
+end
+
+if ~isfield(pln.propOpt,'runVMAT')
+ pln.propOpt.runVMAT = false;
+end
+
+if pln.propOpt.preconditioner
+ %rescale dij matrix, so that apertureWeight/bixelWidth ~= 1
+ % gradient wrt weights ~ 1, gradient wrt leaf pos
+ % ~ apertureWeight/(bixelWidth) ~1
+
+ % need to get the actual weights, so use the jacobiScale vector to
+ % convert from the variables
+ dij.scaleFactor = mean(apertureInfo.apertureVector(1:apertureInfo.totalNumOfShapes)./apertureInfo.jacobiScale)/(apertureInfo.bixelWidth);
+
+ dij.weightToMU = dij.weightToMU*dij.scaleFactor;
+ apertureInfo.weightToMU = apertureInfo.weightToMU*dij.scaleFactor;
+ apertureInfo.apertureVector(1:apertureInfo.totalNumOfShapes) = apertureInfo.apertureVector(1:apertureInfo.totalNumOfShapes)/dij.scaleFactor;
+end
%Use Dose Projection only
backProjection = matRad_DoseProjection();
-optiProb = matRad_OptimizationProblemDAO(backProjection,apertureInfo);
+if pln.propOpt.runVMAT
+ apertureInfo = matRad_OptimizationProblemVMAT.matRad_daoVec2ApertureInfo(apertureInfo,apertureInfo.apertureVector);
+ apertureInfo.newIteration = true; %do we need this?
+ optiProb = matRad_OptimizationProblemVMAT(backProjection,apertureInfo);
+else
+ apertureInfo = matRad_OptimizationProblemDAO.matRad_daoVec2ApertureInfo(apertureInfo,apertureInfo.apertureVector);
+ apertureInfo.newIteration = true; %do we need this?
+ optiProb = matRad_OptimizationProblemDAO(backProjection,apertureInfo);
+end
if ~isfield(pln.propOpt,'optimizer')
pln.propOpt.optimizer = 'IPOPT';
@@ -106,15 +140,59 @@
% Run IPOPT.
optimizer = optimizer.optimize(apertureInfo.apertureVector,optiProb,dij,cst);
-wOpt = optimizer.wResult;
+optApertureInfoVec = optimizer.wResult;
+
+
+%Additional VMAT stuff
+if pln.propOpt.preconditioner
+ % revert scaling
+
+ dij.weightToMU = dij.weightToMU./dij.scaleFactor;
+ resultGUI.apertureInfo.weightToMU = resultGUI.apertureInfo.weightToMU./dij.scaleFactor;
+ optApertureInfoVec(1:apertureInfo.totalNumOfShapes) = optApertureInfoVec(1:apertureInfo.totalNumOfShapes).*dij.scaleFactor;
+end
% update the apertureInfoStruct and calculate bixel weights
-apertureInfo = matRad_OptimizationProblemDAO.matRad_daoVec2ApertureInfo(apertureInfo,wOpt);
+newApertureInfo = optiProb.matRad_daoVec2ApertureInfo(resultGUI.apertureInfo,optApertureInfoVec); %Use optiprob here to automatically choose VMAT / DAO code
+
+% override also bixel weight vector in optResult struct
+w = newApertureInfo.bixelWeights;
+wDao = newApertureInfo.bixelWeights;
+
+dij.scaleFactor = 1;
+
+newApertureInfo = matRad_preconditionFactors(newApertureInfo);
% logging final results
matRad_cfg.dispInfo('Calculating final cubes...\n');
-resultGUI = matRad_calcCubes(apertureInfo.bixelWeights,dij);
-resultGUI.w = apertureInfo.bixelWeights;
-resultGUI.wDAO = apertureInfo.bixelWeights;
-resultGUI.apertureInfo = apertureInfo;
+resultGUI = matRad_calcCubes(w,dij);
+resultGUI.w = w;
+resultGUI.wDAO = wDao;
+resultGUI.apertureInfo = newApertureInfo;
+
+if isfield(pln,'scaleDRx') && pln.scaleDRx
+ %Scale D95 in target to RXDose
+ resultGUI.QI = matRad_calcQualityIndicators(cst,pln,resultGUI.physicalDose);
+
+ resultGUI.apertureInfo.scaleFacRx = max((pln.DRx/pln.numOfFractions)./[resultGUI.QI(pln.RxStruct).D_95]');
+ resultGUI.apertureInfo.apertureVector(1:resultGUI.apertureInfo.totalNumOfShapes) = resultGUI.apertureInfo.apertureVector(1:resultGUI.apertureInfo.totalNumOfShapes)*resultGUI.apertureInfo.scaleFacRx;
+
+ % update the apertureInfoStruct and calculate bixel weights
+ resultGUI.apertureInfo = matRad_daoVec2ApertureInfo(resultGUI.apertureInfo,resultGUI.apertureInfo.apertureVector);
+
+ % override also bixel weight vector in optResult struct
+ resultGUI.w = resultGUI.apertureInfo.bixelWeights;
+ resultGUI.wDao = resultGUI.apertureInfo.bixelWeights;
+
+ resultGUI.physicalDose = resultGUI.physicalDose.*resultGUI.apertureInfo.scaleFacRx;
+end
+
+% update apertureInfoStruct with the maximum leaf speeds per segment
+if pln.propOpt.runVMAT
+ resultGUI.apertureInfo = matRad_maxLeafSpeed(resultGUI.apertureInfo);
+
+ %optimize delivery
+ resultGUI = matRad_optDelivery(resultGUI,1);
+ %resultGUI = matRad_calcDeliveryMetrics(resultGUI,pln,stf);
+end
\ No newline at end of file
diff --git a/matRad/matRad_doseRecalc.m b/matRad/matRad_doseRecalc.m
new file mode 100644
index 000000000..d800db74f
--- /dev/null
+++ b/matRad/matRad_doseRecalc.m
@@ -0,0 +1,161 @@
+function recalc = matRad_doseRecalc(cst,pln,recalc,ct,apertureInfo,calcDoseDirect,dij)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad function to recalculate dose on a Dij angular resolution, or using
+% the dynamic method, whichever the user wants%
+%
+% call
+% resultGUI = matRad_doseRecalc(dij,apertureInfo,resultGUI,pln)
+%
+% input
+% dij: matRad dij struct
+% apertureInfo: aperture shape info struct
+% resultGUI: resultGUI struct to which the output data will be added, if
+% this field is empty optResult struct will be created
+% (optional)
+%
+% output
+% resultGUI: struct containing optimized fluence vector, dose, and
+% shape info
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if nargin < 6
+ calcDoseDirect = true;
+end
+
+recalc.apertureInfo = apertureInfo;
+
+%recalculate dose with finer gantry angles
+%to do this, we need Dij matrices at these new angles
+%Calculate dose directly
+
+%first, we need to update/generate new apertures for these angles
+recalc.pln = matRad_VMATGantryAngles(recalc.pln,cst,ct);
+if ~recalc.interpNew
+ %we will not interpolate new apertures
+ %easiest way to to this is to make ALL gantryAngles optGantryAngles
+ recalc.pln.propStf.DAOGantryAngles = recalc.pln.propStf.gantryAngles;
+end
+
+cd stf
+fname = sprintf('%.1f deg.mat',recalc.pln.propOpt.VMAToptions.maxGantryAngleSpacing);
+if exist(fname,'file')
+ load(fname);
+else
+ stf = matRad_generateStf(ct,cst,recalc.pln);
+ save(fname,'stf')
+end
+recalc.stf = stf;
+clear stf
+cd ..
+
+if ~recalc.interpNew || ~recalc.dijNew
+ %duplicate any beam angles that are directly between two old
+ %ones
+ duplicate = false(size(recalc.pln.propStf.gantryAngles));
+ for i = 1:numel(recalc.pln.propStf.gantryAngles)
+ if numel(find(abs(recalc.pln.propStf.gantryAngles(i)-pln.propStf.gantryAngles) == min(abs(recalc.pln.propStf.gantryAngles(i)-pln.propStf.gantryAngles)))) > 1
+ duplicate(i) = true;
+ end
+ end
+ newGantryAngles = zeros(1,numel(recalc.pln.propStf.gantryAngles)+nnz(duplicate));
+ newCouchAngles = zeros(1,numel(recalc.pln.propStf.gantryAngles)+nnz(duplicate));
+ tempStf = recalc.stf;
+ recalc.stf(1).copyInd = [];
+ tempStf(1).copyInd = [];
+ recalc.stf(1).stfCorr = [];
+ tempStf(1).stfCorr = [];
+ j = 1;
+ for i = 1:numel(recalc.pln.propStf.gantryAngles)
+ if duplicate(i)
+ tempStf(j).stfCorr = false;
+ newGantryAngles(j) = recalc.pln.propStf.gantryAngles(i);
+ newCouchAngles(j) = recalc.pln.propStf.couchAngles(i);
+ tempStf(j) = recalc.stf(i);
+ tempStf(j).gantryAngle = recalc.stf(i-1).gantryAngle;
+ tempStf(j).copyInd = 1;
+
+ j = j+1;
+
+ newGantryAngles(j) = recalc.pln.propStf.gantryAngles(i);
+ newCouchAngles(j) = recalc.pln.propStf.couchAngles(i);
+ tempStf(j) = recalc.stf(i);
+ tempStf(j).gantryAngle = recalc.stf(i+1).gantryAngle;
+ tempStf(j).copyInd = 2;
+ else
+ tempStf(j).stfCorr = true;
+ newGantryAngles(j) = recalc.pln.propStf.gantryAngles(i);
+ newCouchAngles(j) = recalc.pln.propStf.couchAngles(i);
+ tempStf(j) = recalc.stf(i);
+ end
+ j = j+1;
+ end
+ recalc.pln.propStf.gantryAngles = newGantryAngles;
+ recalc.pln.propStf.couchAngles = newCouchAngles;
+ recalc.pln.propStf.numOfBeams = numel(recalc.pln.propStf.gantryAngles);
+ %recalc.pln.optGantryAngles = recalc.pln.gantryAngles;
+ recalc.stf = tempStf;
+end
+
+recalc = matRad_recalcApertureInfo(recalc,recalc.apertureInfo);
+
+if ~recalc.interpNew || ~recalc.dijNew
+ tempPln = recalc.pln;
+ tempStf = recalc.stf;
+ for i = 1:numel(tempPln.propStf.gantryAngles)
+ diff = abs(tempPln.propStf.gantryAngles(i)-pln.propStf.gantryAngles);
+ minDiffInd = diff == min(diff);
+ minDiffInd1 = find(tempPln.propStf.gantryAngles == pln.propStf.gantryAngles(find(minDiffInd,1,'first')));
+ minDiffInd2 = find(tempPln.propStf.gantryAngles == pln.propStf.gantryAngles(find(minDiffInd,1,'last')));
+
+ if ~recalc.dijNew
+ if isempty(recalc.stf(i).copyInd)
+ recalc.stf(i) = tempStf(minDiffInd1);
+ recalc.pln.propStf.gantryAngles(i) = tempPln.propStf.gantryAngles(minDiffInd1);
+ elseif recalc.stf(i).copyInd == 1
+ recalc.stf(i) = tempStf(minDiffInd1);
+ recalc.pln.propStf.gantryAngles(i) = tempPln.propStf.gantryAngles(minDiffInd1);
+ elseif recalc.stf(i).copyInd == 2
+ recalc.stf(i) = tempStf(minDiffInd2);
+ recalc.pln.propStf.gantryAngles(i) = tempPln.propStf.gantryAngles(minDiffInd2);
+ end
+ elseif ~recalc.interpNew
+ if numel(minDiffInd) > 1
+ recalc.stf(i).gantryAngle = tempPln.propStf.gantryAngles(i);
+ end
+ end
+ end
+end
+
+% rename to something else? matRad_daoVec2ApertureInfo_recalc?
+recalc.apertureInfo.propVMAT.continuousAperture = recalc.continuousAperture;
+recalc.apertureInfo = matRad_daoVec2ApertureInfo_VMATrecalcDynamic(recalc.apertureInfo,recalc.apertureInfo.apertureVector);
+
+if calcDoseDirect
+ clear global
+ recalc.resultGUI = matRad_calcDoseDirect(ct,recalc.stf,recalc.pln,cst,recalc.apertureInfo.bixelWeights);
+else
+ recalc.resultGUI.w = recalc.apertureInfo.bixelWeights;
+
+ options.numOfScenarios = 1;
+ options.bioOpt = 'none';
+ dij.scaleFactor = apertureInfo.weightToMU./dij.weightToMU;
+ d = matRad_backProjection(recalc.resultGUI.w,dij,options);
+ recalc.resultGUI.physicalDose = reshape(d{1},dij.dimensions);
+end
diff --git a/matRad/matRad_fluenceOptimization.m b/matRad/matRad_fluenceOptimization.m
index 640b43e7c..84db4e82e 100644
--- a/matRad/matRad_fluenceOptimization.m
+++ b/matRad/matRad_fluenceOptimization.m
@@ -1,4 +1,4 @@
-function [resultGUI,optimizer] = matRad_fluenceOptimization(dij,cst,pln,wInit)
+function [resultGUI,optimizer] = matRad_fluenceOptimization(dij,cst,pln,stf,wInit)
% matRad inverse planning wrapper function
%
% call
@@ -32,6 +32,22 @@
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% input argument handling
+if nargin >= 4
+ % Check whether the 4th argument is a structure.
+ if ~isstruct(stf)
+ % If it is not, then assign it to wInit.
+ tmp = stf;
+ if nargin >= 5
+ % If there are 5 arguments, swap stf and wInit.
+ stf = wInit;
+ end
+ wInit = tmp;
+ clear tmp
+ end
+end
+
+
matRad_cfg = MatRad_Config.instance();
% consider VOI priorities
@@ -375,6 +391,44 @@
matRad_cfg.dispInfo('Using standard MU bounds of [0,Inf]!\n')
end
+if isfield(pln.propOpt,'runVMAT') && pln.propOpt.runVMAT
+ % Only the bixels belonging to FMO gantry angles should have their
+ % weights optimized. The rest should be initialized and bounded to
+ % zero.
+
+ % wInit is already defined, but some of the beam weights will be set to
+ % 0. The remainder should be scaled such that the total weight is
+ % preserved. In doing this, do not assume that all of the weights in wInit
+ % are equal.
+ totalWeightInit = sum(wInit);
+
+ % Loop through angles to find non-FMO beams.
+ offset = 0;
+ for i = 1:dij.numOfBeams
+
+ if ~stf(i).propVMAT.FMOBeam
+ % This is not an FMO beam. Set wOnes for the bixels belonging
+ % to this beam to 0.
+ rayIndices = offset + (1:dij.numOfRaysPerBeam(i));
+ wOnes(rayIndices) = 0;
+ end
+
+ offset = offset + dij.numOfRaysPerBeam(i);
+ end
+
+ % Zero out bixels in wInit.
+ wInit = wInit .* wOnes;
+
+ % Rescale wInit to preserve sum.
+ wInit = wInit .* totalWeightInit ./ sum(wInit);
+
+ % Set upper bound on bixels belonging to FMO angles to Inf; the rest,
+ % to 0.
+ optiProb.maximumW = wOnes;
+ optiProb.maximumW(optiProb.maximumW == 1) = Inf;
+
+end
+
if ~isfield(pln.propOpt,'optimizer')
%While the default optimizer is IPOPT, we can try to fallback to
%fmincon in case it does not work for some reason
diff --git a/matRad/matRad_preconditionFactors.m b/matRad/matRad_preconditionFactors.m
new file mode 100644
index 000000000..1a167ffe8
--- /dev/null
+++ b/matRad/matRad_preconditionFactors.m
@@ -0,0 +1,75 @@
+function apertureInfo = matRad_preconditionFactors(apertureInfo)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Calculate preconditioning factors for DAO (only what Esther Wild called
+% the Jacobi preconditioner, not the dij scaled). Scale weights in vector
+% accordingly.
+%
+% call
+% apertureInfo =
+% matRad_preconditionFactors(apertureInfo)
+%
+% input
+% apertureInfo: aperture shape info struct
+%
+% output
+% apertureInfo: aperture shape info struct with new factors
+%
+% References
+% [1] http://onlinelibrary.wiley.com/doi/10.1118/1.4914863/full
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% This is the dij scaling factor which will be applied during DAO. It is
+% given by the dividing the mean of the actual aperture weights by the
+% bixel width. This factor will divide all of the aperture weights.
+
+% TODO: could probably integrate dijScaleFactor into jacobiScale. These
+% were originally separated for research purposes. It would greatly
+% simplify the code to have them all together.
+dijScaleFactor = mean(apertureInfo.apertureVector(1:apertureInfo.totalNumOfShapes)./apertureInfo.jacobiScale)/(apertureInfo.bixelWidth);
+
+for i = 1:numel(apertureInfo.beam)
+
+ if ~apertureInfo.runVMAT || (apertureInfo.runVMAT && apertureInfo.propVMAT.beam(i).DAOBeam)
+ % in other words, do this for every beam if it's not VMAT, and for
+ % optimized beams only if it is
+
+ for j = 1:apertureInfo.beam(i).numOfShapes
+
+ % To get the jacobi scaling factor, first factor the
+ % current aperture's weight out of the dijScaling factor. Also
+ % remove the bixel width. Now we have the mean weight relative
+ % to the current weight.
+ % Next, multiply by the sqrt of ~approximately the number of
+ % open bixels (slight modification to Esther Wild's formula).
+ % The variables corresponding to the aperture weights will be
+ % multiplied by this number, which will decrease the gradients.
+
+ if apertureInfo.runVMAT
+ apertureInfo.beam(i).shape(j).jacobiScale = (dijScaleFactor./apertureInfo.beam(i).shape(j).weight).*sqrt(sum(apertureInfo.beam(i).shape(j).shapeMap(:).^2)./apertureInfo.beam(i).shape(j).sumGradSq);
+ else
+ apertureInfo.beam(i).shape(j).jacobiScale = (dijScaleFactor.*apertureInfo.bixelWidth./apertureInfo.beam(i).shape(j).weight).*sqrt(sum(apertureInfo.beam(i).shape(j).shapeMap(:).^2));
+ end
+ apertureInfo.jacobiScale(apertureInfo.beam(i).shape(j).weightOffset) = apertureInfo.beam(i).shape(j).jacobiScale;
+
+ apertureInfo.apertureVector(apertureInfo.beam(i).shape(j).weightOffset) = apertureInfo.beam(i).shape(j).jacobiScale*apertureInfo.beam(i).shape(j).weight;
+ end
+ end
+end
+
+end
+
+
diff --git a/matRad/matRad_recalcApertureInfo.m b/matRad/matRad_recalcApertureInfo.m
new file mode 100644
index 000000000..3a2b69f97
--- /dev/null
+++ b/matRad/matRad_recalcApertureInfo.m
@@ -0,0 +1,230 @@
+function recalc = matRad_recalcApertureInfo(recalc,apertureInfoOld)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad function to apertures for a different dose resolution
+%
+% call
+% recalc = matRad_recalcApertureInfo(recalc,apertureInfo)
+%
+% input
+% recalc:
+% apertureInfo:
+%
+% output
+% recalc:
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+pln = recalc.pln;
+stf = recalc.stf;
+
+apertureInfoNew = apertureInfoOld;
+apertureInfoNew = rmfield(apertureInfoNew,'beam');
+
+apertureInfoNew.totalNumOfBixels = sum([stf(:).totalNumOfBixels]);
+
+shapeInd = 1;
+
+if recalc.interpNew
+ oldGantryAngles = zeros(1,numel(apertureInfoOld.beam));
+ oldLeftLeafPoss = zeros(apertureInfoOld.beam(1).numOfActiveLeafPairs,numel(apertureInfoOld.beam));
+ oldRightLeafPoss = zeros(apertureInfoOld.beam(1).numOfActiveLeafPairs,numel(apertureInfoOld.beam));
+ for phase = 1:apertureInfoOld.numPhases
+ for i = 1:numel(apertureInfoOld.beam)
+ oldGantryAngles(i) = apertureInfoOld.beam(i).gantryAngle;
+ oldLeftLeafPoss(:,i) = apertureInfoOld.beam(i).shape(1).leftLeafPos;
+ oldRightLeafPoss(:,i) = apertureInfoOld.beam(i).shape(1).rightLeafPos;
+ end
+ end
+end
+
+% MLC parameters:
+bixelWidth = stf(1).bixelWidth; % [mm]
+numOfMLCLeafPairs = 80;
+% define central leaf pair (here we want the 0mm position to be in the
+% center of a leaf pair (e.g. leaf 41 stretches from -2.5mm to 2.5mm
+% for a bixel/leafWidth of 5mm and 81 leaf pairs)
+centralLeafPair = ceil(numOfMLCLeafPairs/2);
+
+% initializing variables
+totalNumOfShapes = numel(stf);
+% loop over all phases
+for phase = 1:apertureInfoOld.numPhases
+ for i = 1:numel(apertureInfoOld.beam)
+ newInd = (apertureInfoOld.propVMAT.beam(i).doseAngleBorders(1) <= [stf.gantryAngle] & [stf.gantryAngle] <= apertureInfoOld.propVMAT.beam(i).doseAngleBorders(2)).*(1:numel([stf.gantryAngle]));
+ newInd(newInd == 0) = [];
+
+ totalAmountOfOldWeight = 0;
+
+ for j = newInd
+ % get x- and z-coordinates of bixels
+ rayPos_bev = reshape([stf(j).ray.rayPos_bev],3,[]);
+ X = rayPos_bev(1,:)';
+ Z = rayPos_bev(3,:)';
+
+ % create ray-map
+ maxX = max(X); minX = min(X);
+ maxZ = max(Z); minZ = min(Z);
+
+ dimX = (maxX-minX)/stf(j).bixelWidth + 1;
+ dimZ = (maxZ-minZ)/stf(j).bixelWidth + 1;
+
+ rayMap = zeros(dimZ,dimX);
+
+ % get indices for x and z positions
+ xPos = (X-minX)/stf(j).bixelWidth + 1;
+ zPos = (Z-minZ)/stf(j).bixelWidth + 1;
+
+ % get indices in the ray-map
+ indInRay = zPos + (xPos-1)*dimZ;
+
+ % fill ray-map
+ rayMap(indInRay) = 1;
+
+ % create map of bixel indices
+ bixelIndMap = NaN * ones(dimZ,dimX);
+ bixelIndMap(indInRay) = [1:stf(j).numOfRays] + (j-1)*stf(1).numOfRays;
+
+ % store physical position of first entry in bixelIndMap
+ posOfCornerBixel = [minX 0 minZ];
+
+ % get leaf limits from the leaf map
+ lim_l = NaN * ones(dimZ,1);
+ lim_r = NaN * ones(dimZ,1);
+ % looping oder leaf pairs
+ for l = 1:dimZ
+ lim_lInd = find(rayMap(l,:),1,'first');
+ lim_rInd = find(rayMap(l,:),1,'last');
+ % the physical position [mm] can be calculated from the indices
+ lim_l(l) = (lim_lInd-1)*bixelWidth + minX - 1/2*bixelWidth;
+ lim_r(l) = (lim_rInd-1)*bixelWidth + minX + 1/2*bixelWidth;
+ end
+
+ leafPairPos = unique(Z);
+
+ % find upmost and downmost leaf pair
+ topLeafPairPos = maxZ;
+ bottomLeafPairPos = minZ;
+
+ topLeafPair = centralLeafPair - topLeafPairPos/bixelWidth;
+ bottomLeafPair = centralLeafPair - bottomLeafPairPos/bixelWidth;
+
+ % create bool map of active leaf pairs
+ isActiveLeafPair = zeros(numOfMLCLeafPairs,1);
+ isActiveLeafPair(topLeafPair:bottomLeafPair) = 1;
+
+ MLCWindow = [minX-bixelWidth/2 maxX+bixelWidth/2 ...
+ minZ-bixelWidth/2 maxZ+bixelWidth/2];
+
+
+ % save data for each beam
+ apertureInfoNew.beam(j).numOfActiveLeafPairs = dimZ;
+ apertureInfoNew.beam(j).leafPairPos = leafPairPos;
+ apertureInfoNew.beam(j).isActiveLeafPair = isActiveLeafPair;
+ apertureInfoNew.beam(j).centralLeafPair = centralLeafPair;
+ apertureInfoNew.beam(j).lim_l = lim_l;
+ apertureInfoNew.beam(j).lim_r = lim_r;
+ apertureInfoNew.beam(j).bixelIndMap = bixelIndMap;
+ apertureInfoNew.beam(j).posOfCornerBixel = posOfCornerBixel;
+ apertureInfoNew.beam(j).MLCWindow = MLCWindow;
+ apertureInfoNew.beam(j).bixOffset = 1+(j-1)*dimZ;
+ apertureInfoNew.beam(j).shape(1).vectorOffset = totalNumOfShapes+1+(j-1)*dimZ;
+
+ %inherit from old beam
+ apertureInfoNew.propVMAT.beam(j).leafDir = apertureInfoOld.propVMAT.beam(i).leafDir;
+
+ %specific to new beam
+ apertureInfoNew.beam(j).gantryAngle = pln.propStf.gantryAngles(j);
+ apertureInfoNew.propVMAT.beam(j).doseAngleBorders = stf(j).propVMAT.doseAngleBorders;
+ apertureInfoNew.propVMAT.beam(j).doseAngleBorderCentreDiff = stf(j).propVMAT.doseAngleBorderCentreDiff;
+ apertureInfoNew.propVMAT.beam(j).doseAngleBordersDiff = stf(j).propVMAT.doseAngleBordersDiff;
+ apertureInfoNew.propVMAT.beam(j).lastDAOIndex = stf(j).propVMAT.lastDAOIndex;
+ apertureInfoNew.propVMAT.beam(j).nextDAOIndex = stf(j).propVMAT.lastDAOIndex;
+
+
+ amountOfOldSpeed = (min(apertureInfoNew.propVMAT.beam(j).doseAngleBorders(2),apertureInfoOld.propVMAT.beam(i).doseAngleBorders(2))-max(apertureInfoNew.propVMAT.beam(j).doseAngleBorders(1),apertureInfoOld.propVMAT.beam(i).doseAngleBorders(1)))./apertureInfoNew.propVMAT.beam(j).doseAngleBordersDiff;
+ amountOfOldWeight = (min(apertureInfoNew.propVMAT.beam(j).doseAngleBorders(2),apertureInfoOld.propVMAT.beam(i).doseAngleBorders(2))-max(apertureInfoNew.propVMAT.beam(j).doseAngleBorders(1),apertureInfoOld.propVMAT.beam(i).doseAngleBorders(1)))./apertureInfoOld.propVMAT.beam(i).doseAngleBordersDiff;
+
+ totalAmountOfOldWeight = totalAmountOfOldWeight+amountOfOldWeight;
+
+ amountOfOldWeight_I = (min(apertureInfoNew.beam(j).gantryAngle,apertureInfoOld.propVMAT.beam(i).doseAngleBorders(2))-max(apertureInfoNew.propVMAT.beam(j).doseAngleBorders(1),apertureInfoOld.propVMAT.beam(i).doseAngleBorders(1)))./apertureInfoOld.propVMAT.beam(i).doseAngleBordersDiff;
+ amountOfOldWeight_F = (min(apertureInfoNew.propVMAT.beam(j).doseAngleBorders(2),apertureInfoOld.propVMAT.beam(i).doseAngleBorders(2))-max(apertureInfoNew.beam(j).gantryAngle,apertureInfoOld.propVMAT.beam(i).doseAngleBorders(1)))./apertureInfoOld.propVMAT.beam(i).doseAngleBordersDiff;
+
+ if ~isfield(apertureInfoNew.beam(j),'gantryRot') || isempty(apertureInfoNew.beam(j).gantryRot)
+ apertureInfoNew.beam(j).gantryRot = 0;
+ apertureInfoNew.beam(j).shape(1).weight = 0;
+ apertureInfoNew.beam(j).shape(1).weight_I = 0;
+ apertureInfoNew.beam(j).shape(1).weight_F = 0;
+ end
+ apertureInfoNew.beam(j).gantryRot = amountOfOldSpeed*apertureInfoOld.beam(i).gantryRot+apertureInfoNew.beam(j).gantryRot;
+
+ %recalculate weight, MU
+ apertureInfoNew.beam(j).shape(1).weight = apertureInfoNew.beam(j).shape(1).weight+amountOfOldWeight*apertureInfoOld.beam(i).shape(1).weight;
+ apertureInfoNew.beam(j).shape(1).weight_I = apertureInfoNew.beam(j).shape(1).weight_I+amountOfOldWeight_I*apertureInfoOld.beam(i).shape(1).weight;
+ apertureInfoNew.beam(j).shape(1).weight_F = apertureInfoNew.beam(j).shape(1).weight_F+amountOfOldWeight_F*apertureInfoOld.beam(i).shape(1).weight;
+ apertureInfoNew.beam(j).MU = apertureInfoNew.beam(j).shape(1).weight.*apertureInfoNew.weightToMU;
+
+ apertureInfoNew.beam(j).MURate = apertureInfoNew.beam(j).MU.*apertureInfoNew.beam(j).gantryRot./apertureInfoNew.propVMAT.beam(j).doseAngleBordersDiff;
+
+ %apertureInfoNew.beam(j).shape(1).jacobiScale = apertureInfoOld.beam(i).shape(1).jacobiScale;
+ apertureInfoNew.jacobiScale(j) = 1;
+ apertureInfoNew.beam(j).shape(1).jacobiScale = 1;
+
+ if recalc.interpNew
+ %interpolate new apertures now so that weights are not
+ %overwritten
+ apertureInfoNew.beam(j).shape(1).leftLeafPos = (interp1(oldGantryAngles',oldLeftLeafPoss',apertureInfoNew.beam(j).gantryAngle))';
+ apertureInfoNew.beam(j).shape(1).rightLeafPos = (interp1(oldGantryAngles',oldRightLeafPoss',apertureInfoNew.beam(j).gantryAngle))';
+
+ apertureInfoNew.beam(j).shape(1).leftLeafPos_I = (interp1(oldGantryAngles',oldLeftLeafPoss',apertureInfoNew.propVMAT.beam(j).doseAngleBorders(1)))';
+ apertureInfoNew.beam(j).shape(1).rightLeafPos_I = (interp1(oldGantryAngles',oldRightLeafPoss',apertureInfoNew.propVMAT.beam(j).doseAngleBorders(1)))';
+
+ apertureInfoNew.beam(j).shape(1).leftLeafPos_F = (interp1(oldGantryAngles',oldLeftLeafPoss',apertureInfoNew.propVMAT.beam(j).doseAngleBorders(2)))';
+ apertureInfoNew.beam(j).shape(1).rightLeafPos_F = (interp1(oldGantryAngles',oldRightLeafPoss',apertureInfoNew.propVMAT.beam(j).doseAngleBorders(2)))';
+ else
+ apertureInfoNew.beam(j).shape(1).leftLeafPos = apertureInfoOld.beam(i).shape(1).leftLeafPos;
+ apertureInfoNew.beam(j).shape(1).rightLeafPos = apertureInfoOld.beam(i).shape(1).rightLeafPos;
+ end
+
+ %all beams are now "optimized" to prevent their weights from being
+ %overwritten
+ %optAngleBorders becomes doseAngleBorders
+ apertureInfoNew.beam(j).numOfShapes = 1;
+ apertureInfoNew.propVMAT.beam(j).DAOBeam = true;
+ apertureInfoNew.propVMAT.beam(j).DAOAngleBorders = stf(j).propVMAT.doseAngleBorders;
+ apertureInfoNew.propVMAT.beam(j).DAOAngleBorderCentreDiff = stf(j).propVMAT.doseAngleBorderCentreDiff;
+ apertureInfoNew.propVMAT.beam(j).DAOAngleBordersDiff = stf(j).propVMAT.doseAngleBordersDiff;
+ apertureInfoNew.propVMAT.beam(j).timeFacCurr = apertureInfoNew.propVMAT.beam(j).doseAngleBordersDiff./apertureInfoNew.propVMAT.beam(j).DAOAngleBordersDiff; % = 1
+
+ apertureInfoNew.apertureVector(shapeInd) = apertureInfoNew.beam(j).shape(1).weight;
+ shapeInd = shapeInd+1;
+ end
+
+ end
+end
+
+apertureInfoNew.totalNumOfShapes = sum([apertureInfoNew.beam.numOfShapes]);
+apertureInfoNew.totalNumOfLeafPairs = sum([apertureInfoNew.beam.numOfShapes]*[apertureInfoNew.beam.numOfActiveLeafPairs]');
+apertureInfoNew.doseTotalNumOfLeafPairs = sum([apertureInfoNew.beam(:).numOfActiveLeafPairs]);
+apertureInfoNew.totalNumOfOptBixels = apertureInfoNew.totalNumOfBixels;
+
+%recalc apertureVector
+[apertureInfoNew.apertureVector, apertureInfoNew.mappingMx, apertureInfoNew.limMx] = matRad_daoApertureInfo2Vec(apertureInfoNew);
+
+recalc.apertureInfo = apertureInfoNew;
+recalc.stf = stf;
\ No newline at end of file
diff --git a/matRad/matRad_sequencing.m b/matRad/matRad_sequencing.m
index 51207ada2..85fc7df75 100644
--- a/matRad/matRad_sequencing.m
+++ b/matRad/matRad_sequencing.m
@@ -48,18 +48,24 @@
matRad_cfg.dispWarning ('pln.propSeq.sequencer not specified. Using siochi leaf sequencing (default).')
end
- if ~isfield(pln.propSeq, 'sequencingLevel')
- pln.propSeq.sequencingLevel = 5;
- matRad_cfg.dispWarning ('pln.propSeq.sequencingLevel not specified. Using 5 sequencing levels (default).')
+
+ if ~any(isfield(pln.propSeq, {'numLevels','sequencingLevel'}))
+ pln.propSeq.numLevels = 5;
+ matRad_cfg.dispWarning ('pln.propSeq.sequencingLevel not specified. Using 5 sequencing levels (default).')
+ elseif isfield(pln.propSeq,'sequencingLevel')
+ matRad_cfg.dispDeprecationWarning('The pln.propSeq.sequencingLevel property is deprecated. Use pln.propSeq.numLevels instead!');
+ pln.propSeq.numLevels = pln.propSeq.sequencingLevel;
end
+ % Could probably consolidate a lot of the code in the following
+ % functions.
switch pln.propSeq.sequencer
case 'xia'
- resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,pln.propSeq.sequencingLevel,visBool);
+ resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,pln,visBool);
case 'engel'
- resultGUI = matRad_engelLeafSequencing(resultGUI,stf,dij,pln.propSeq.sequencingLevel,visBool);
+ resultGUI = matRad_engelLeafSequencing(resultGUI,stf,dij,pln,visBool);
case 'siochi'
- resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,pln.propSeq.sequencingLevel,visBool);
+ resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,pln,visBool);
otherwise
matRad_cfg.dispError('Could not find specified sequencing algorithm ''%s''',pln.propSeq.sequencer);
end
diff --git a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m
index 66a5bec0f..5dea7ae99 100644
--- a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m
@@ -39,12 +39,14 @@
apertureInfo = optiProb.apertureInfo;
% value of constraints for leaves
-leftLeafPos = apertureInfoVec([1:apertureInfo.totalNumOfLeafPairs]+apertureInfo.totalNumOfShapes);
-rightLeafPos = apertureInfoVec(1+apertureInfo.totalNumOfLeafPairs+apertureInfo.totalNumOfShapes:end);
+leftLeafPos = apertureInfoVec((1:apertureInfo.totalNumOfLeafPairs)+apertureInfo.totalNumOfShapes);
+rightLeafPos = apertureInfoVec((1+apertureInfo.totalNumOfLeafPairs+apertureInfo.totalNumOfShapes):(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2));
c_dao = rightLeafPos - leftLeafPos;
+
% bixel based objective function calculation
c_dos = matRad_constraintFunctions@matRad_OptimizationProblem(optiProb,apertureInfo.bixelWeights,dij,cst);
+
% concatenate
-c = [c_dao; c_dos];
+c = [c_dos; c_dao];
diff --git a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m
index 30bed9248..5798da5e6 100644
--- a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m
@@ -41,15 +41,15 @@
% row indices
i = repmat(1:apertureInfo.totalNumOfLeafPairs,1,2);
% column indices
-j = [apertureInfo.totalNumOfShapes+1:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs ...
- apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+1:apertureInfo.totalNumOfShapes+2*apertureInfo.totalNumOfLeafPairs];
-
+j = [(apertureInfo.totalNumOfShapes+1):(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs) ...
+ ((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs)+1):(apertureInfo.totalNumOfShapes+2*apertureInfo.totalNumOfLeafPairs)];
+
% -1 for left leaves, 1 for right leaves
s = [-1*ones(1,apertureInfo.totalNumOfLeafPairs) ones(1,apertureInfo.totalNumOfLeafPairs)];
jacob_dao = sparse(i,j,s, ...
apertureInfo.totalNumOfLeafPairs, ...
- apertureInfo.totalNumOfShapes+2*apertureInfo.totalNumOfLeafPairs, ...
+ numel(apertureInfoVec), ...
2*apertureInfo.totalNumOfLeafPairs);
% compute jacobian of dosimetric constrainst
@@ -57,41 +57,69 @@
% dosimetric jacobian in bixel space
jacob_dos_bixel = matRad_constraintJacobian@matRad_OptimizationProblem(optiProb,apertureInfo.bixelWeights,dij,cst);
-% allocate sparse matrix for dosimetric jacobian
-jacob_dos = sparse(size(jacob_dos_bixel,1),numel(apertureInfoVec));
-if ~isempty(jacob_dos)
+if ~isempty(jacob_dos_bixel)
+ %If we would have the apertureInfo.bixelJApVec in DAO, we could use
+ %this instead of the full if branch
+ %jacob_dos = jacob_dos_bixel*apertureInfo.bixelJApVec';
+
+ numOfConstraints = size(jacob_dos_bixel,1);
+
+ i_sparse = 1:numOfConstraints;
+ i_sparse = kron(i_sparse,ones(1,numel(apertureInfoVec)));
+
+ j_sparse = 1:numel(apertureInfoVec);
+ j_sparse = repmat(j_sparse,1,numOfConstraints);
+
+ jacobSparseVec = zeros(numOfConstraints*size(apertureInfoVec,1),1);
+
% 1. calculate jacobian for aperture weights
% loop over all beams
- offset = 0;
+ conOffset = 0;
for i = 1:numel(apertureInfo.beam)
-
+
% get used bixels in beam
ix = ~isnan(apertureInfo.beam(i).bixelIndMap);
-
+
% loop over all shapes and add up the gradients x openingFrac for this shape
- for j = 1:apertureInfo.beam(i).numOfShapes
- jacob_dos(:,offset+j) = jacob_dos_bixel(:,apertureInfo.beam(i).bixelIndMap(ix)) ...
- * apertureInfo.beam(i).shape(j).shapeMap(ix);
+ for j = 1:apertureInfo.beam(i).numOfShapes
+
+ jacobSparseVec(conOffset+j == j_sparse) = jacob_dos_bixel(:,apertureInfo.beam(i).bixelIndMap(ix)) ...
+ * apertureInfo.beam(i).shape(j).shapeMap(ix)./apertureInfo.beam(i).shape(j).jacobiScale;
end
-
+
% increment offset
- offset = offset + apertureInfo.beam(i).numOfShapes;
-
+ conOffset = conOffset + apertureInfo.beam(i).numOfShapes;
+
end
-
- % 2. find corresponding bixel to the leaf Positions and aperture
+
+ % 2. find corresponding bixel to the leaf Positions and aperture
% weights to calculate the jacobian
- jacob_dos(:,apertureInfo.totalNumOfShapes+1:end) = ...
- ( ones(size(jacob_dos,1),1) * apertureInfoVec(apertureInfo.mappingMx(apertureInfo.totalNumOfShapes+1:end,2))' ) ...
- .* jacob_dos_bixel(:,apertureInfo.bixelIndices(apertureInfo.totalNumOfShapes+1:end)) / apertureInfo.bixelWidth;
-
+
+ ixAperturesOnly = apertureInfo.totalNumOfShapes+1:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2; %The first entries in most of the vectors denote shape weights
+
+ indInSparseVec = repmat(ixAperturesOnly,1,numOfConstraints) ...
+ +kron((0:numOfConstraints-1)*numel(apertureInfoVec),ones(1,apertureInfo.totalNumOfLeafPairs*2));
+
+ jacobSparseVec(indInSparseVec) = ...
+ reshape(transpose(( ones(numOfConstraints,1) * apertureInfoVec(apertureInfo.mappingMx(ixAperturesOnly,2))' ) ...
+ .* jacob_dos_bixel(:,apertureInfo.bixelIndices) ./ ...
+ (ones(numOfConstraints,1) * (apertureInfo.bixelWidth.*apertureInfo.jacobiScale(apertureInfo.mappingMx(ixAperturesOnly,2)))')),[],1);
+
+
% correct the sign for the left leaf positions
- jacob_dos(:,apertureInfo.totalNumOfShapes+1:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs) = ...
- -jacob_dos(:,apertureInfo.totalNumOfShapes+1:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs);
-
+ %indInSparseVec = repmat(apertureInfo.totalNumOfShapes+1:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs,1,numOfConstraints) ...
+ indInSparseVec = repmat(ixAperturesOnly(1:apertureInfo.totalNumOfLeafPairs),1,numOfConstraints) ...
+ +kron((0:numOfConstraints-1)*numel(apertureInfoVec),ones(1,apertureInfo.totalNumOfLeafPairs));
+
+ jacobSparseVec(indInSparseVec) = -jacobSparseVec(indInSparseVec);
+
+ jacob_dos = sparse(i_sparse,j_sparse,jacobSparseVec,numOfConstraints,numel(apertureInfoVec));
+else
+ jacob_dos = sparse(0,0);
end
+
% concatenate
-jacob = [jacob_dao;jacob_dos];
+jacob = [jacob_dos; jacob_dao];
diff --git a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m
index ee8fca784..fd7a3ee70 100644
--- a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m
@@ -16,7 +16,7 @@
% aperture weights (0/inf) and leav positions (custom)
%
% References
-%
+%
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -37,17 +37,26 @@
% first: aperature weights
% second: left leaf positions
% third: right leaf positions
+% fourth (VMAT only): times between successive DAO gantry angles
% initializing variables
-apertureInfoVec = NaN * ones(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2,1);
+vecLength = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2);
+
+if apertureInfo.runVMAT
+ vecLength = vecLength+apertureInfo.totalNumOfShapes; %Extra set of (apertureInfo.totalNumOfShapes) number of elements, allowing arc sector times to be optimized
+end
+
+apertureInfoVec = NaN * ones(vecLength,1);
offset = 0;
%% 1. aperture weights
for i = 1:size(apertureInfo.beam,2)
for j = 1:apertureInfo.beam(i).numOfShapes
- apertureInfoVec(offset+j) = apertureInfo.beam(i).shape(j).weight;
+
+ apertureInfoVec(offset+j) = apertureInfo.beam(i).shape(j).jacobiScale*apertureInfo.beam(i).shape(j).weight; %In VMAT, this weight is "spread" over unoptimized beams (assume constant dose rate over sector)
+
end
offset = offset + apertureInfo.beam(i).numOfShapes;
end
@@ -56,24 +65,74 @@
%% fill the vector for all shapes of all beams
for i = 1:size(apertureInfo.beam,2)
for j = 1:apertureInfo.beam(i).numOfShapes
- apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]) = apertureInfo.beam(i).shape(j).leftLeafPos;
- apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]+apertureInfo.totalNumOfLeafPairs) = apertureInfo.beam(i).shape(j).rightLeafPos;
- offset = offset + apertureInfo.beam(i).numOfActiveLeafPairs;
+
+ if ~apertureInfo.runVMAT || ~apertureInfo.propVMAT.continuousAperture
+
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]) = apertureInfo.beam(i).shape(j).leftLeafPos;
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]+apertureInfo.totalNumOfLeafPairs) = apertureInfo.beam(i).shape(j).rightLeafPos;
+
+ offset = offset + apertureInfo.beam(i).numOfActiveLeafPairs;
+ else
+
+ if apertureInfo.propVMAT.beam(i).doseAngleDAO(1)
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]) = apertureInfo.beam(i).shape(j).leftLeafPos_I;
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]+apertureInfo.totalNumOfLeafPairs) = apertureInfo.beam(i).shape(j).rightLeafPos_I;
+
+ offset = offset + apertureInfo.beam(i).numOfActiveLeafPairs;
+ end
+
+ if apertureInfo.propVMAT.beam(i).doseAngleDAO(2)
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]) = apertureInfo.beam(i).shape(j).leftLeafPos_F;
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]+apertureInfo.totalNumOfLeafPairs) = apertureInfo.beam(i).shape(j).rightLeafPos_F;
+
+ offset = offset + apertureInfo.beam(i).numOfActiveLeafPairs;
+ end
+ end
+
end
end
+%% 3. time of arc sector/beam
+if apertureInfo.runVMAT
+ offset = offset + apertureInfo.totalNumOfLeafPairs;
+
+ %this gives a vector of the arc lengths belonging to each optimized CP
+ %unique gets rid of double-counted angles (which is every interior
+ %angle)
+
+ optInd = [apertureInfo.propVMAT.beam.DAOBeam];
+ optAngleLengths = [apertureInfo.propVMAT.beam(optInd).DAOAngleBordersDiff];
+ optGantryRot = [apertureInfo.beam(optInd).gantryRot];
+ apertureInfoVec((offset+1):end) = optAngleLengths./optGantryRot; %entries are the times until the next opt gantry angle is reached
+end
-%% 3. create additional information for later use
+%% 4. create additional information for later use
if nargout > 1
- mappingMx = NaN * ones(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2,4);
- limMx = NaN * ones(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2,2);
- limMx(1:apertureInfo.totalNumOfShapes,:) = ones(apertureInfo.totalNumOfShapes,1)*[0 inf];
+ mappingMx = NaN * ones(vecLength,4);
+ limMx = NaN * ones(vecLength,2);
+
+ limMx(1:(apertureInfo.totalNumOfShapes),:) = ones((apertureInfo.totalNumOfShapes),1)*[0 inf];
counter = 1;
+
for i = 1:numel(apertureInfo.beam)
for j = 1:apertureInfo.beam(i).numOfShapes
mappingMx(counter,1) = i;
+ if apertureInfo.runVMAT
+ fileName = apertureInfo.propVMAT.machineConstraintFile;
+ try
+ load(fileName,'machine');
+ catch
+ error(['Could not find the following machine file: ' fileName ]);
+ end
+
+ timeLimL = diff(apertureInfo.propVMAT.beam(i).DAOAngleBorders)/machine.constraints.gantryRotationSpeed(2); %Minimum time interval between two optimized beams/gantry angles
+ timeLimU = diff(apertureInfo.propVMAT.beam(i).DAOAngleBorders)/machine.constraints.gantryRotationSpeed(1); %Maximum time interval between two optimized beams/gantry angles
+
+ mappingMx(counter+(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2),1) = i;
+ limMx(counter+(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2),:) = [timeLimL timeLimU];
+ end
counter = counter + 1;
end
end
@@ -90,12 +149,30 @@
limMx(counter,1) = apertureInfo.beam(i).lim_l(k);
limMx(counter,2) = apertureInfo.beam(i).lim_r(k);
counter = counter + 1;
+
+ if apertureInfo.runVMAT && apertureInfo.propVMAT.continuousAperture && nnz(apertureInfo.propVMAT.beam(i).doseAngleDAO) == 2
+ %redo for initial and final leaf positions
+ %might have to revisit this after looking at gradient,
+ %esp. mappingMx(counter,2)
+ %only an issue for non-interpolated deliveries
+ mappingMx(counter,1) = i;
+ mappingMx(counter,2) = j + shapeOffset; % store global shape number for grad calc
+ mappingMx(counter,3) = j; % store local shape number
+ mappingMx(counter,4) = k; % store local leaf number
+
+ limMx(counter,1) = apertureInfo.beam(i).lim_l(k);
+ limMx(counter,2) = apertureInfo.beam(i).lim_r(k);
+ counter = counter + 1;
+ end
end
end
shapeOffset = shapeOffset + apertureInfo.beam(i).numOfShapes;
end
- mappingMx(counter:end,:) = mappingMx(apertureInfo.totalNumOfShapes+1:counter-1,:);
- limMx(counter:end,:) = limMx(apertureInfo.totalNumOfShapes+1:counter-1,:);
+ mappingMx(counter:(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2),:) = mappingMx(apertureInfo.totalNumOfShapes+1:counter-1,:);
+ limMx(counter:(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2),:) = limMx(apertureInfo.totalNumOfShapes+1:counter-1,:);
+
+end
end
+
diff --git a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m
index e7741fa73..2625e8bd3 100644
--- a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoVec2ApertureInfo.m
@@ -6,7 +6,7 @@
% tips and bixel indices for gradient calculation
%
% call
-% updatedInfo = matRad_daoVec2ApertureInfo(apertureInfo,apertureInfoVect)
+% [updatedInfo,w,indVect] = matRad_daoVec2ApertureInfo(apertureInfo,apertureInfoVect)
%
% input
% apertureInfo: aperture shape info struct
@@ -16,7 +16,7 @@
% updatedInfo: updated aperture shape info struct according to apertureInfoVect
%
% References
-%
+%
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -36,109 +36,435 @@
% function to update the apertureInfo struct after the each iteraton of the
% optimization
-w = zeros(apertureInfo.totalNumOfBixels,1);
-
% initializing variables
updatedInfo = apertureInfo;
updatedInfo.apertureVector = apertureInfoVect;
-shapeInd = 1;
+if ~updatedInfo.runVMAT
+ shapeInd = 1;
+
+ indVect = NaN*ones(2*apertureInfo.doseTotalNumOfLeafPairs,1);
+ offset = 0;
+
+ % helper function to cope with numerical instabilities through rounding
+ round2 = @(a,b) round(a*10^b)/10^b;
+else
+
+ % options for bixel and Jacobian calculation
+ mlcOptions.bixelWidth = apertureInfo.bixelWidth;
+ calcOptions.continuousAperture = updatedInfo.propVMAT.continuousAperture;
+ vectorIndices.totalNumOfShapes = apertureInfo.totalNumOfShapes;
+end
-indVect = NaN*ones(apertureInfo.totalNumOfShapes + apertureInfo.totalNumOfLeafPairs,1);
+w = zeros(apertureInfo.totalNumOfBixels,1);
-% helper function to cope with numerical instabilities through rounding
-round2 = @(a,b) round(a*10^b)/10^b;
+if updatedInfo.runVMAT && ~all([updatedInfo.propVMAT.beam.DAOBeam])
+ j = 1;
+ for i = 1:numel(updatedInfo.beam)
+ if updatedInfo.propVMAT.beam(i).DAOBeam
+ % update the shape weight
+ % rescale the weight from the vector using the previous
+ % iteration scaling factor
+ updatedInfo.beam(i).shape(j).weight = apertureInfoVect(updatedInfo.beam(i).shape(j).weightOffset)./updatedInfo.beam(i).shape(j).jacobiScale;
+
+ updatedInfo.beam(i).shape(j).MU = updatedInfo.beam(i).shape(j).weight*updatedInfo.weightToMU;
+ updatedInfo.beam(i).time = apertureInfoVect((updatedInfo.totalNumOfShapes+updatedInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(i).DAOIndex)*updatedInfo.propVMAT.beam(i).timeFacCurr;
+ updatedInfo.beam(i).gantryRot = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff/updatedInfo.beam(i).time;
+ updatedInfo.beam(i).shape(j).MURate = updatedInfo.beam(i).shape(j).MU./updatedInfo.beam(i).time;
+ end
+ end
+end
+if updatedInfo.runVMAT
+ %% ONLY SUPPORTED IN VMAT FOR NOW
+
+ % Jacobian matrix to be used in the DAO gradient function
+ % this tells us the gradient of a particular bixel with respect to an
+ % element in the apertureVector (aperture weight or leaf position)
+ % store as a vector for now, convert to sparse matrix later
+
+ optBixelFactor = 7;
+ % For optimized beams: 5 = (1 from weights) + (3 from left leaf positions (I, M, and F)) + (3 from
+ % right leaf positions (I, M, and F))
+
+ if updatedInfo.runVMAT
+ intBixelFactor = 2*optBixelFactor+2;
+ % For interpolated beams: multiply this number times 2 (influenced by the
+ % one before and the one after), then add 2 (influenced by the time of the
+ % times before and after)
+ else
+ intBixelFactor = 2*optBixelFactor;
+ % For interpolated beams: multiply this number times 2 (influenced by the
+ % one before and the one after)
+ end
+
+ % for the time (probability) gradients
+ optBixelFactor = optBixelFactor+apertureInfo.totalNumOfShapes;
+ intBixelFactor = intBixelFactor+apertureInfo.totalNumOfShapes;
+
+ bixelJApVec_sz = (updatedInfo.totalNumOfOptBixels*optBixelFactor+(updatedInfo.totalNumOfBixels-updatedInfo.totalNumOfOptBixels)*intBixelFactor)*2;
+
+ bixelJApVec_vec = zeros(1,bixelJApVec_sz);
+
+ % vector indices
+ bixelJApVec_i = nan(1,bixelJApVec_sz);
+ % bixel indices
+ bixelJApVec_j = zeros(1,bixelJApVec_sz);
+ % offset
+ bixelJApVec_offset = 0;
+end
%% update the shapeMaps
% here the new colimator positions are used to create new shapeMaps that
% now include decimal values instead of binary
+calcOptions.saveJacobian = true;
+
% loop over all beams
for i = 1:numel(updatedInfo.beam)
%posOfRightCornerPixel = apertureInfo.beam(i).posOfCornerBixel(1) + (size(apertureInfo.beam(i).bixelIndMap,2)-1)*apertureInfo.bixelWidth;
-
+
% pre compute left and right bixel edges
edges_l = updatedInfo.beam(i).posOfCornerBixel(1)...
- + ([1:size(apertureInfo.beam(i).bixelIndMap,2)]-1-1/2)*updatedInfo.bixelWidth;
+ + ((1:size(apertureInfo.beam(i).bixelIndMap,2))-1-1/2)*updatedInfo.bixelWidth;
edges_r = updatedInfo.beam(i).posOfCornerBixel(1)...
- + ([1:size(apertureInfo.beam(i).bixelIndMap,2)]-1+1/2)*updatedInfo.bixelWidth;
+ + ((1:size(apertureInfo.beam(i).bixelIndMap,2))-1+1/2)*updatedInfo.bixelWidth;
+
+ % get dimensions of 2d matrices that store shape/bixel information
+ n = apertureInfo.beam(i).numOfActiveLeafPairs;
% loop over all shapes
- for j = 1:updatedInfo.beam(i).numOfShapes
-
- % update the shape weight
- updatedInfo.beam(i).shape(j).weight = apertureInfoVect(shapeInd);
-
- % get dimensions of 2d matrices that store shape/bixel information
- n = apertureInfo.beam(i).numOfActiveLeafPairs;
-
- % extract left and right leaf positions from shape vector
- vectorIx = updatedInfo.beam(i).shape(j).vectorOffset + ([1:n]-1);
- leftLeafPos = apertureInfoVect(vectorIx);
- rightLeafPos = apertureInfoVect(vectorIx+apertureInfo.totalNumOfLeafPairs);
-
- % update information in shape structure
- updatedInfo.beam(i).shape(j).leftLeafPos = leftLeafPos;
- updatedInfo.beam(i).shape(j).rightLeafPos = rightLeafPos;
-
- % rounding for numerical stability
- leftLeafPos = round2(leftLeafPos,6);
- rightLeafPos = round2(rightLeafPos,6);
-
- %
- xPosIndLeftLeaf = round((leftLeafPos - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth + 1);
- xPosIndRightLeaf = round((rightLeafPos - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth + 1);
-
- % check limits because of rounding off issues at maximum, i.e.,
- % enforce round(X.5) -> X
- xPosIndLeftLeaf(leftLeafPos == apertureInfo.beam(i).lim_r) = ...
- .5 + (leftLeafPos(leftLeafPos == apertureInfo.beam(i).lim_r) ...
- - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth;
- xPosIndRightLeaf(rightLeafPos == apertureInfo.beam(i).lim_r) = ...
- .5 + (rightLeafPos(rightLeafPos == apertureInfo.beam(i).lim_r) ...
- - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth;
-
- % find the bixel index that the leaves currently touch
- bixelIndLeftLeaf = apertureInfo.beam(i).bixelIndMap((xPosIndLeftLeaf-1)*n+[1:n]');
- bixelIndRightLeaf = apertureInfo.beam(i).bixelIndMap((xPosIndRightLeaf-1)*n+[1:n]');
+ if updatedInfo.runVMAT
+ numOfShapes = 1;
+ calcOptions.DAOBeam = updatedInfo.propVMAT.beam(i).DAOBeam;
+ else
+ numOfShapes = updatedInfo.beam(i).numOfShapes;
+ end
+
+ mlcOptions.lim_l = apertureInfo.beam(i).lim_l;
+ mlcOptions.lim_r = apertureInfo.beam(i).lim_r;
+ mlcOptions.edges_l = edges_l;
+ mlcOptions.edges_r = edges_r;
+ mlcOptions.centres = (edges_l+edges_r)/2;
+ mlcOptions.widths = edges_r-edges_l;
+ mlcOptions.n = n;
+ mlcOptions.numBix = size(apertureInfo.beam(i).bixelIndMap,2);
+ mlcOptions.bixelIndMap = apertureInfo.beam(i).bixelIndMap;
+
+ for j = 1:numOfShapes
- if any(isnan(bixelIndLeftLeaf)) || any(isnan(bixelIndRightLeaf))
- error('cannot map leaf position to bixel index');
+ if ~updatedInfo.runVMAT || updatedInfo.propVMAT.beam(i).DAOBeam
+ % either this is not VMAT, or if it is VMAT, this is a DAO beam
+
+ % update the shape weight
+ updatedInfo.beam(i).shape(j).weight = apertureInfoVect(updatedInfo.beam(i).shape(j).weightOffset)./updatedInfo.beam(i).shape(j).jacobiScale;
+
+ if updatedInfo.runVMAT
+ updatedInfo.beam(i).shape(j).MU = updatedInfo.beam(i).shape(j).weight*updatedInfo.weightToMU;
+ updatedInfo.beam(i).time = apertureInfoVect((updatedInfo.totalNumOfShapes+updatedInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(i).DAOIndex)*updatedInfo.propVMAT.beam(i).timeFacCurr;
+ updatedInfo.beam(i).gantryRot = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff/updatedInfo.beam(i).time;
+ updatedInfo.beam(i).shape(j).MURate = updatedInfo.beam(i).shape(j).MU./updatedInfo.beam(i).time;
+ end
+
+ if ~updatedInfo.runVMAT || ~updatedInfo.propVMAT.continuousAperture
+ % extract left and right leaf positions from shape vector
+ vectorIx_L = updatedInfo.beam(i).shape(j).vectorOffset + ((1:n)-1);
+ vectorIx_R = vectorIx_L+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos = apertureInfoVect(vectorIx_L);
+ rightLeafPos = apertureInfoVect(vectorIx_R);
+
+ % update information in shape structure
+ updatedInfo.beam(i).shape(j).leftLeafPos = leftLeafPos;
+ updatedInfo.beam(i).shape(j).leftLeafPos_I = leftLeafPos;
+ updatedInfo.beam(i).shape(j).leftLeafPos_F = leftLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos = rightLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos_I = rightLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos_F = rightLeafPos;
+ else
+ % extract left and right leaf positions from shape vector
+ vectorIx_LI = updatedInfo.beam(i).shape(j).vectorOffset(1) + ((1:n)-1);
+ vectorIx_RI = vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_I = apertureInfoVect(vectorIx_LI);
+ rightLeafPos_I = apertureInfoVect(vectorIx_RI);
+
+ vectorIx_LF = updatedInfo.beam(i).shape(j).vectorOffset(2) + ((1:n)-1);
+ vectorIx_RF = vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_F = apertureInfoVect(vectorIx_LF);
+ rightLeafPos_F = apertureInfoVect(vectorIx_RF);
+
+ % update information in shape structure
+ updatedInfo.beam(i).shape(j).leftLeafPos_I = leftLeafPos_I;
+ updatedInfo.beam(i).shape(j).rightLeafPos_I = rightLeafPos_I;
+
+ updatedInfo.beam(i).shape(j).leftLeafPos_F = leftLeafPos_F;
+ updatedInfo.beam(i).shape(j).rightLeafPos_F = rightLeafPos_F;
+ end
+
+ else
+ % this is an interpolated beam
+
+ %MURate is interpolated between MURates of optimized apertures
+ updatedInfo.beam(i).gantryRot = 1./(updatedInfo.propVMAT.beam(i).timeFracFromLastDAO./updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).gantryRot+updatedInfo.propVMAT.beam(i).timeFracFromNextDAO./updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).gantryRot);
+ updatedInfo.beam(i).time = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff./updatedInfo.beam(i).gantryRot;
+ updatedInfo.beam(i).shape(j).MURate = updatedInfo.propVMAT.beam(i).fracFromLastDAO*updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).MURate+(1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).MURate;
+
+ % calculate MU, weight
+ updatedInfo.beam(i).shape(j).MU = updatedInfo.beam(i).shape(j).MURate.*updatedInfo.beam(i).time;
+ updatedInfo.beam(i).shape(j).weight = updatedInfo.beam(i).shape(j).MU./updatedInfo.weightToMU;
+
+ if ~updatedInfo.propVMAT.continuousAperture
+
+ fracFromLastOpt = updatedInfo.propVMAT.beam(i).fracFromLastDAO;
+ fracFromLastOptI = updatedInfo.propVMAT.beam(i).fracFromLastDAO*ones(n,1);
+ fracFromLastOptF = updatedInfo.propVMAT.beam(i).fracFromLastDAO*ones(n,1);
+ fracFromNextOptI = (1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*ones(n,1);
+ fracFromNextOptF = (1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*ones(n,1);
+
+ % obtain leaf positions at last DAO beam
+ vectorIx_LF_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).vectorOffset + ((1:n)-1);
+ vectorIx_RF_last = vectorIx_LF_last+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_last = apertureInfoVect(vectorIx_LF_last);
+ rightLeafPos_last = apertureInfoVect(vectorIx_RF_last);
+
+ % obtain leaf positions at next DAO beam
+ vectorIx_LI_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).vectorOffset + ((1:n)-1);
+ vectorIx_RI_next = vectorIx_LI_next+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_next = apertureInfoVect(vectorIx_LI_next);
+ rightLeafPos_next = apertureInfoVect(vectorIx_RI_next);
+
+ % interpolate leaf positions
+ leftLeafPos = updatedInfo.propVMAT.beam(i).fracFromLastDAO*leftLeafPos_last+(1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*leftLeafPos_next;
+ rightLeafPos = updatedInfo.propVMAT.beam(i).fracFromLastDAO*rightLeafPos_last+(1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*rightLeafPos_next;
+
+ % update information in shape structure
+ updatedInfo.beam(i).shape(j).leftLeafPos = leftLeafPos;
+ updatedInfo.beam(i).shape(j).leftLeafPos_I = leftLeafPos;
+ updatedInfo.beam(i).shape(j).leftLeafPos_F = leftLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos = rightLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos_I = rightLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos_F = rightLeafPos;
+ else
+
+ fracFromLastOpt = updatedInfo.propVMAT.beam(i).fracFromLastDAO;
+ fracFromLastOptI = updatedInfo.propVMAT.beam(i).fracFromLastDAO_I*ones(n,1);
+ fracFromLastOptF = updatedInfo.propVMAT.beam(i).fracFromLastDAO_F*ones(n,1);
+ fracFromNextOptI = updatedInfo.propVMAT.beam(i).fracFromNextDAO_I*ones(n,1);
+ fracFromNextOptF = updatedInfo.propVMAT.beam(i).fracFromNextDAO_F*ones(n,1);
+
+ % obtain leaf positions at last DAO beam
+ vectorIx_LF_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).vectorOffset(2) + ((1:n)-1);
+ vectorIx_RF_last = vectorIx_LF_last+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_F_last = apertureInfoVect(vectorIx_LF_last);
+ rightLeafPos_F_last = apertureInfoVect(vectorIx_RF_last);
+
+ % obtain leaf positions at next DAO beam
+ vectorIx_LI_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).vectorOffset(1) + ((1:n)-1);
+ vectorIx_RI_next = vectorIx_LI_next+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_I_next = apertureInfoVect(vectorIx_LI_next);
+ rightLeafPos_I_next = apertureInfoVect(vectorIx_RI_next);
+
+ % interpolate leaf positions
+ updatedInfo.beam(i).shape(j).leftLeafPos_I = fracFromLastOptI.*leftLeafPos_F_last+fracFromNextOptI.*leftLeafPos_I_next;
+ updatedInfo.beam(i).shape(j).rightLeafPos_I = fracFromLastOptI.*rightLeafPos_F_last+fracFromNextOptI.*rightLeafPos_I_next;
+
+ updatedInfo.beam(i).shape(j).leftLeafPos_F = fracFromLastOptF.*leftLeafPos_F_last+fracFromNextOptF.*leftLeafPos_I_next;
+ updatedInfo.beam(i).shape(j).rightLeafPos_F = fracFromLastOptF.*rightLeafPos_F_last+fracFromNextOptF.*rightLeafPos_I_next;
+ end
end
- % store information in index vector for gradient calculation
- indVect(apertureInfo.beam(i).shape(j).vectorOffset+[1:n]-1) = bixelIndLeftLeaf;
- indVect(apertureInfo.beam(i).shape(j).vectorOffset+[1:n]-1+apertureInfo.totalNumOfLeafPairs) = bixelIndRightLeaf;
-
- % calculate opening fraction for every bixel in shape to construct
- % bixel weight vector
-
- coveredByLeftLeaf = bsxfun(@minus,leftLeafPos,edges_l) / updatedInfo.bixelWidth;
- coveredByRightLeaf = bsxfun(@minus,edges_r,rightLeafPos) / updatedInfo.bixelWidth;
-
- tempMap = 1 - (coveredByLeftLeaf + abs(coveredByLeftLeaf)) / 2 ...
- - (coveredByRightLeaf + abs(coveredByRightLeaf)) / 2;
+ if ~updatedInfo.runVMAT
+
+ % rounding for numerical stability
+ leftLeafPos = round2(leftLeafPos,10);
+ rightLeafPos = round2(rightLeafPos,10);
+
+ % check overshoot of leaf positions
+ leftLeafPos(leftLeafPos <= apertureInfo.beam(i).lim_l) = apertureInfo.beam(i).lim_l(leftLeafPos <= apertureInfo.beam(i).lim_l);
+ rightLeafPos(rightLeafPos <= apertureInfo.beam(i).lim_l) = apertureInfo.beam(i).lim_l(rightLeafPos <= apertureInfo.beam(i).lim_l);
+ leftLeafPos(leftLeafPos >= apertureInfo.beam(i).lim_r) = apertureInfo.beam(i).lim_r(leftLeafPos >= apertureInfo.beam(i).lim_r);
+ rightLeafPos(rightLeafPos >= apertureInfo.beam(i).lim_r) = apertureInfo.beam(i).lim_r(rightLeafPos >= apertureInfo.beam(i).lim_r);
+
+ %
+ xPosIndLeftLeaf = round((leftLeafPos - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth + 1);
+ xPosIndRightLeaf = round((rightLeafPos - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth + 1);
+
+ %
+ xPosIndLeftLeaf_lim = floor((apertureInfo.beam(i).lim_l - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth+1);
+ xPosIndRightLeaf_lim = ceil((apertureInfo.beam(i).lim_r - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth + 1);
+
+ xPosIndLeftLeaf(xPosIndLeftLeaf <= xPosIndLeftLeaf_lim) = xPosIndLeftLeaf_lim(xPosIndLeftLeaf <= xPosIndLeftLeaf_lim)+1;
+ xPosIndRightLeaf(xPosIndRightLeaf >= xPosIndRightLeaf_lim) = xPosIndRightLeaf_lim(xPosIndRightLeaf >= xPosIndRightLeaf_lim)-1;
+
+ % check limits because of rounding off issues at maximum, i.e.,
+ % enforce round(X.5) -> X
+ % LeafPos can occasionally go slightly beyond lim_r, so changed
+ % == check to >=
+ xPosIndLeftLeaf(leftLeafPos >= apertureInfo.beam(i).lim_r) = round(...
+ .5 + (leftLeafPos(leftLeafPos >= apertureInfo.beam(i).lim_r) ...
+ - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth);
+
+ xPosIndRightLeaf(rightLeafPos >= apertureInfo.beam(i).lim_r) = round(...
+ .5 + (rightLeafPos(rightLeafPos >= apertureInfo.beam(i).lim_r) ...
+ - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth);
+
+ % find the bixel index that the leaves currently touch
+ bixelIndLeftLeaf = apertureInfo.beam(i).bixelIndMap((xPosIndLeftLeaf-1)*n+[1:n]');
+ bixelIndRightLeaf = apertureInfo.beam(i).bixelIndMap((xPosIndRightLeaf-1)*n+[1:n]');
+
+ if any(isnan(bixelIndLeftLeaf)) || any(isnan(bixelIndRightLeaf))
+ error('cannot map leaf position to bixel index');
+ end
+
+ % store information in index vector for gradient calculation
+ indVect(offset+(1:n)) = bixelIndLeftLeaf;
+ indVect(offset+(1:n)+apertureInfo.doseTotalNumOfLeafPairs) = bixelIndRightLeaf;
+ offset = offset+n;
+
+ % calculate opening fraction for every bixel in shape to construct
+ % bixel weight vector
+
+ coveredByLeftLeaf = bsxfun(@minus,leftLeafPos,edges_l) / updatedInfo.bixelWidth;
+ coveredByRightLeaf = bsxfun(@minus,edges_r,rightLeafPos) / updatedInfo.bixelWidth;
+
+ tempMap = 1 - (coveredByLeftLeaf + abs(coveredByLeftLeaf)) / 2 ...
+ - (coveredByRightLeaf + abs(coveredByRightLeaf)) / 2;
+
+ % find open bixels
+ tempMapIx = tempMap > 0;
+
+ currBixelIx = apertureInfo.beam(i).bixelIndMap(tempMapIx);
+ w(currBixelIx) = w(currBixelIx) + tempMap(tempMapIx)*updatedInfo.beam(i).shape(j).weight;
- % find open bixels
- tempMapIx = tempMap > 0;
+ % save the tempMap (we need to apply a positivity operator !)
+ updatedInfo.beam(i).shape(j).shapeMap = (tempMap + abs(tempMap)) / 2;
+
+ % increment shape index
+ shapeInd = shapeInd +1;
+ end
- currBixelIx = apertureInfo.beam(i).bixelIndMap(tempMapIx);
- w(currBixelIx) = w(currBixelIx) + tempMap(tempMapIx)*updatedInfo.beam(i).shape(j).weight;
+ end
- % save the tempMap (we need to apply a positivity operator !)
- updatedInfo.beam(i).shape(j).shapeMap = (tempMap + abs(tempMap)) / 2;
+ if updatedInfo.runVMAT
- % increment shape index
- shapeInd = shapeInd +1;
+ for j = 1:numOfShapes
+
+ % shapeMap
+ shapeMap = zeros(size(updatedInfo.beam(i).bixelIndMap));
+ % sumGradSq
+ sumGradSq = 0;
+
+ % insert variables
+ vectorIndices.tIx_Vec = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+(1:apertureInfo.totalNumOfShapes);
+
+ variables.weight = updatedInfo.beam(i).shape(j).weight;
+ variables.leftLeafPos_I = updatedInfo.beam(i).shape(j).leftLeafPos_I;
+ variables.leftLeafPos_F = updatedInfo.beam(i).shape(j).leftLeafPos_F;
+ variables.rightLeafPos_I = updatedInfo.beam(i).shape(j).rightLeafPos_I;
+ variables.rightLeafPos_F = updatedInfo.beam(i).shape(j).rightLeafPos_F;
+
+ if updatedInfo.propVMAT.beam(i).DAOBeam
+
+ variables.jacobiScale = updatedInfo.beam(i).shape(1).jacobiScale;
+
+ vectorIndices.DAOindex = updatedInfo.propVMAT.beam(i).DAOIndex;
+ if updatedInfo.propVMAT.continuousAperture
+ vectorIndices.vectorIx_LI = updatedInfo.beam(i).shape(j).vectorOffset(1) + ((1:n)-1);
+ vectorIndices.vectorIx_LF = updatedInfo.beam(i).shape(j).vectorOffset(2) + ((1:n)-1);
+ vectorIndices.vectorIx_RI = vectorIndices.vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ vectorIndices.vectorIx_RF = vectorIndices.vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+ else
+ vectorIndices.vectorIx_LI = updatedInfo.beam(i).shape(j).vectorOffset + ((1:n)-1);
+ vectorIndices.vectorIx_LF = updatedInfo.beam(i).shape(j).vectorOffset + ((1:n)-1);
+ vectorIndices.vectorIx_RI = vectorIndices.vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ vectorIndices.vectorIx_RF = vectorIndices.vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+ end
+ else
+
+ variables.weight_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).weight;
+ variables.weight_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).weight;
+
+ variables.jacobiScale_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(1).jacobiScale;
+ variables.jacobiScale_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(1).jacobiScale;
+
+ variables.time_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).time;
+ variables.time_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).time;
+ variables.time = updatedInfo.beam(i).time;
+
+ variables.fracFromLastOptI = fracFromLastOptI;
+ variables.fracFromLastOptF = fracFromLastOptF;
+ variables.fracFromNextOptI = fracFromNextOptI;
+ variables.fracFromNextOptF = fracFromNextOptF;
+ variables.fracFromLastOpt = fracFromLastOpt;
+
+ variables.doseAngleBordersDiff = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff;
+ variables.doseAngleBordersDiff_last = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).doseAngleBordersDiff;
+ variables.doseAngleBordersDiff_next = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).doseAngleBordersDiff;
+ variables.timeFacCurr_last = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).timeFacCurr;
+ variables.timeFacCurr_next = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).timeFacCurr;
+ variables.fracFromLastDAO = updatedInfo.propVMAT.beam(i).fracFromLastDAO;
+ variables.timeFracFromLastDAO = updatedInfo.propVMAT.beam(i).timeFracFromLastDAO;
+ variables.timeFracFromNextDAO = updatedInfo.propVMAT.beam(i).timeFracFromNextDAO;
+
+ vectorIndices.DAOindex_last = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).DAOIndex;
+ vectorIndices.DAOindex_next = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).DAOIndex;
+ vectorIndices.tIx_last = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).DAOIndex;
+ vectorIndices.tIx_next = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).DAOIndex;
+
+ if updatedInfo.propVMAT.continuousAperture
+ vectorIndices.vectorIx_LF_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).vectorOffset(2) + ((1:n)-1);
+ vectorIndices.vectorIx_LI_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).vectorOffset(1) + ((1:n)-1);
+ vectorIndices.vectorIx_RF_last = vectorIndices.vectorIx_LF_last+apertureInfo.totalNumOfLeafPairs;
+ vectorIndices.vectorIx_RI_next = vectorIndices.vectorIx_LI_next+apertureInfo.totalNumOfLeafPairs;
+ else
+ vectorIndices.vectorIx_LF_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).vectorOffset + ((1:n)-1);
+ vectorIndices.vectorIx_LI_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).vectorOffset + ((1:n)-1);
+ vectorIndices.vectorIx_RF_last = vectorIndices.vectorIx_LF_last+apertureInfo.totalNumOfLeafPairs;
+ vectorIndices.vectorIx_RI_next = vectorIndices.vectorIx_LI_next+apertureInfo.totalNumOfLeafPairs;
+ end
+ end
+
+ counters.bixelJApVec_offset = bixelJApVec_offset;
+
+ % calculate bixel weight and derivative in function
+ [w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMap,counters] = ...
+ matRad_bixWeightAndGrad(calcOptions,mlcOptions,variables,vectorIndices,counters,w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMap);
+
+ bixelJApVec_offset = counters.bixelJApVec_offset;
+
+ % update shapeMap
+ updatedInfo.beam(i).shape(j).shapeMap = shapeMap;
+ % update sumGradSq
+ % FIX THIS FOR INTERPOLATED ANGLES???
+ updatedInfo.beam(i).shape(j).sumGradSq = sumGradSq;
+
+ end
end
-
end
+
+% save bixelWeight, apertureVector
updatedInfo.bixelWeights = w;
-updatedInfo.bixelIndices = indVect;
+updatedInfo.apertureVector = apertureInfoVect;
+
+if updatedInfo.runVMAT
+ % save Jacobian between bixelWeight, apertureVector
+
+ deleteInd_i = isnan(bixelJApVec_i);
+ deleteInd_j = bixelJApVec_j == 0;
+ if ~all(deleteInd_i == deleteInd_j)
+ error('Jacobian deletion mismatch');
+ else
+ bixelJApVec_i(deleteInd_i) = [];
+ bixelJApVec_j(deleteInd_i) = [];
+ bixelJApVec_vec(deleteInd_i) = [];
+ end
+ updatedInfo.bixelJApVec = sparse(bixelJApVec_i,bixelJApVec_j,bixelJApVec_vec,numel(apertureInfoVect),updatedInfo.totalNumOfBixels);
+else
+ % save indVect
+ updatedInfo.bixelIndices = indVect;
+end
end
diff --git a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m
index f1be15e78..e4275d1b2 100644
--- a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m
@@ -30,16 +30,16 @@
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% get dosimetric bounds from cst (just like for conv opt) by call to
+% superclass method
+[cl_dos,cu_dos] = matRad_getConstraintBounds@matRad_OptimizationProblem(optiProb,cst);
+
apertureInfo = optiProb.apertureInfo;
% Initialize bounds
cl_dao = zeros(apertureInfo.totalNumOfLeafPairs,1);
cu_dao = inf*ones(apertureInfo.totalNumOfLeafPairs,1);
-% get dosimetric bounds from cst (just like for conv opt) by call to
-% superclass method
-[cl_dos,cu_dos] = matRad_getConstraintBounds@matRad_OptimizationProblem(optiProb,cst);
-
% concatenate
-cl = [cl_dao; cl_dos];
-cu = [cu_dao; cu_dos];
+cl = [cl_dos; cl_dao];
+cu = [cu_dos; cu_dao];
diff --git a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m
index bf8b0309c..47a2ebfe1 100644
--- a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m
@@ -60,32 +60,53 @@
% all stuff can be done per beam direction and then I use repmat to build
% up the big matrix
-% allocate
-jacobStruct_dos = sparse(size(jacobStruct_dos_bixel,1),size(jacobStruct_dao,2));
+offset = 0;
-if ~isempty(jacobStruct_dos)
+if ~isempty(jacobStruct_dos_bixel)
+ numOfConstraints = size(jacobStruct_dos_bixel,1);
+
+ i_sparse = 1:numOfConstraints;
+ i_sparse = kron(i_sparse,ones(1,numel(apertureInfo.apertureVector)));
+
+ j_sparse = 1:numel(apertureInfo.apertureVector);
+ j_sparse = repmat(j_sparse,1,numOfConstraints);
+
+ jacobStructSparseVec = zeros(numOfConstraints*numel(apertureInfo.apertureVector),1);
- % first aperture weights
- for i = 1:apertureInfo.totalNumOfShapes
- currBeam = apertureInfo.mappingMx(i,1);
- currBixelIxInBeam = dij.beamNum == currBeam;
- jacobStruct_dos(:,i) = spones(sum(jacobStruct_dos_bixel(:,currBixelIxInBeam),2));
- end
+ %counter = apertureInfo.totalNumOfShapes;
+ for i = 1:numel(apertureInfo.beam)
+ %for i = 1:size(apertureInfo.beam,2)
+
+ % get used bixels in beam
+ ixWeight = ~isnan(apertureInfo.beam(i).bixelIndMap);
- % second leaves
- counter = apertureInfo.totalNumOfShapes;
- for i = 1:size(apertureInfo.beam,2)
for j = 1:apertureInfo.beam(i).numOfShapes
+ % first weight
+ jacobStructSparseVec(offset+j == j_sparse) = jacobStructSparseVec(offset+j == j_sparse)+sum(jacobStruct_dos_bixel(:,apertureInfo.beam(i).bixelIndMap(ixWeight)),2);
+
+ % now leaf positions
for k = 1:apertureInfo.beam(i).numOfActiveLeafPairs
- counter = counter + 1;
- bixelIxInCurrRow = ~isnan(apertureInfo.beam(i).bixelIndMap(k,:));
- jacobStruct_dos(:,counter+[0 apertureInfo.totalNumOfLeafPairs]) = ...
- repmat(spones(sum(jacobStruct_dos_bixel(:,bixelIxInCurrRow),2)),1,2);
+
+ ixLeaf = ~isnan(apertureInfo.beam(i).bixelIndMap(k,:));
+ indInBixVec = apertureInfo.beam(i).bixelIndMap(k,ixLeaf);
+
+ indInOptVec = apertureInfo.beam(i).shape(1).vectorOffset+k-1+[0 apertureInfo.totalNumOfLeafPairs];
+ indInSparseVec = repmat(indInOptVec,1,numOfConstraints)...
+ +kron((0:numOfConstraints-1)*numel(apertureInfo.apertureVector),ones(1,2));
+
+ jacobStructSparseVec(indInSparseVec) = jacobStructSparseVec(indInSparseVec)+repelem(sum(jacobStruct_dos_bixel(:,indInBixVec),2),2,1);
end
- end
+
+ offset = offset+1;
+ end
end
-
+
+ jacobStructSparseVec(jacobStructSparseVec ~= 0) = 1;
+ jacobStruct_dos = sparse(i_sparse,j_sparse,jacobStructSparseVec,numOfConstraints,numel(apertureInfo.apertureVector));
+
+else
+ jacobStruct_dos = sparse(0,0);
end
% concatenate
-jacobStruct = [jacobStruct_dao; jacobStruct_dos];
+jacobStruct = [jacobStruct_dos; jacobStruct_dao];
diff --git a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m
index e7a9da081..c8719c2c9 100644
--- a/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m
+++ b/matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m
@@ -53,7 +53,7 @@
% loop over all shapes and add up the gradients x openingFrac for this shape
for j = 1:apertureInfo.beam(i).numOfShapes
- g(j+offset) = apertureInfo.beam(i).shape(j).shapeMap(ix)' ...
+ g(j+offset) = apertureInfo.beam(i).shape(j).shapeMap(ix)' ./apertureInfo.beam(i).shape(j).jacobiScale ...
* bixelG(apertureInfo.beam(i).bixelIndMap(ix));
end
@@ -62,12 +62,15 @@
end
+ixAperturesOnly = apertureInfo.totalNumOfShapes+1:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2; %The first entries in most of the vectors denote shape weights
+
% 2. find corresponding bixel to the leaf Positions and aperture
% weights to calculate the gradient
-g(apertureInfo.totalNumOfShapes+1:end) = ...
- apertureInfoVec(apertureInfo.mappingMx(apertureInfo.totalNumOfShapes+1:end,2)) ...
- .* bixelG(apertureInfo.bixelIndices(apertureInfo.totalNumOfShapes+1:end)) / apertureInfo.bixelWidth;
-
-% correct the sign for the left leaf positions
-g(apertureInfo.totalNumOfShapes+1:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs) = ...
- -g(apertureInfo.totalNumOfShapes+1:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs);
+g(ixAperturesOnly) = ...
+ apertureInfoVec(apertureInfo.mappingMx(ixAperturesOnly,2)) ...
+ .* bixelG(apertureInfo.bixelIndices) ./ ...
+ (apertureInfo.bixelWidth.*apertureInfo.jacobiScale(apertureInfo.mappingMx(ixAperturesOnly,2)));
+
+ % correct the sign for the left leaf positions
+ g(apertureInfo.totalNumOfShapes+(1:(apertureInfo.totalNumOfLeafPairs))) = ...
+ -g(apertureInfo.totalNumOfShapes+(1:(apertureInfo.totalNumOfLeafPairs)));
diff --git a/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_OptimizationProblemVMAT.m b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_OptimizationProblemVMAT.m
new file mode 100644
index 000000000..b69025f96
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_OptimizationProblemVMAT.m
@@ -0,0 +1,26 @@
+classdef matRad_OptimizationProblemVMAT < matRad_OptimizationProblemDAO
+ %handle class to keep state easily
+
+
+ methods (Static)
+ %In External Files
+ updatedInfo = matRad_daoVec2ApertureInfo(apertureInfo,apertureInfoVect);
+
+ [apertureInfoVec, mappingMx, limMx] = matRad_daoApertureInfo2Vec(apertureInfo);
+ end
+
+ methods
+ function obj = matRad_OptimizationProblemVMAT(backProjection,apertureInfo)
+ obj = obj@matRad_OptimizationProblemDAO(backProjection,apertureInfo);
+ end
+
+ function lb = lowerBounds(obj,w)
+ lb = obj.apertureInfo.limMx(:,1); % Lower bound on the variables.
+ end
+
+ function ub = upperBounds(obj,w)
+ ub = obj.apertureInfo.limMx(:,2);
+ end
+ end
+end
+
diff --git a/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_constraintFunctions.m b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_constraintFunctions.m
new file mode 100644
index 000000000..9173ac93e
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_constraintFunctions.m
@@ -0,0 +1,151 @@
+function c = matRad_constraintFunctions(optiProb,apertureInfoVec,dij,cst)
+% matRad IPOPT callback: constraint function for VMAT
+%
+% call
+% c = matRad_daoObjFunc(apertueInfoVec,dij,cst)
+%
+% input
+% apertueInfoVec: aperture info vector
+% dij: dose influence matrix
+% cst: matRad cst struct
+% options: option struct defining the type of optimization
+%
+% output
+% c: value of constraints
+%
+% Reference
+% [1] http://www.sciencedirect.com/science/article/pii/S0958394701000577
+% [2] http://www.sciencedirect.com/science/article/pii/S0360301601025858
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% DAO constraint function calculation
+c_dos_dao = matRad_constraintFunctions@matRad_OptimizationProblemDAO(optiProb,apertureInfoVec,dij,cst);
+
+% update apertureInfo, bixel weight vector an mapping of leafes to bixels
+%This update should have taken place if necessary in the call above
+%if ~isequal(apertureInfoVec,optiProb.apertureInfo.apertureVector)
+% optiProb.apertureInfo = matRad_daoVec2ApertureInfo(optiProb.apertureInfo,apertureInfoVec);
+%end
+apertureInfo = optiProb.apertureInfo;
+
+% We need the leaf pos
+leftLeafPos = apertureInfoVec((1:apertureInfo.totalNumOfLeafPairs)+apertureInfo.totalNumOfShapes);
+rightLeafPos = apertureInfoVec((1+apertureInfo.totalNumOfLeafPairs+apertureInfo.totalNumOfShapes):(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2));
+
+% values of times spent in an arc surrounding the optimized angles (full
+% arc/dose influence arc)
+timeDAOBorderAngles = apertureInfoVec(((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+1):end);
+timeDoseBorderAngles = timeDAOBorderAngles.*[apertureInfo.propVMAT.beam([apertureInfo.propVMAT.beam.DAOBeam]).timeFacCurr]';
+
+if apertureInfo.continuousAperture
+ % Using the dynamic fluence calculation, we have the leaf positions in
+ % the vector be the leaf positions at the borders of the Dij arcs (for optimized angles only).
+ % Therefore we must also use the times between the borders of the Dij
+ % arc (for optimized angles only).
+ timeFac = [apertureInfo.propVMAT.beam.timeFac]';
+ deleteInd = timeFac == 0;
+ timeFac(deleteInd) = [];
+
+ i = [apertureInfo.propVMAT.beam.timeFacInd]';
+ i(deleteInd) = [];
+
+ j = repelem(1:apertureInfo.totalNumOfShapes,1,3);
+ j(deleteInd) = [];
+
+ timeFacMatrix = sparse(i,j,timeFac,max(i),apertureInfo.totalNumOfShapes);
+ timeBNOptAngles = timeFacMatrix*timeDAOBorderAngles;
+
+ % prep
+ leftLeafSpeed = zeros(apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs,1);
+ rightLeafSpeed = zeros(apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs,1);
+
+ offset = 0;
+ shapeInd = 1;
+
+ for i = 1:numel(apertureInfo.beam)
+ % loop over beams
+ n = apertureInfo.beam(i).numOfActiveLeafPairs;
+
+ if ~isempty(apertureInfo.propVMAT.beam(i).leafConstMask)
+
+ % get vector indices
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ % if it's a DAO beam, use own vector offset
+ vectorIx_LI = apertureInfo.beam(i).shape(1).vectorOffset(1) + ((1:n)-1);
+ vectorIx_LF = apertureInfo.beam(i).shape(1).vectorOffset(2) + ((1:n)-1);
+ else
+ % otherwise, use vector offset of previous and next
+ % beams
+ vectorIx_LI = apertureInfo.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).shape(1).vectorOffset(2) + ((1:n)-1);
+ vectorIx_LF = apertureInfo.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).shape(1).vectorOffset(1) + ((1:n)-1);
+ end
+ vectorIx_RI = vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ vectorIx_RF = vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+
+ % extract leaf positions, time
+ leftLeafPos_I = apertureInfoVec(vectorIx_LI);
+ rightLeafPos_I = apertureInfoVec(vectorIx_RI);
+ leftLeafPos_F = apertureInfoVec(vectorIx_LF);
+ rightLeafPos_F = apertureInfoVec(vectorIx_RF);
+ t = timeBNOptAngles(shapeInd);
+
+ % determine indices
+ indInConVec = offset+(1:n);
+
+ % calc speeds
+ leftLeafSpeed(indInConVec) = abs(leftLeafPos_F-leftLeafPos_I)./t;
+ rightLeafSpeed(indInConVec) = abs(rightLeafPos_F-rightLeafPos_I)./t;
+
+ % update offset
+ offset = offset+n;
+
+ % increment shapeInd only for beams which have transtion
+ % defined
+ shapeInd = shapeInd+1;
+ end
+ end
+
+ c_lfspd = [leftLeafSpeed; rightLeafSpeed];
+else
+
+ i = sort(repmat(1:(apertureInfo.totalNumOfShapes-1),1,2));
+ j = sort(repmat(1:apertureInfo.totalNumOfShapes,1,2));
+ j(1) = [];
+ j(end) = [];
+
+ timeFac = [apertureInfo.propVMAT.beam([apertureInfo.propVMAT.beam.DAOBeam]).timeFac]';
+ timeFac(1) = [];
+ timeFac(end) = [];
+ %timeFac(timeFac == 0) = [];
+
+ timeFacMatrix = sparse(i,j,timeFac,(apertureInfo.totalNumOfShapes-1),apertureInfo.totalNumOfShapes);
+ timeBNOptAngles = timeFacMatrix*timeDAOBorderAngles;
+
+ % values of average leaf speeds of optimized gantry angles
+ c_lfspd = reshape([abs(diff(reshape(leftLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2)) ...
+ abs(diff(reshape(rightLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2))]./ ...
+ repmat(timeBNOptAngles',apertureInfo.beam(1).numOfActiveLeafPairs,2),2*apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles),1);
+end
+
+% values of doserate (MU/sec) in an arc surrounding the optimized angles
+weights = apertureInfoVec(1:(apertureInfo.totalNumOfShapes))./apertureInfo.jacobiScale;
+c_dosrt = apertureInfo.weightToMU.*weights./timeDoseBorderAngles;
+
+% concatenate %Maybe do in a way we can call the superclass above?
+c = [c_dos_dao; c_lfspd; c_dosrt];
+
+
diff --git a/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_constraintJacobian.m b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_constraintJacobian.m
new file mode 100644
index 000000000..08b667b1a
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_constraintJacobian.m
@@ -0,0 +1,283 @@
+function jacob = matRad_constraintJacobian(optiProb,apertureInfoVec,dij,cst)
+% matRad IPOPT callback: jacobian function for VMAT optimization
+%
+% call
+% jacob = matRad_constraintJacobian(optiProb,apertureInfoVec,dij,cst)
+%
+% input
+% apertureInfoVec: aperture info vector
+% dij: dose influence matrix
+% cst: matRad cst struct
+%
+% output
+% jacob: jacobian of constraint function
+%
+% References
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% update apertureInfo, bixel weight vector an mapping of leafes to bixels
+if ~isequal(apertureInfoVec,optiProb.apertureInfo.apertureVector)
+ optiProb.apertureInfo = optiProb.matRad_daoVec2ApertureInfo(optiProb.apertureInfo,apertureInfoVec);
+end
+apertureInfo = optiProb.apertureInfo;
+
+% jacobian of the dao constraints
+
+% row indices
+i = repmat(1:apertureInfo.totalNumOfLeafPairs,1,2);
+% column indices
+j = [(apertureInfo.totalNumOfShapes+1):(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs) ...
+ ((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs)+1):(apertureInfo.totalNumOfShapes+2*apertureInfo.totalNumOfLeafPairs)];
+
+% -1 for left leaves, 1 for right leaves
+s = [-1*ones(1,apertureInfo.totalNumOfLeafPairs) ones(1,apertureInfo.totalNumOfLeafPairs)];
+
+jacob_dao = sparse(i,j,s, ...
+ apertureInfo.totalNumOfLeafPairs, ...
+ numel(apertureInfoVec), ...
+ 2*apertureInfo.totalNumOfLeafPairs);
+
+% compute jacobian of dosimetric constrainst
+
+% dosimetric jacobian in bixel space
+jacob_dos_bixel = matRad_constraintJacobian@matRad_OptimizationProblem(optiProb,apertureInfo.bixelWeights,dij,cst);
+
+if ~isempty(jacob_dos_bixel)
+ %Use pre-computed bixelAperture-Jacobian
+ jacob_dos = jacob_dos_bixel*apertureInfo.bixelJApVec';
+else
+ jacob_dos = sparse(0,0);
+end
+
+%VMAT
+% values of times spent in an arc surrounding the optimized angles (full
+% arc/dose influence arc)
+timeDAOBorderAngles = apertureInfoVec(((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+1):end);
+timeFacCurr = [apertureInfo.propVMAT.beam([apertureInfo.propVMAT.beam.DAOBeam]).timeFacCurr]';
+timeDoseBorderAngles = timeDAOBorderAngles.*timeFacCurr;
+
+if apertureInfo.continuousAperture
+ timeFac = [apertureInfo.propVMAT.beam.timeFac]';
+ deleteInd = timeFac == 0;
+ timeFac(deleteInd) = [];
+
+ i = [apertureInfo.propVMAT.beam.timeFacInd]';
+ i(deleteInd) = [];
+
+ j = repelem(1:apertureInfo.totalNumOfShapes,1,3);
+ j(deleteInd) = [];
+
+ timeFacMatrix = sparse(i,j,timeFac,max(i),apertureInfo.totalNumOfShapes);
+ timeBNOptAngles = timeFacMatrix*timeDAOBorderAngles;
+
+ % set up
+ n = apertureInfo.beam(1).numOfActiveLeafPairs;
+ indInSparseVec = (1:n);
+ indInConVec = (1:n);
+ shapeInd = 1;
+
+ % sparse matrix
+ numElem = n.*(apertureInfo.propVMAT.numLeafSpeedConstraintDAO*6+(apertureInfo.propVMAT.numLeafSpeedConstraint-apertureInfo.propVMAT.numLeafSpeedConstraintDAO)*8);
+ i_sparse = zeros(numElem,1);
+ j_sparse = zeros(numElem,1);
+ s_sparse = zeros(numElem,1);
+
+ for i = 1:numel(apertureInfo.beam)
+ % loop over beams
+
+ if ~isempty(apertureInfo.propVMAT.beam(i).leafConstMask)
+
+ % get vector indices
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ % if it's a DAO beam, use own vector offset
+ vectorIx_LI = apertureInfo.beam(i).shape(1).vectorOffset(1) + ((1:n)-1);
+ vectorIx_LF = apertureInfo.beam(i).shape(1).vectorOffset(2) + ((1:n)-1);
+ else
+ % otherwise, use vector offset of previous and next
+ % beams
+ vectorIx_LI = apertureInfo.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).shape(1).vectorOffset(2) + ((1:n)-1);
+ vectorIx_LF = apertureInfo.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).shape(1).vectorOffset(1) + ((1:n)-1);
+ end
+ vectorIx_RI = vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ vectorIx_RF = vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+
+ % extract leaf positions, time
+ leftLeafPos_I = apertureInfoVec(vectorIx_LI);
+ rightLeafPos_I = apertureInfoVec(vectorIx_RI);
+ leftLeafPos_F = apertureInfoVec(vectorIx_LF);
+ rightLeafPos_F = apertureInfoVec(vectorIx_RF);
+ t = timeBNOptAngles(shapeInd);
+
+ % calc diffs
+ leftLeafDiff = leftLeafPos_F-leftLeafPos_I;
+ rightLeafDiff = rightLeafPos_F-rightLeafPos_I;
+
+ % calc jacobs
+
+ % wrt initial leaf positions (left, then right)
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = vectorIx_LI;
+ s_sparse(indInSparseVec) = -sign(leftLeafDiff)./t;
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = vectorIx_RI;
+ s_sparse(indInSparseVec) = -sign(rightLeafDiff)./t;
+ indInSparseVec = indInSparseVec+n;
+
+ % wrt final leaf positions (left, then right)
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = vectorIx_LF;
+ s_sparse(indInSparseVec) = sign(leftLeafDiff)./t;
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = vectorIx_RF;
+ s_sparse(indInSparseVec) = sign(rightLeafDiff)./t;
+ indInSparseVec = indInSparseVec+n;
+
+ % wrt time (left, then right)
+ % how we do this depends on if it's a DAO beam or
+ % not
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ % if it is, then speeds only depend on its own
+ % time
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(i).timeInd;
+ s_sparse(indInSparseVec) = -apertureInfo.propVMAT.beam(i).timeFac(2).*abs(leftLeafDiff)./(t.^2);
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(i).timeInd;
+ s_sparse(indInSparseVec) = -apertureInfo.propVMAT.beam(i).timeFac(2).*abs(rightLeafDiff)./(t.^2);
+ indInSparseVec = indInSparseVec+n;
+
+ else
+ % otherwise, speed depends on time of DAO
+ % before and DAO after
+
+ % before
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).timeInd;
+ s_sparse(indInSparseVec) = -apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).timeFac(3).*abs(leftLeafDiff)./(t.^2);
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).timeInd;
+ s_sparse(indInSparseVec) = -apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).timeFac(3).*abs(rightLeafDiff)./(t.^2);
+ indInSparseVec = indInSparseVec+n;
+
+ % after
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).timeInd;
+ s_sparse(indInSparseVec) = -apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).timeFac(1).*abs(leftLeafDiff)./(t.^2);
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).timeInd;
+ s_sparse(indInSparseVec) = -apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).timeFac(1).*abs(rightLeafDiff)./(t.^2);
+ indInSparseVec = indInSparseVec+n;
+
+ end
+
+ % update offset
+ indInConVec = indInConVec+n;
+
+ % increment shapeInd only for beams which have transtion
+ % defined
+ shapeInd = shapeInd+1;
+ end
+ end
+
+ jacob_lfspd = sparse(i_sparse,j_sparse,s_sparse,2*apertureInfo.beam(1).numOfActiveLeafPairs*apertureInfo.propVMAT.numLeafSpeedConstraint,numel(apertureInfoVec));
+
+else
+
+ % get index values for the jacobian
+ % variable index
+ % value of constraints for leaves
+ leftLeafPos = apertureInfoVec((1:apertureInfo.totalNumOfLeafPairs)+apertureInfo.totalNumOfShapes);
+ rightLeafPos = apertureInfoVec(1+apertureInfo.totalNumOfLeafPairs+apertureInfo.totalNumOfShapes:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2);
+
+ i = sort(repmat(1:(apertureInfo.totalNumOfShapes-1),1,2));
+ j = sort(repmat(1:apertureInfo.totalNumOfShapes,1,2));
+ j(1) = [];
+ j(end) = [];
+
+ timeFac = [apertureInfo.propVMAT.beam([apertureInfo.propVMAT.beam.DAOBeam]).timeFac]';
+ timeFac(1) = [];
+ timeFac(end) = [];
+ %timeFac(timeFac == 0) = [];
+
+ timeFacMatrix = sparse(i,j,timeFac,(apertureInfo.totalNumOfShapes-1),apertureInfo.totalNumOfShapes);
+ timeBNOptAngles = timeFacMatrix*timeDAOBorderAngles;
+
+ currentLeftLeafInd = (apertureInfo.totalNumOfShapes+1):(apertureInfo.totalNumOfShapes+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles));
+ currentRightLeafInd = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+1):(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles));
+ nextLeftLeafInd = (apertureInfo.beam(1).numOfActiveLeafPairs+apertureInfo.totalNumOfShapes+1):(apertureInfo.beam(1).numOfActiveLeafPairs+apertureInfo.totalNumOfShapes+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles));
+ nextRightLeafInd = (apertureInfo.beam(1).numOfActiveLeafPairs+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+1):(apertureInfo.beam(1).numOfActiveLeafPairs+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles));
+ leftTimeInd = kron(j,ones(1,apertureInfo.beam(1).numOfActiveLeafPairs))+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2;
+ rightTimeInd = kron(j,ones(1,apertureInfo.beam(1).numOfActiveLeafPairs))+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2;
+ %leftTimeInd = repelem(j,apertureInfo.beam(1).numOfActiveLeafPairs)+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2;
+ %rightTimeInd = repelem(j,apertureInfo.beam(1).numOfActiveLeafPairs)+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2;
+ % constraint index
+ constraintInd = 1:2*apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles);
+
+
+ % jacobian of the leafspeed constraint
+ i = repmat((i'-1)*apertureInfo.beam(1).numOfActiveLeafPairs,1,apertureInfo.beam(1).numOfActiveLeafPairs)+repmat(1:apertureInfo.beam(1).numOfActiveLeafPairs,2*numel(timeBNOptAngles),1);
+ i = reshape([i' i'+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles)],1,[]);
+
+ i = [repmat(constraintInd,1,2) i];
+ j = [currentLeftLeafInd currentRightLeafInd nextLeftLeafInd nextRightLeafInd leftTimeInd rightTimeInd];
+ % first do jacob wrt current leaf position (left, right), then next leaf
+ % position (left, right), then time (left, right)
+ j_lfspd_cur = -reshape([sign(diff(reshape(leftLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2)) ...
+ sign(diff(reshape(rightLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2))]./ ...
+ repmat(timeBNOptAngles',apertureInfo.beam(1).numOfActiveLeafPairs,2),2*apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles),1);
+
+ j_lfspd_nxt = reshape([sign(diff(reshape(leftLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2)) ...
+ sign(diff(reshape(rightLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2))]./ ...
+ repmat(timeBNOptAngles',apertureInfo.beam(1).numOfActiveLeafPairs,2),2*apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeBNOptAngles),1);
+
+ j_lfspd_t = -reshape([kron(abs(diff(reshape(leftLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2)),ones(1,2)).*repmat(timeFac',apertureInfo.beam(1).numOfActiveLeafPairs,1) ...
+ kron(abs(diff(reshape(rightLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,apertureInfo.totalNumOfShapes),1,2)),ones(1,2)).*repmat(timeFac',apertureInfo.beam(1).numOfActiveLeafPairs,1)]./ ...
+ repmat(kron((timeBNOptAngles.^2)',ones(1,2)),apertureInfo.beam(1).numOfActiveLeafPairs,2),[],1);
+
+ s = [j_lfspd_cur; j_lfspd_nxt; j_lfspd_t];
+
+ jacob_lfspd = sparse(i,j,s,2*apertureInfo.beam(1).numOfActiveLeafPairs*(apertureInfo.totalNumOfShapes-1),numel(apertureInfoVec),numel(s));
+end
+
+% jacobian of the doserate constraint
+% values of doserate (MU/sec) between optimized gantry angles
+weights = apertureInfoVec(1:(apertureInfo.totalNumOfShapes))./apertureInfo.jacobiScale;
+
+i = repmat(1:(apertureInfo.totalNumOfShapes),1,2);
+j = [1:(apertureInfo.totalNumOfShapes) ...
+ ((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+1):((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+apertureInfo.totalNumOfShapes)];
+% first do jacob wrt weights, then wrt times
+
+s = [apertureInfo.weightToMU./(timeDoseBorderAngles.*apertureInfo.jacobiScale); -apertureInfo.weightToMU.*weights.*timeFacCurr./(timeDoseBorderAngles.^2)];
+
+jacob_dosrt = sparse(i,j,s,apertureInfo.totalNumOfShapes,numel(apertureInfoVec),2*apertureInfo.totalNumOfShapes);
+
+
+% concatenate
+jacob = [jacob_dos; jacob_dao; jacob_lfspd; jacob_dosrt];
+
+
diff --git a/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_daoApertureInfo2Vec.m b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_daoApertureInfo2Vec.m
new file mode 100644
index 000000000..d6bb721db
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_daoApertureInfo2Vec.m
@@ -0,0 +1,172 @@
+function [apertureInfoVec, mappingMx, limMx] = matRad_daoApertureInfo2Vec(apertureInfo)
+% matRad function to generate a vector respresentation of the aperture
+% weights and shapes and (optional) some meta information needed during
+% optimization
+%
+% call
+% [apertureInfoVec, mappingMx, limMx] = matRad_daoApertureInfo2Vec(apertureInfo)
+%
+% input
+% apertureInfo: aperture weight and shape info struct
+%
+% output
+% apertureInfoVec: vector respresentation of the apertue weights and shapes
+% mappingMx: mapping of vector components to beams, shapes and leaves
+% limMx: bounds on vector components, i.e., minimum and maximum
+% aperture weights (0/inf) and leav positions (custom)
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% function to create a single vector for the direct aperature optimization
+% first: aperature weights
+% second: left leaf positions
+% third: right leaf positions
+% fourth (VMAT only): times between successive DAO gantry angles
+
+% initializing variables
+
+vecLength = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2);
+
+if apertureInfo.runVMAT
+ vecLength = vecLength+apertureInfo.totalNumOfShapes; %Extra set of (apertureInfo.totalNumOfShapes) number of elements, allowing arc sector times to be optimized
+end
+
+apertureInfoVec = NaN * ones(vecLength,1);
+
+offset = 0;
+
+%% 1. aperture weights
+for i = 1:size(apertureInfo.beam,2)
+ for j = 1:apertureInfo.beam(i).numOfShapes
+
+ apertureInfoVec(offset+j) = apertureInfo.beam(i).shape(j).jacobiScale*apertureInfo.beam(i).shape(j).weight; %In VMAT, this weight is "spread" over unoptimized beams (assume constant dose rate over sector)
+
+ end
+ offset = offset + apertureInfo.beam(i).numOfShapes;
+end
+
+% 2. left and right leaf positions
+%% fill the vector for all shapes of all beams
+for i = 1:size(apertureInfo.beam,2)
+ for j = 1:apertureInfo.beam(i).numOfShapes
+
+ if ~apertureInfo.runVMAT || ~apertureInfo.continuousAperture
+
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]) = apertureInfo.beam(i).shape(j).leftLeafPos;
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]+apertureInfo.totalNumOfLeafPairs) = apertureInfo.beam(i).shape(j).rightLeafPos;
+
+ offset = offset + apertureInfo.beam(i).numOfActiveLeafPairs;
+ else
+
+ if apertureInfo.propVMAT.beam(i).doseAngleDAO(1)
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]) = apertureInfo.beam(i).shape(j).leftLeafPos_I;
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]+apertureInfo.totalNumOfLeafPairs) = apertureInfo.beam(i).shape(j).rightLeafPos_I;
+
+ offset = offset + apertureInfo.beam(i).numOfActiveLeafPairs;
+ end
+
+ if apertureInfo.propVMAT.beam(i).doseAngleDAO(2)
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]) = apertureInfo.beam(i).shape(j).leftLeafPos_F;
+ apertureInfoVec(offset+[1:apertureInfo.beam(i).numOfActiveLeafPairs]+apertureInfo.totalNumOfLeafPairs) = apertureInfo.beam(i).shape(j).rightLeafPos_F;
+
+ offset = offset + apertureInfo.beam(i).numOfActiveLeafPairs;
+ end
+ end
+
+ end
+end
+%% 3. time of arc sector/beam
+if apertureInfo.runVMAT
+ offset = offset + apertureInfo.totalNumOfLeafPairs;
+
+ %this gives a vector of the arc lengths belonging to each optimized CP
+ %unique gets rid of double-counted angles (which is every interior
+ %angle)
+
+ optInd = [apertureInfo.propVMAT.beam.DAOBeam];
+ optAngleLengths = [apertureInfo.propVMAT.beam(optInd).DAOAngleBordersDiff];
+ optGantryRot = [apertureInfo.beam(optInd).gantryRot];
+ apertureInfoVec((offset+1):end) = optAngleLengths./optGantryRot; %entries are the times until the next opt gantry angle is reached
+
+end
+
+%% 4. create additional information for later use
+if nargout > 1
+
+ mappingMx = NaN * ones(vecLength,4);
+ limMx = NaN * ones(vecLength,2);
+
+ limMx(1:(apertureInfo.totalNumOfShapes),:) = ones((apertureInfo.totalNumOfShapes),1)*[0 inf];
+
+ counter = 1;
+
+ for i = 1:numel(apertureInfo.beam)
+ for j = 1:apertureInfo.beam(i).numOfShapes
+ mappingMx(counter,1) = i;
+ if apertureInfo.runVMAT
+
+ timeLimL = diff(apertureInfo.propVMAT.beam(i).DAOAngleBorders)/apertureInfo.propVMAT.constraints.gantryRotationSpeed(2); %Minimum time interval between two optimized beams/gantry angles
+ timeLimU = diff(apertureInfo.propVMAT.beam(i).DAOAngleBorders)/apertureInfo.propVMAT.constraints.gantryRotationSpeed(1); %Maximum time interval between two optimized beams/gantry angles
+
+ mappingMx(counter+(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2),1) = i;
+ limMx(counter+(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2),:) = [timeLimL timeLimU];
+ end
+ counter = counter + 1;
+ end
+ end
+
+ shapeOffset = 0;
+ for i = 1:numel(apertureInfo.beam)
+ for j = 1:apertureInfo.beam(i).numOfShapes
+ for k = 1:apertureInfo.beam(i).numOfActiveLeafPairs
+ mappingMx(counter,1) = i;
+ mappingMx(counter,2) = j + shapeOffset; % store global shape number for grad calc
+ mappingMx(counter,3) = j; % store local shape number
+ mappingMx(counter,4) = k; % store local leaf number
+
+ limMx(counter,1) = apertureInfo.beam(i).lim_l(k);
+ limMx(counter,2) = apertureInfo.beam(i).lim_r(k);
+ counter = counter + 1;
+
+ if apertureInfo.runVMAT && apertureInfo.continuousAperture && nnz(apertureInfo.propVMAT.beam(i).doseAngleDAO) == 2
+ %redo for initial and final leaf positions
+ %might have to revisit this after looking at gradient,
+ %esp. mappingMx(counter,2)
+ %only an issue for non-interpolated deliveries
+ mappingMx(counter,1) = i;
+ mappingMx(counter,2) = j + shapeOffset; % store global shape number for grad calc
+ mappingMx(counter,3) = j; % store local shape number
+ mappingMx(counter,4) = k; % store local leaf number
+
+ limMx(counter,1) = apertureInfo.beam(i).lim_l(k);
+ limMx(counter,2) = apertureInfo.beam(i).lim_r(k);
+ counter = counter + 1;
+ end
+ end
+ end
+ shapeOffset = shapeOffset + apertureInfo.beam(i).numOfShapes;
+ end
+
+ mappingMx(counter:(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2),:) = mappingMx(apertureInfo.totalNumOfShapes+1:counter-1,:);
+ limMx(counter:(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2),:) = limMx(apertureInfo.totalNumOfShapes+1:counter-1,:);
+
+end
+
+end
+
diff --git a/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_daoVec2ApertureInfo.m b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_daoVec2ApertureInfo.m
new file mode 100644
index 000000000..2b26f6f15
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_daoVec2ApertureInfo.m
@@ -0,0 +1,481 @@
+function updatedInfo = matRad_daoVec2ApertureInfo(apertureInfo,apertureInfoVect)
+% matRad function to translate the vector representation of the aperture
+% shape and weight into an aperture info struct. At the same time, the
+% updated bixel weight vector w is computed and a vector listing the
+% correspondence between leaf tips and bixel indices for gradient
+% calculation
+%
+% call
+% [updatedInfo,w,indVect] = matRad_daoVec2ApertureInfo(apertureInfo,apertureInfoVect)
+%
+% input
+% apertureInfo: aperture shape info struct
+% apertureInfoVect: aperture weights and shapes parameterized as vector
+%
+% output
+% updatedInfo: updated aperture shape info struct according to apertureInfoVect
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015, Mark Bangert, on behalf of the matRad development team
+%
+% m.bangert@dkfz.de
+%
+% This file is part of matRad.
+%
+% matrad is free software: you can redistribute it and/or modify it under
+% the terms of the GNU General Public License as published by the Free
+% Software Foundation, either version 3 of the License, or (at your option)
+% any later version.
+%
+% matRad is distributed in the hope that it will be useful, but WITHOUT ANY
+% WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+% FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+% details.
+%
+% You should have received a copy of the GNU General Public License in the
+% file license.txt along with matRad. If not, see
+% .
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% function to update the apertureInfo struct after the each iteraton of the
+% optimization
+
+% initializing variables
+updatedInfo = apertureInfo;
+
+updatedInfo.apertureVector = apertureInfoVect;
+
+if ~updatedInfo.runVMAT
+ shapeInd = 1;
+
+ indVect = NaN*ones(2*apertureInfo.doseTotalNumOfLeafPairs,1);
+ offset = 0;
+
+ % helper function to cope with numerical instabilities through rounding
+ round2 = @(a,b) round(a*10^b)/10^b;
+else
+
+ % options for bixel and Jacobian calculation
+ mlcOptions.bixelWidth = apertureInfo.bixelWidth;
+ calcOptions.continuousAperture = updatedInfo.continuousAperture;
+ vectorIndices.totalNumOfShapes = apertureInfo.totalNumOfShapes;
+end
+
+w = zeros(apertureInfo.totalNumOfBixels,1);
+
+if updatedInfo.runVMAT && ~all([updatedInfo.propVMAT.beam.DAOBeam])
+ j = 1;
+ for i = 1:numel(updatedInfo.beam)
+ if updatedInfo.propVMAT.beam(i).DAOBeam
+ % update the shape weight
+ % rescale the weight from the vector using the previous
+ % iteration scaling factor
+ updatedInfo.beam(i).shape(j).weight = apertureInfoVect(updatedInfo.beam(i).shape(j).weightOffset)./updatedInfo.beam(i).shape(j).jacobiScale;
+
+ updatedInfo.beam(i).shape(j).MU = updatedInfo.beam(i).shape(j).weight*updatedInfo.weightToMU;
+ updatedInfo.beam(i).time = apertureInfoVect((updatedInfo.totalNumOfShapes+updatedInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(i).DAOIndex)*updatedInfo.propVMAT.beam(i).timeFacCurr;
+ updatedInfo.beam(i).gantryRot = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff/updatedInfo.beam(i).time;
+ updatedInfo.beam(i).shape(j).MURate = updatedInfo.beam(i).shape(j).MU./updatedInfo.beam(i).time;
+ end
+ end
+end
+
+if updatedInfo.runVMAT
+ %% ONLY SUPPORTED IN VMAT FOR NOW
+
+ % Jacobian matrix to be used in the DAO gradient function
+ % this tells us the gradient of a particular bixel with respect to an
+ % element in the apertureVector (aperture weight or leaf position)
+ % store as a vector for now, convert to sparse matrix later
+
+ optBixelFactor = 7;
+ % For optimized beams: 5 = (1 from weights) + (3 from left leaf positions (I, M, and F)) + (3 from
+ % right leaf positions (I, M, and F))
+
+ if updatedInfo.runVMAT
+ intBixelFactor = 2*optBixelFactor+2;
+ % For interpolated beams: multiply this number times 2 (influenced by the
+ % one before and the one after), then add 2 (influenced by the time of the
+ % times before and after)
+ else
+ intBixelFactor = 2*optBixelFactor;
+ % For interpolated beams: multiply this number times 2 (influenced by the
+ % one before and the one after)
+ end
+
+ % for the time (probability) gradients
+ optBixelFactor = optBixelFactor+apertureInfo.totalNumOfShapes;
+ intBixelFactor = intBixelFactor+apertureInfo.totalNumOfShapes;
+
+ bixelJApVec_sz = (updatedInfo.totalNumOfOptBixels*optBixelFactor+(updatedInfo.totalNumOfBixels-updatedInfo.totalNumOfOptBixels)*intBixelFactor)*2;
+
+ bixelJApVec_vec = zeros(1,bixelJApVec_sz);
+
+ % vector indices
+ bixelJApVec_i = nan(1,bixelJApVec_sz);
+ % bixel indices
+ bixelJApVec_j = zeros(1,bixelJApVec_sz);
+ % offset
+ bixelJApVec_offset = 0;
+end
+
+%% update the shapeMaps
+% here the new colimator positions are used to create new shapeMaps that
+% now include decimal values instead of binary
+
+calcOptions.saveJacobian = true;
+
+% loop over all beams
+for i = 1:numel(updatedInfo.beam)
+
+ %posOfRightCornerPixel = apertureInfo.beam(i).posOfCornerBixel(1) + (size(apertureInfo.beam(i).bixelIndMap,2)-1)*apertureInfo.bixelWidth;
+
+ % pre compute left and right bixel edges
+ edges_l = updatedInfo.beam(i).posOfCornerBixel(1)...
+ + ((1:size(apertureInfo.beam(i).bixelIndMap,2))-1-1/2)*updatedInfo.bixelWidth;
+ edges_r = updatedInfo.beam(i).posOfCornerBixel(1)...
+ + ((1:size(apertureInfo.beam(i).bixelIndMap,2))-1+1/2)*updatedInfo.bixelWidth;
+
+ % get dimensions of 2d matrices that store shape/bixel information
+ n = apertureInfo.beam(i).numOfActiveLeafPairs;
+
+ % loop over all shapes
+ if updatedInfo.runVMAT
+ numOfShapes = 1;
+ calcOptions.DAOBeam = updatedInfo.propVMAT.beam(i).DAOBeam;
+ else
+ numOfShapes = updatedInfo.beam(i).numOfShapes;
+ end
+
+ mlcOptions.lim_l = apertureInfo.beam(i).lim_l;
+ mlcOptions.lim_r = apertureInfo.beam(i).lim_r;
+ mlcOptions.edges_l = edges_l;
+ mlcOptions.edges_r = edges_r;
+ mlcOptions.centres = (edges_l+edges_r)/2;
+ mlcOptions.widths = edges_r-edges_l;
+ mlcOptions.n = n;
+ mlcOptions.numBix = size(apertureInfo.beam(i).bixelIndMap,2);
+ mlcOptions.bixelIndMap = apertureInfo.beam(i).bixelIndMap;
+
+ for j = 1:numOfShapes
+
+ if ~updatedInfo.runVMAT || updatedInfo.propVMAT.beam(i).DAOBeam
+ % either this is not VMAT, or if it is VMAT, this is a DAO beam
+
+ % update the shape weight
+ updatedInfo.beam(i).shape(j).weight = apertureInfoVect(updatedInfo.beam(i).shape(j).weightOffset)./updatedInfo.beam(i).shape(j).jacobiScale;
+
+ if updatedInfo.runVMAT
+ updatedInfo.beam(i).shape(j).MU = updatedInfo.beam(i).shape(j).weight*updatedInfo.weightToMU;
+ updatedInfo.beam(i).time = apertureInfoVect((updatedInfo.totalNumOfShapes+updatedInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(i).DAOIndex)*updatedInfo.propVMAT.beam(i).timeFacCurr;
+ updatedInfo.beam(i).gantryRot = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff/updatedInfo.beam(i).time;
+ updatedInfo.beam(i).shape(j).MURate = updatedInfo.beam(i).shape(j).MU./updatedInfo.beam(i).time;
+ end
+
+ if ~updatedInfo.runVMAT || ~updatedInfo.continuousAperture
+ % extract left and right leaf positions from shape vector
+ vectorIx_L = updatedInfo.beam(i).shape(j).vectorOffset + ((1:n)-1);
+ vectorIx_R = vectorIx_L+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos = apertureInfoVect(vectorIx_L);
+ rightLeafPos = apertureInfoVect(vectorIx_R);
+
+ % update information in shape structure
+ updatedInfo.beam(i).shape(j).leftLeafPos = leftLeafPos;
+ updatedInfo.beam(i).shape(j).leftLeafPos_I = leftLeafPos;
+ updatedInfo.beam(i).shape(j).leftLeafPos_F = leftLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos = rightLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos_I = rightLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos_F = rightLeafPos;
+ else
+ % extract left and right leaf positions from shape vector
+ vectorIx_LI = updatedInfo.beam(i).shape(j).vectorOffset(1) + ((1:n)-1);
+ vectorIx_RI = vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_I = apertureInfoVect(vectorIx_LI);
+ rightLeafPos_I = apertureInfoVect(vectorIx_RI);
+
+ vectorIx_LF = updatedInfo.beam(i).shape(j).vectorOffset(2) + ((1:n)-1);
+ vectorIx_RF = vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_F = apertureInfoVect(vectorIx_LF);
+ rightLeafPos_F = apertureInfoVect(vectorIx_RF);
+
+ % update information in shape structure
+ updatedInfo.beam(i).shape(j).leftLeafPos_I = leftLeafPos_I;
+ updatedInfo.beam(i).shape(j).rightLeafPos_I = rightLeafPos_I;
+
+ updatedInfo.beam(i).shape(j).leftLeafPos_F = leftLeafPos_F;
+ updatedInfo.beam(i).shape(j).rightLeafPos_F = rightLeafPos_F;
+ end
+
+ else
+ % this is an interpolated beam
+
+ %MURate is interpolated between MURates of optimized apertures
+ updatedInfo.beam(i).gantryRot = 1./(updatedInfo.propVMAT.beam(i).timeFracFromLastDAO./updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).gantryRot+updatedInfo.propVMAT.beam(i).timeFracFromNextDAO./updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).gantryRot);
+ updatedInfo.beam(i).time = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff./updatedInfo.beam(i).gantryRot;
+ updatedInfo.beam(i).shape(j).MURate = updatedInfo.propVMAT.beam(i).fracFromLastDAO*updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).MURate+(1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).MURate;
+
+ % calculate MU, weight
+ updatedInfo.beam(i).shape(j).MU = updatedInfo.beam(i).shape(j).MURate.*updatedInfo.beam(i).time;
+ updatedInfo.beam(i).shape(j).weight = updatedInfo.beam(i).shape(j).MU./updatedInfo.weightToMU;
+
+ if ~updatedInfo.continuousAperture
+
+ fracFromLastOpt = updatedInfo.propVMAT.beam(i).fracFromLastDAO;
+ fracFromLastOptI = updatedInfo.propVMAT.beam(i).fracFromLastDAO*ones(n,1);
+ fracFromLastOptF = updatedInfo.propVMAT.beam(i).fracFromLastDAO*ones(n,1);
+ fracFromNextOptI = (1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*ones(n,1);
+ fracFromNextOptF = (1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*ones(n,1);
+
+ % obtain leaf positions at last DAO beam
+ vectorIx_LF_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).vectorOffset + ((1:n)-1);
+ vectorIx_RF_last = vectorIx_LF_last+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_last = apertureInfoVect(vectorIx_LF_last);
+ rightLeafPos_last = apertureInfoVect(vectorIx_RF_last);
+
+ % obtain leaf positions at next DAO beam
+ vectorIx_LI_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).vectorOffset + ((1:n)-1);
+ vectorIx_RI_next = vectorIx_LI_next+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_next = apertureInfoVect(vectorIx_LI_next);
+ rightLeafPos_next = apertureInfoVect(vectorIx_RI_next);
+
+ % interpolate leaf positions
+ leftLeafPos = updatedInfo.propVMAT.beam(i).fracFromLastDAO*leftLeafPos_last+(1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*leftLeafPos_next;
+ rightLeafPos = updatedInfo.propVMAT.beam(i).fracFromLastDAO*rightLeafPos_last+(1-updatedInfo.propVMAT.beam(i).fracFromLastDAO)*rightLeafPos_next;
+
+ % update information in shape structure
+ updatedInfo.beam(i).shape(j).leftLeafPos = leftLeafPos;
+ updatedInfo.beam(i).shape(j).leftLeafPos_I = leftLeafPos;
+ updatedInfo.beam(i).shape(j).leftLeafPos_F = leftLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos = rightLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos_I = rightLeafPos;
+ updatedInfo.beam(i).shape(j).rightLeafPos_F = rightLeafPos;
+ else
+
+ fracFromLastOpt = updatedInfo.propVMAT.beam(i).fracFromLastDAO;
+ fracFromLastOptI = updatedInfo.propVMAT.beam(i).fracFromLastDAO_I*ones(n,1);
+ fracFromLastOptF = updatedInfo.propVMAT.beam(i).fracFromLastDAO_F*ones(n,1);
+ fracFromNextOptI = updatedInfo.propVMAT.beam(i).fracFromNextDAO_I*ones(n,1);
+ fracFromNextOptF = updatedInfo.propVMAT.beam(i).fracFromNextDAO_F*ones(n,1);
+
+ % obtain leaf positions at last DAO beam
+ vectorIx_LF_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).vectorOffset(2) + ((1:n)-1);
+ vectorIx_RF_last = vectorIx_LF_last+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_F_last = apertureInfoVect(vectorIx_LF_last);
+ rightLeafPos_F_last = apertureInfoVect(vectorIx_RF_last);
+
+ % obtain leaf positions at next DAO beam
+ vectorIx_LI_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).vectorOffset(1) + ((1:n)-1);
+ vectorIx_RI_next = vectorIx_LI_next+apertureInfo.totalNumOfLeafPairs;
+ leftLeafPos_I_next = apertureInfoVect(vectorIx_LI_next);
+ rightLeafPos_I_next = apertureInfoVect(vectorIx_RI_next);
+
+ % interpolate leaf positions
+ updatedInfo.beam(i).shape(j).leftLeafPos_I = fracFromLastOptI.*leftLeafPos_F_last+fracFromNextOptI.*leftLeafPos_I_next;
+ updatedInfo.beam(i).shape(j).rightLeafPos_I = fracFromLastOptI.*rightLeafPos_F_last+fracFromNextOptI.*rightLeafPos_I_next;
+
+ updatedInfo.beam(i).shape(j).leftLeafPos_F = fracFromLastOptF.*leftLeafPos_F_last+fracFromNextOptF.*leftLeafPos_I_next;
+ updatedInfo.beam(i).shape(j).rightLeafPos_F = fracFromLastOptF.*rightLeafPos_F_last+fracFromNextOptF.*rightLeafPos_I_next;
+ end
+ end
+
+ if ~updatedInfo.runVMAT
+
+ % rounding for numerical stability
+ leftLeafPos = round2(leftLeafPos,10);
+ rightLeafPos = round2(rightLeafPos,10);
+
+ % check overshoot of leaf positions
+ leftLeafPos(leftLeafPos <= apertureInfo.beam(i).lim_l) = apertureInfo.beam(i).lim_l(leftLeafPos <= apertureInfo.beam(i).lim_l);
+ rightLeafPos(rightLeafPos <= apertureInfo.beam(i).lim_l) = apertureInfo.beam(i).lim_l(rightLeafPos <= apertureInfo.beam(i).lim_l);
+ leftLeafPos(leftLeafPos >= apertureInfo.beam(i).lim_r) = apertureInfo.beam(i).lim_r(leftLeafPos >= apertureInfo.beam(i).lim_r);
+ rightLeafPos(rightLeafPos >= apertureInfo.beam(i).lim_r) = apertureInfo.beam(i).lim_r(rightLeafPos >= apertureInfo.beam(i).lim_r);
+
+ %
+ xPosIndLeftLeaf = round((leftLeafPos - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth + 1);
+ xPosIndRightLeaf = round((rightLeafPos - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth + 1);
+
+ %
+ xPosIndLeftLeaf_lim = floor((apertureInfo.beam(i).lim_l - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth+1);
+ xPosIndRightLeaf_lim = ceil((apertureInfo.beam(i).lim_r - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth + 1);
+
+ xPosIndLeftLeaf(xPosIndLeftLeaf <= xPosIndLeftLeaf_lim) = xPosIndLeftLeaf_lim(xPosIndLeftLeaf <= xPosIndLeftLeaf_lim)+1;
+ xPosIndRightLeaf(xPosIndRightLeaf >= xPosIndRightLeaf_lim) = xPosIndRightLeaf_lim(xPosIndRightLeaf >= xPosIndRightLeaf_lim)-1;
+
+ % check limits because of rounding off issues at maximum, i.e.,
+ % enforce round(X.5) -> X
+ % LeafPos can occasionally go slightly beyond lim_r, so changed
+ % == check to >=
+ xPosIndLeftLeaf(leftLeafPos >= apertureInfo.beam(i).lim_r) = round(...
+ .5 + (leftLeafPos(leftLeafPos >= apertureInfo.beam(i).lim_r) ...
+ - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth);
+
+ xPosIndRightLeaf(rightLeafPos >= apertureInfo.beam(i).lim_r) = round(...
+ .5 + (rightLeafPos(rightLeafPos >= apertureInfo.beam(i).lim_r) ...
+ - apertureInfo.beam(i).posOfCornerBixel(1))/apertureInfo.bixelWidth);
+
+ % find the bixel index that the leaves currently touch
+ bixelIndLeftLeaf = apertureInfo.beam(i).bixelIndMap((xPosIndLeftLeaf-1)*n+[1:n]');
+ bixelIndRightLeaf = apertureInfo.beam(i).bixelIndMap((xPosIndRightLeaf-1)*n+[1:n]');
+
+ if any(isnan(bixelIndLeftLeaf)) || any(isnan(bixelIndRightLeaf))
+ error('cannot map leaf position to bixel index');
+ end
+
+ % store information in index vector for gradient calculation
+ indVect(offset+(1:n)) = bixelIndLeftLeaf;
+ indVect(offset+(1:n)+apertureInfo.doseTotalNumOfLeafPairs) = bixelIndRightLeaf;
+ offset = offset+n;
+
+ % calculate opening fraction for every bixel in shape to construct
+ % bixel weight vector
+
+ coveredByLeftLeaf = bsxfun(@minus,leftLeafPos,edges_l) / updatedInfo.bixelWidth;
+ coveredByRightLeaf = bsxfun(@minus,edges_r,rightLeafPos) / updatedInfo.bixelWidth;
+
+ tempMap = 1 - (coveredByLeftLeaf + abs(coveredByLeftLeaf)) / 2 ...
+ - (coveredByRightLeaf + abs(coveredByRightLeaf)) / 2;
+
+ % find open bixels
+ tempMapIx = tempMap > 0;
+
+ currBixelIx = apertureInfo.beam(i).bixelIndMap(tempMapIx);
+ w(currBixelIx) = w(currBixelIx) + tempMap(tempMapIx)*updatedInfo.beam(i).shape(j).weight;
+
+ % save the tempMap (we need to apply a positivity operator !)
+ updatedInfo.beam(i).shape(j).shapeMap = (tempMap + abs(tempMap)) / 2;
+
+ % increment shape index
+ shapeInd = shapeInd +1;
+ end
+
+ end
+
+ if updatedInfo.runVMAT
+
+ for j = 1:numOfShapes
+
+ % shapeMap
+ shapeMap = zeros(size(updatedInfo.beam(i).bixelIndMap));
+ % sumGradSq
+ sumGradSq = 0;
+
+ % insert variables
+ vectorIndices.tIx_Vec = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+(1:apertureInfo.totalNumOfShapes);
+
+ variables.weight = updatedInfo.beam(i).shape(j).weight;
+ variables.leftLeafPos_I = updatedInfo.beam(i).shape(j).leftLeafPos_I;
+ variables.leftLeafPos_F = updatedInfo.beam(i).shape(j).leftLeafPos_F;
+ variables.rightLeafPos_I = updatedInfo.beam(i).shape(j).rightLeafPos_I;
+ variables.rightLeafPos_F = updatedInfo.beam(i).shape(j).rightLeafPos_F;
+
+ if updatedInfo.propVMAT.beam(i).DAOBeam
+
+ variables.jacobiScale = updatedInfo.beam(i).shape(1).jacobiScale;
+
+ vectorIndices.DAOindex = updatedInfo.propVMAT.beam(i).DAOIndex;
+ if updatedInfo.continuousAperture
+ vectorIndices.vectorIx_LI = updatedInfo.beam(i).shape(j).vectorOffset(1) + ((1:n)-1);
+ vectorIndices.vectorIx_LF = updatedInfo.beam(i).shape(j).vectorOffset(2) + ((1:n)-1);
+ vectorIndices.vectorIx_RI = vectorIndices.vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ vectorIndices.vectorIx_RF = vectorIndices.vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+ else
+ vectorIndices.vectorIx_LI = updatedInfo.beam(i).shape(j).vectorOffset + ((1:n)-1);
+ vectorIndices.vectorIx_LF = updatedInfo.beam(i).shape(j).vectorOffset + ((1:n)-1);
+ vectorIndices.vectorIx_RI = vectorIndices.vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ vectorIndices.vectorIx_RF = vectorIndices.vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+ end
+ else
+
+ variables.weight_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).weight;
+ variables.weight_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).weight;
+
+ variables.jacobiScale_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(1).jacobiScale;
+ variables.jacobiScale_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(1).jacobiScale;
+
+ variables.time_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).time;
+ variables.time_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).time;
+ variables.time = updatedInfo.beam(i).time;
+
+ variables.fracFromLastOptI = fracFromLastOptI;
+ variables.fracFromLastOptF = fracFromLastOptF;
+ variables.fracFromNextOptI = fracFromNextOptI;
+ variables.fracFromNextOptF = fracFromNextOptF;
+ variables.fracFromLastOpt = fracFromLastOpt;
+
+ variables.doseAngleBordersDiff = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff;
+ variables.doseAngleBordersDiff_last = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).doseAngleBordersDiff;
+ variables.doseAngleBordersDiff_next = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).doseAngleBordersDiff;
+ variables.timeFacCurr_last = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).timeFacCurr;
+ variables.timeFacCurr_next = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).timeFacCurr;
+ variables.fracFromLastDAO = updatedInfo.propVMAT.beam(i).fracFromLastDAO;
+ variables.timeFracFromLastDAO = updatedInfo.propVMAT.beam(i).timeFracFromLastDAO;
+ variables.timeFracFromNextDAO = updatedInfo.propVMAT.beam(i).timeFracFromNextDAO;
+
+ vectorIndices.DAOindex_last = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).DAOIndex;
+ vectorIndices.DAOindex_next = updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).DAOIndex;
+ vectorIndices.tIx_last = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).DAOIndex;
+ vectorIndices.tIx_next = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).DAOIndex;
+
+ if updatedInfo.continuousAperture
+ vectorIndices.vectorIx_LF_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).vectorOffset(2) + ((1:n)-1);
+ vectorIndices.vectorIx_LI_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).vectorOffset(1) + ((1:n)-1);
+ vectorIndices.vectorIx_RF_last = vectorIndices.vectorIx_LF_last+apertureInfo.totalNumOfLeafPairs;
+ vectorIndices.vectorIx_RI_next = vectorIndices.vectorIx_LI_next+apertureInfo.totalNumOfLeafPairs;
+ else
+ vectorIndices.vectorIx_LF_last = updatedInfo.beam(updatedInfo.propVMAT.beam(i).lastDAOIndex).shape(j).vectorOffset + ((1:n)-1);
+ vectorIndices.vectorIx_LI_next = updatedInfo.beam(updatedInfo.propVMAT.beam(i).nextDAOIndex).shape(j).vectorOffset + ((1:n)-1);
+ vectorIndices.vectorIx_RF_last = vectorIndices.vectorIx_LF_last+apertureInfo.totalNumOfLeafPairs;
+ vectorIndices.vectorIx_RI_next = vectorIndices.vectorIx_LI_next+apertureInfo.totalNumOfLeafPairs;
+ end
+ end
+
+ counters.bixelJApVec_offset = bixelJApVec_offset;
+
+ % calculate bixel weight and derivative in function
+ [w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMap,counters] = ...
+ matRad_bixWeightAndGrad(calcOptions,mlcOptions,variables,vectorIndices,counters,w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMap);
+
+ bixelJApVec_offset = counters.bixelJApVec_offset;
+
+ % update shapeMap
+ updatedInfo.beam(i).shape(j).shapeMap = shapeMap;
+ % update sumGradSq
+ % FIX THIS FOR INTERPOLATED ANGLES???
+ updatedInfo.beam(i).shape(j).sumGradSq = sumGradSq;
+
+ end
+ end
+end
+
+
+% save bixelWeight, apertureVector
+updatedInfo.bixelWeights = w;
+updatedInfo.apertureVector = apertureInfoVect;
+
+if updatedInfo.runVMAT
+ % save Jacobian between bixelWeight, apertureVector
+
+ deleteInd_i = isnan(bixelJApVec_i);
+ deleteInd_j = bixelJApVec_j == 0;
+ if ~all(deleteInd_i == deleteInd_j)
+ error('Jacobian deletion mismatch');
+ else
+ bixelJApVec_i(deleteInd_i) = [];
+ bixelJApVec_j(deleteInd_i) = [];
+ bixelJApVec_vec(deleteInd_i) = [];
+ end
+ updatedInfo.bixelJApVec = sparse(bixelJApVec_i,bixelJApVec_j,bixelJApVec_vec,numel(apertureInfoVect),updatedInfo.totalNumOfBixels);
+else
+ % save indVect
+ updatedInfo.bixelIndices = indVect;
+end
+
+end
+
diff --git a/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_getConstraintBounds.m b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_getConstraintBounds.m
new file mode 100644
index 000000000..438dda557
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_getConstraintBounds.m
@@ -0,0 +1,61 @@
+function [cl,cu] = matRad_getConstraintBounds(optiProb,cst)
+% matRad IPOPT get constraint bounds function for VMAT
+%
+% call
+% [cl,cu] = matRad_daoGetConstBounds(cst,apertureInfo,type)
+%
+% input
+% cst: matRad cst struct
+% apertureInfo: aperture info struct
+% options: option struct defining the type of optimization
+%
+% output
+% cl: lower bounds on constraints
+% cu: lower bounds on constraints
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+apertureInfo = optiProb.apertureInfo;
+
+% get dosimetric bounds from cst by call to DAO superclass method
+[cl_dos_dao,cu_dos_dao] = matRad_getConstraintBounds@matRad_OptimizationProblemDAO(optiProb,cst);
+
+optInd = find([apertureInfo.propVMAT.beam.DAOBeam]);
+
+
+if apertureInfo.continuousAperture
+ cl_lfspd = apertureInfo.propVMAT.constraints.leafSpeed(1)*ones(2*apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs,1); %Minimum leaf travel speed (mm/s)
+ cu_lfspd = apertureInfo.propVMAT.constraints.leafSpeed(2)*ones(2*apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs,1); %Maximum leaf travel speed (mm/s)
+ %apertureInfo.beam(i).numOfActiveLeafPairs should be independent of i, due to using the union of all ray positions in the stf
+ %Convert from cm/deg when checking constraints; cannot do it at this stage since gantry rotation speed is not hard-coded
+else
+
+ cl_lfspd = apertureInfo.propVMAT.constraints.leafSpeed(1)*ones(2*(numel(optInd)-1)*apertureInfo.beam(1).numOfActiveLeafPairs,1); %Minimum leaf travel speed (mm/s)
+ cu_lfspd = apertureInfo.propVMAT.constraints.leafSpeed(2)*ones(2*(numel(optInd)-1)*apertureInfo.beam(1).numOfActiveLeafPairs,1); %Maximum leaf travel speed (mm/s)
+ %apertureInfo.beam(i).numOfActiveLeafPairs should be independent of i, due to using the union of all ray positions in the stf
+ %Convert from cm/deg when checking constraints; cannot do it at this stage since gantry rotation speed is not hard-coded
+end
+cl_dosrt = apertureInfo.propVMAT.constraints.monitorUnitRate(1)*ones(numel(optInd),1); %Minimum MU/sec
+cu_dosrt = apertureInfo.propVMAT.constraints.monitorUnitRate(2)*ones(numel(optInd),1); %Maximum MU/sec
+
+% concatenate
+cl = [cl_dos_dao; cl_lfspd; cl_dosrt];
+cu = [cu_dos_dao; cu_lfspd; cu_dosrt];
+
+
diff --git a/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_getJacobianStructure.m b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_getJacobianStructure.m
new file mode 100644
index 000000000..149c31ce3
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_getJacobianStructure.m
@@ -0,0 +1,326 @@
+function jacobStruct = matRad_getJacobianStructure(optiProb,apertureInfoVec,dij,cst)
+% matRad IPOPT callback: get jacobian structure for direct aperture optimization
+%
+% call
+% jacobStruct = matRad_daoGetJacobStruct(apertureInfo,dij,cst)
+%
+% input
+% apertureInfo: aperture info struct
+% dij: dose influence matrix
+% cst: matRad cst struct
+%
+% output
+% jacobStruct: jacobian of constraint function
+%
+% References
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2015 the matRad development team.
+%
+% This file is part of the matRad project. It is subject to the license
+% terms in the LICENSE file found in the top-level directory of this
+% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
+% of the matRad project, including this file, may be copied, modified,
+% propagated, or distributed except according to the terms contained in the
+% LICENSE file.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%Here we can't use the DAO superclass function, since the dosimetric
+%jacobian needs to be altered
+
+apertureInfo = optiProb.apertureInfo;
+
+% jacobian structure of the dao constraints
+% row indices
+i = repmat(1:apertureInfo.totalNumOfLeafPairs,1,2);
+% column indices
+j = [(apertureInfo.totalNumOfShapes+1):(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs) ...
+ ((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs)+1):(apertureInfo.totalNumOfShapes+2*apertureInfo.totalNumOfLeafPairs)];
+
+% -1 for left leaves, 1 for right leaves
+s = ones(1,2*apertureInfo.totalNumOfLeafPairs);
+
+jacobStruct_dao = sparse(i,j,s, ...
+ apertureInfo.totalNumOfLeafPairs, ...
+ numel(apertureInfo.apertureVector), ...
+ 2*apertureInfo.totalNumOfLeafPairs);
+
+jacobStruct_dos_bixel = matRad_getJacobianStructure@matRad_OptimizationProblem(optiProb,apertureInfo.bixelWeights,dij,cst);
+
+% --> gives me a matrix with number of rows = num of constraints and tells
+% me in th columns if a beamlet has an influence on this constraint
+
+% for apertures I need to check if the very beam orientation of the aperture has a bixel
+% that potentially influences the constraint
+
+% for leaves I need to check if that particular leaf row has bixels that
+% potentially influence the objective which works via apertureInfo.beam(i).bixelIndMap
+
+% all stuff can be done per beam direction and then I use repmat to build
+% up the big matrix
+
+numOfConstraints = size(jacobStruct_dos_bixel,1);
+
+i_sparse = 1:numOfConstraints;
+i_sparse = kron(i_sparse,ones(1,numel(apertureInfo.apertureVector)));
+
+j_sparse = 1:numel(apertureInfo.apertureVector);
+j_sparse = repmat(j_sparse,1,numOfConstraints);
+
+jacobStructSparseVec = zeros(numOfConstraints*numel(apertureInfo.apertureVector),1);
+
+offset = 1;
+if apertureInfo.runVMAT && apertureInfo.continuousAperture
+ repFactor = 2;
+else
+ repFactor = 1;
+end
+
+if ~isempty(jacobStruct_dos_bixel)
+
+ DAOBeams = find([apertureInfo.propVMAT.beam.DAOBeam]);
+
+ for i = 1:numel(apertureInfo.beam)
+
+ % get used bixels in beam
+ ixWeight = ~isnan(apertureInfo.beam(i).bixelIndMap);
+
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ % DAO beam, don't worry about adding since this is just
+ % struct, i.e. we are only interested if the element is
+ % non-zero
+
+ % first weight
+ jacobStructSparseVec(offset == j_sparse) = jacobStructSparseVec(offset == j_sparse)+sum(jacobStruct_dos_bixel(:,apertureInfo.beam(i).bixelIndMap(ixWeight)),2);
+
+ % now leaf positions
+ for k = 1:apertureInfo.beam(i).numOfActiveLeafPairs
+
+ ixLeaf = ~isnan(apertureInfo.beam(i).bixelIndMap(k,:));
+ indInBixVec = apertureInfo.beam(i).bixelIndMap(k,ixLeaf);
+
+ indInOptVec = apertureInfo.beam(i).shape(1).vectorOffset+k-1;
+ indInOptVec = repmat(indInOptVec,1,repFactor)+repelem([0 apertureInfo.totalNumOfLeafPairs],1,repFactor);
+
+ indInSparseVec = repmat(indInOptVec,1,numOfConstraints)...
+ +kron((0:numOfConstraints-1)*numel(apertureInfo.apertureVector),ones(1,2*repFactor));
+
+ jacobStructSparseVec(indInSparseVec) = jacobStructSparseVec(indInSparseVec)+repelem(sum(jacobStruct_dos_bixel(:,indInBixVec),2),2*repFactor,1);
+ end
+
+ offset = offset+1;
+ else
+ % not DAO beam, these may contain bixels which affect the
+ % constraints which are influenced by DAO leaf pairs that
+ % do not affect the constraints (unlikely to happen, but it
+ % might)
+
+ %first weight
+
+ %give fraction of gradient to previous optimized beam
+ lastDAOInd = find(DAOBeams == apertureInfo.propVMAT.beam(i).lastDAOIndex,1);
+ jacobStructSparseVec(lastDAOInd == j_sparse) = jacobStructSparseVec(lastDAOInd == j_sparse)+sum(jacobStruct_dos_bixel(:,apertureInfo.beam(i).bixelIndMap(ixWeight)),2);
+ %give the other fraction to next optimized beam
+ nextDAOInd = find(DAOBeams == apertureInfo.propVMAT.beam(i).nextDAOIndex,1);
+ jacobStructSparseVec(nextDAOInd == j_sparse) = jacobStructSparseVec(nextDAOInd == j_sparse)+sum(jacobStruct_dos_bixel(:,apertureInfo.beam(i).bixelIndMap(ixWeight)),2);
+
+ %now leaf pos
+
+ for k = 1:apertureInfo.beam(i).numOfActiveLeafPairs
+
+ ixLeaf = ~isnan(apertureInfo.beam(i).bixelIndMap(k,:));
+ indInBixVec = apertureInfo.beam(i).bixelIndMap(k,ixLeaf);
+
+ %give fraction of gradient to previous optimized beam
+ indInOptVec = apertureInfo.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).shape(1).vectorOffset+k-1;
+ indInOptVec = repmat(indInOptVec,1,repFactor)+repelem([0 apertureInfo.totalNumOfLeafPairs],1,repFactor);
+ indInSparseVec = repmat(indInOptVec,1,numOfConstraints)...
+ +kron((0:numOfConstraints-1)*numel(apertureInfo.apertureVector),ones(1,2*repFactor));
+
+ jacobStructSparseVec(indInSparseVec) = jacobStructSparseVec(indInSparseVec)+repelem(sum(jacobStruct_dos_bixel(:,indInBixVec),2),2*repFactor,1);
+
+
+ %give the other fraction to next optimized beam
+ indInOptVec = apertureInfo.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).shape(1).vectorOffset+k-1;
+ indInOptVec = repmat(indInOptVec,1,repFactor)+repelem([0 apertureInfo.totalNumOfLeafPairs],1,repFactor);
+ indInSparseVec = repmat(indInOptVec,1,numOfConstraints)...
+ +kron((0:numOfConstraints-1)*numel(apertureInfo.apertureVector),ones(1,2*repFactor));
+
+ jacobStructSparseVec(indInSparseVec) = jacobStructSparseVec(indInSparseVec)+repelem(sum(jacobStruct_dos_bixel(:,indInBixVec),2),2*repFactor,1);
+ end
+
+ %now time
+
+ %give fraction of gradient to previous optimized beam
+ lastDAOIndTime = find(DAOBeams == apertureInfo.propVMAT.beam(i).lastDAOIndex,1)+(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2);
+ jacobStructSparseVec(lastDAOIndTime == j_sparse) = jacobStructSparseVec(lastDAOIndTime == j_sparse)+sum(jacobStruct_dos_bixel(:,apertureInfo.beam(i).bixelIndMap(ixWeight)),2);
+
+ %give the other fraction to next optimized beam
+ nextDAOIndTime = find(DAOBeams == apertureInfo.propVMAT.beam(i).nextDAOIndex,1)+(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2);
+ jacobStructSparseVec(nextDAOIndTime == j_sparse) = jacobStructSparseVec(nextDAOIndTime == j_sparse)+sum(jacobStruct_dos_bixel(:,apertureInfo.beam(i).bixelIndMap(ixWeight)),2);
+ end
+ end
+
+ jacobStructSparseVec(jacobStructSparseVec ~= 0) = 1;
+ jacobStruct_dos = sparse(i_sparse,j_sparse,jacobStructSparseVec,numOfConstraints,numel(apertureInfo.apertureVector));
+else
+ jacobStruct_dos = sparse(0,0);
+end
+
+jacobStruct_dos_dao = [jacobStruct_dos; jacobStruct_dao];
+
+if apertureInfo.continuousAperture
+ % set up
+ n = apertureInfo.beam(1).numOfActiveLeafPairs;
+ indInSparseVec = (1:n);
+ indInConVec = (1:n);
+ shapeInd = 1;
+
+ % sparse matrix
+ numElem = n.*(apertureInfo.propVMAT.numLeafSpeedConstraintDAO*6+(apertureInfo.propVMAT.numLeafSpeedConstraint-apertureInfo.propVMAT.numLeafSpeedConstraintDAO)*8);
+ i_sparse = zeros(numElem,1);
+ j_sparse = zeros(numElem,1);
+ s_sparse = ones(numElem,1);
+
+ for i = 1:numel(apertureInfo.beam)
+ % loop over beams
+
+ if ~isempty(apertureInfo.propVMAT.beam(i).leafConstMask)
+
+ % get vector indices
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ % if it's a DAO beam, use own vector offset
+ vectorIx_LI = apertureInfo.beam(i).shape(1).vectorOffset(1) + ((1:n)-1);
+ vectorIx_LF = apertureInfo.beam(i).shape(1).vectorOffset(2) + ((1:n)-1);
+ else
+ % otherwise, use vector offset of previous and next
+ % beams
+ vectorIx_LI = apertureInfo.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).shape(1).vectorOffset(2) + ((1:n)-1);
+ vectorIx_LF = apertureInfo.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).shape(1).vectorOffset(1) + ((1:n)-1);
+ end
+ vectorIx_RI = vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ vectorIx_RF = vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+
+ % calc jacobs
+
+ % wrt initial leaf positions (left, then right)
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = vectorIx_LI;
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = vectorIx_RI;
+ indInSparseVec = indInSparseVec+n;
+
+ % wrt final leaf positions (left, then right)
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = vectorIx_LF;
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = vectorIx_RF;
+ indInSparseVec = indInSparseVec+n;
+
+ % wrt time (left, then right)
+ % how we do this depends on if it's a DAO beam or
+ % not
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ % if it is, then speeds only depend on its own
+ % time
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(i).timeInd;
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(i).timeInd;
+ indInSparseVec = indInSparseVec+n;
+
+ else
+ % otherwise, speed depends on time of DAO
+ % before and DAO after
+
+ % before
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).timeInd;
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).timeInd;
+ indInSparseVec = indInSparseVec+n;
+
+ % after
+ i_sparse(indInSparseVec) = indInConVec;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).timeInd;
+ indInSparseVec = indInSparseVec+n;
+
+ i_sparse(indInSparseVec) = indInConVec+apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs;
+ j_sparse(indInSparseVec) = apertureInfo.propVMAT.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).timeInd;
+ indInSparseVec = indInSparseVec+n;
+
+ end
+
+ % update offset
+ indInConVec = indInConVec+n;
+
+ % increment shapeInd only for beams which have transtion
+ % defined
+ shapeInd = shapeInd+1;
+ end
+ end
+
+ jacobStruct_lfspd = sparse(i_sparse,j_sparse,s_sparse,2*apertureInfo.beam(1).numOfActiveLeafPairs*apertureInfo.propVMAT.numLeafSpeedConstraint,numel(apertureInfo.apertureVector));
+
+else
+
+ i = sort(repmat(1:(apertureInfo.totalNumOfShapes-1),1,2));
+ j = sort(repmat(1:apertureInfo.totalNumOfShapes,1,2));
+ j(1) = [];
+ j(end) = [];
+
+ % get index values for the jacobian
+ % variable index
+ timeInd = (1+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2):(apertureInfo.totalNumOfShapes*2+apertureInfo.totalNumOfLeafPairs*2-1);
+ currentLeftLeafInd = (apertureInfo.totalNumOfShapes+1):(apertureInfo.totalNumOfShapes+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeInd));
+ currentRightLeafInd = (apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+1):(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeInd));
+ nextLeftLeafInd = (apertureInfo.beam(1).numOfActiveLeafPairs+apertureInfo.totalNumOfShapes+1):(apertureInfo.beam(1).numOfActiveLeafPairs+apertureInfo.totalNumOfShapes+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeInd));
+ nextRightLeafInd = (apertureInfo.beam(1).numOfActiveLeafPairs+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+1):(apertureInfo.beam(1).numOfActiveLeafPairs+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeInd));
+ leftTimeInd = kron(j,ones(1,apertureInfo.beam(1).numOfActiveLeafPairs))+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2;
+ rightTimeInd = kron(j,ones(1,apertureInfo.beam(1).numOfActiveLeafPairs))+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2;
+ %leftTimeInd = repelem(j,apertureInfo.beam(1).numOfActiveLeafPairs)+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2;
+ %rightTimeInd = repelem(j,apertureInfo.beam(1).numOfActiveLeafPairs)+apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2;
+ % constraint index
+ leafConstraintInd = 1:2*apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeInd);
+
+ % jacobian of the leafspeed constraint
+ i = repmat((i'-1)*apertureInfo.beam(1).numOfActiveLeafPairs,1,apertureInfo.beam(1).numOfActiveLeafPairs)+repmat(1:apertureInfo.beam(1).numOfActiveLeafPairs,2*numel(timeInd),1);
+ i = reshape([i' i'+apertureInfo.beam(1).numOfActiveLeafPairs*numel(timeInd)],1,[]);
+
+ i = [repmat(leafConstraintInd,1,2) i];
+ j = [currentLeftLeafInd currentRightLeafInd nextLeftLeafInd nextRightLeafInd leftTimeInd rightTimeInd];
+ % first do jacob wrt current leaf position (left, right), then next leaf
+ % position (left, right), then time (left, right)
+
+ s = ones(1,numel(j));
+
+ jacobStruct_lfspd = sparse(i,j,s,2*apertureInfo.beam(1).numOfActiveLeafPairs*(apertureInfo.totalNumOfShapes-1),numel(apertureInfo.apertureVector),numel(s));
+end
+
+% jacobian of the doserate constraint
+i = repmat(1:(apertureInfo.totalNumOfShapes),1,2);
+j = [1:(apertureInfo.totalNumOfShapes) ...
+ ((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+1):((apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2)+apertureInfo.totalNumOfShapes)];
+% first do jacob wrt weights, then wrt times
+
+s = ones(1,2*apertureInfo.totalNumOfShapes);
+
+jacobStruct_dosrt = sparse(i,j,s,apertureInfo.totalNumOfShapes,numel(apertureInfo.apertureVector),2*apertureInfo.totalNumOfShapes);
+
+% concatenate
+jacobStruct = [jacobStruct_dos_dao; jacobStruct_lfspd; jacobStruct_dosrt];
+
+
+
diff --git a/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_objectiveGradient.m b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_objectiveGradient.m
new file mode 100644
index 000000000..e20ab5c63
--- /dev/null
+++ b/matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_objectiveGradient.m
@@ -0,0 +1,51 @@
+function g = matRad_objectiveGradient(optiProb,apertureInfoVec,dij,cst)
+% matRad IPOPT callback: gradient function for direct aperture optimization
+%
+% call
+% g = matRad_daoGradFunc(apertureInfoVec,apertureInfo,dij,cst,type)
+%
+% input
+% apertureInfoVec: aperture info in form of vector
+% dij: matRad dij struct as generated by bixel-based dose calculation
+% cst: matRad cst struct
+% options: option struct defining the type of optimization
+%
+% output
+% g: gradient
+%
+% References
+% [1] http://dx.doi.org/10.1118/1.4914863
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%We don't use the DAO gradient in here, because dosimetric stuff is altered
+
+% update apertureInfo, bixel weight vector an mapping of leafes to bixels
+if ~isequal(apertureInfoVec,optiProb.apertureInfo.apertureVector)
+ optiProb.apertureInfo = optiProb.matRad_daoVec2ApertureInfo(optiProb.apertureInfo,apertureInfoVec);
+end
+apertureInfo = optiProb.apertureInfo;
+
+% bixel based gradient calculation
+bixelG = matRad_objectiveGradient@matRad_OptimizationProblem(optiProb,apertureInfo.bixelWeights,dij,cst);
+
+% allocate gradient vector for aperture weights and leaf positions
+%g = NaN * ones(size(apertureInfoVec,1),1);
+
+% use the Jacobian calculated in daoVec2ApertureInfo.
+% should also do this for non-VMAT
+g = apertureInfo.bixelJApVec * bixelG;
+
diff --git a/matRad/optimization/matRad_bixWeightAndGrad.m b/matRad/optimization/matRad_bixWeightAndGrad.m
new file mode 100644
index 000000000..63466d5ae
--- /dev/null
+++ b/matRad/optimization/matRad_bixWeightAndGrad.m
@@ -0,0 +1,422 @@
+function [w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMapW,counters] = ...
+ matRad_bixWeightAndGrad(calcOptions,mlcOptions,variables,vectorIndices,counters,w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMapW)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad function to calculate the bixel weights from the aperture vector,
+% and also the Jacobian matrix relating these two.
+%
+% call
+% [w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sqrtSumGradSq,shapeMap,counters] = ...
+% matRad_bixWeightAndGrad(calcOptions,mlcOptions,variables,vectorIndices,counters,w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j)
+%
+% input
+%
+% output
+%
+% References
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Copyright 2018, Mark Bangert, on behalf of the matRad development team
+%
+% m.bangert@dkfz.de
+%
+% This file is part of matRad.
+%
+% matrad is free software: you can redistribute it and/or modify it under
+% the terms of the GNU General Public License as published by the Free
+% Software Foundation, either version 3 of the License, or (at your option)
+% any later version.
+%
+% matRad is distributed in the hope that it will be useful, but WITHOUT ANY
+% WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+% FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+% details.
+%
+% You should have received a copy of the GNU General Public License in the
+% file license.txt along with matRad. If not, see
+% .
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+round2 = @(a,b) round(a*10^b)/10^b;
+
+%% extract variables from inputs
+lim_l = mlcOptions.lim_l;
+lim_r = mlcOptions.lim_r;
+edges_l = mlcOptions.edges_l;
+edges_r = mlcOptions.edges_r;
+centres = mlcOptions.centres;
+widths = mlcOptions.widths;
+n = mlcOptions.n;
+numBix = mlcOptions.numBix;
+bixelWidth = mlcOptions.bixelWidth;
+bixelIndMap = mlcOptions.bixelIndMap;
+
+weight = variables.weight;
+leftLeafPos_I = variables.leftLeafPos_I;
+leftLeafPos_F = variables.leftLeafPos_F;
+rightLeafPos_I = variables.rightLeafPos_I;
+rightLeafPos_F = variables.rightLeafPos_F;
+
+bixelJApVec_offset = counters.bixelJApVec_offset;
+
+
+%% sort out order, set up calculation of bixel weight and gradients
+
+% set the initial leaf positions to the minimum leaf positions
+% always, instead of the leaf positions at the actual beginning
+% of the arc
+% this simplifies the calculation
+% remember which one is actually I and F in leftMinInd
+[leftLeafPosI,leftMinInd] = min([leftLeafPos_I,leftLeafPos_F],[],2);
+leftLeafPosF = max([leftLeafPos_I,leftLeafPos_F],[],2);
+[rightLeafPosI,rightMinInd] = min([rightLeafPos_I,rightLeafPos_F],[],2);
+rightLeafPosF = max([rightLeafPos_I,rightLeafPos_F],[],2);
+
+if calcOptions.saveJacobian
+ % only need these variables for the Jacobian
+
+ if calcOptions.DAOBeam
+ jacobiScale = variables.jacobiScale;
+
+ vectorIx_LI = vectorIndices.vectorIx_LI;
+ vectorIx_LF = vectorIndices.vectorIx_LF;
+ vectorIx_RI = vectorIndices.vectorIx_RI;
+ vectorIx_RF = vectorIndices.vectorIx_RF;
+ DAOindex = vectorIndices.DAOindex;
+
+ % change the vectorIx_xy elements to remember which
+ % apertureVector elements the "new" I and F
+ % if leftMinInd is 2, the I and F are switched
+ tempL = vectorIx_LI;
+ tempR = vectorIx_RI;
+ vectorIx_LI(leftMinInd == 2) = vectorIx_LF(leftMinInd == 2);
+ vectorIx_LF(leftMinInd == 2) = tempL(leftMinInd == 2);
+ vectorIx_RI(rightMinInd == 2) = vectorIx_RF(rightMinInd == 2);
+ vectorIx_RF(rightMinInd == 2) = tempR(rightMinInd == 2);
+ else
+
+ weight_last = variables.weight_last;
+ weight_next = variables.weight_next;
+ jacobiScale_last = variables.jacobiScale_last;
+ jacobiScale_next = variables.jacobiScale_next;
+
+ time_last = variables.time_last;
+ time_next = variables.time_next;
+ time = variables.time;
+
+ fracFromLastOptI = variables.fracFromLastOptI;
+ fracFromLastOptF = variables.fracFromLastOptF;
+ fracFromNextOptI = variables.fracFromNextOptI;
+ fracFromNextOptF = variables.fracFromNextOptF;
+ fracFromLastOpt = variables.fracFromLastOpt;
+
+ % replicate
+ fracFromLastOptI = repmat(fracFromLastOptI,1,numBix);
+ fracFromLastOptF = repmat(fracFromLastOptF,1,numBix);
+ fracFromNextOptI = repmat(fracFromNextOptI,1,numBix);
+ fracFromNextOptF = repmat(fracFromNextOptF,1,numBix);
+
+ doseAngleBordersDiff = variables.doseAngleBordersDiff;
+ doseAngleBordersDiff_last = variables.doseAngleBordersDiff_last;
+ doseAngleBordersDiff_next = variables.doseAngleBordersDiff_next;
+ timeFacCurr_last = variables.timeFacCurr_last;
+ timeFacCurr_next = variables.timeFacCurr_next;
+ fracFromLastDAO = variables.fracFromLastDAO;
+ timeFracFromLastDAO = variables.timeFracFromLastDAO;
+ timeFracFromNextDAO = variables.timeFracFromNextDAO;
+
+ vectorIx_LF_last = vectorIndices.vectorIx_LF_last;
+ vectorIx_LI_next = vectorIndices.vectorIx_LI_next;
+ vectorIx_RF_last = vectorIndices.vectorIx_RF_last;
+ vectorIx_RI_next = vectorIndices.vectorIx_RI_next;
+ DAOindex_last = vectorIndices.DAOindex_last;
+ DAOindex_next = vectorIndices.DAOindex_next;
+ tIx_last = vectorIndices.tIx_last;
+ tIx_next = vectorIndices.tIx_next;
+
+ tempL = vectorIx_LF_last;
+ tempR = vectorIx_RF_last;
+
+ vectorIx_LF_last(leftMinInd == 2) = vectorIx_LI_next(leftMinInd == 2);
+ vectorIx_LI_next(leftMinInd == 2) = tempL(leftMinInd == 2);
+
+ vectorIx_RF_last(rightMinInd == 2) = vectorIx_RI_next(rightMinInd == 2);
+ vectorIx_RI_next(rightMinInd == 2) = tempR(rightMinInd == 2);
+ end
+end
+
+leftLeafPosI = round2(leftLeafPosI,10);
+leftLeafPosF = round2(leftLeafPosF,10);
+rightLeafPosI = round2(rightLeafPosI,10);
+rightLeafPosF = round2(rightLeafPosF,10);
+
+leftLeafPosI(leftLeafPosI <= lim_l) = lim_l(leftLeafPosI <= lim_l);
+leftLeafPosF(leftLeafPosF <= lim_l) = lim_l(leftLeafPosF <= lim_l);
+rightLeafPosI(rightLeafPosI <= lim_l) = lim_l(rightLeafPosI <= lim_l);
+rightLeafPosF(rightLeafPosF <= lim_l) = lim_l(rightLeafPosF <= lim_l);
+leftLeafPosI(leftLeafPosI >= lim_r) = lim_r(leftLeafPosI >= lim_r);
+leftLeafPosF(leftLeafPosF >= lim_r) = lim_r(leftLeafPosF >= lim_r);
+rightLeafPosI(rightLeafPosI >= lim_r) = lim_r(rightLeafPosI >= lim_r);
+rightLeafPosF(rightLeafPosF >= lim_r) = lim_r(rightLeafPosF >= lim_r);
+
+% find bixel indices where leaves are located
+xPosIndLeftLeafI = min(floor((leftLeafPosI-edges_l(1))./bixelWidth)+1,numBix);
+xPosIndLeftLeafF = min(floor((leftLeafPosF-edges_l(1))./bixelWidth)+1,numBix);
+xPosIndRightLeafI = min(floor((rightLeafPosI-edges_l(1))./bixelWidth)+1,numBix);
+xPosIndRightLeafF = min(floor((rightLeafPosF-edges_l(1))./bixelWidth)+1,numBix);
+%
+xPosLinearIndLeftLeafI = sub2ind([n numBix],(1:n)',xPosIndLeftLeafI);
+xPosLinearIndLeftLeafF = sub2ind([n numBix],(1:n)',xPosIndLeftLeafF);
+xPosLinearIndRightLeafI = sub2ind([n numBix],(1:n)',xPosIndRightLeafI);
+xPosLinearIndRightLeafF = sub2ind([n numBix],(1:n)',xPosIndRightLeafF);
+
+
+%
+% leaves sweep from _I to _F, with weight
+%
+
+
+%% bixel weight calculation
+
+%calculate fraction of fluence uncovered by left leaf
+%initial computation
+uncoveredByLeftLeaf = bsxfun(@minus,centres,leftLeafPosI)./repmat(leftLeafPosF-leftLeafPosI,1,numBix);
+%correct for overshoot in initial and final leaf positions
+uncoveredByLeftLeaf(xPosLinearIndLeftLeafI) = uncoveredByLeftLeaf(xPosLinearIndLeftLeafI) + (leftLeafPosI-edges_l(xPosIndLeftLeafI)').^2./((leftLeafPosF-leftLeafPosI).*(widths(xPosIndLeftLeafI)').*2);
+uncoveredByLeftLeaf(xPosLinearIndLeftLeafF) = uncoveredByLeftLeaf(xPosLinearIndLeftLeafF) - (edges_r(xPosIndLeftLeafF)'-leftLeafPosF).^2./((leftLeafPosF-leftLeafPosI).*(widths(xPosIndLeftLeafF)').*2);
+%round <0 to 0, >1 to 1
+uncoveredByLeftLeaf(uncoveredByLeftLeaf < 0) = 0;
+uncoveredByLeftLeaf(uncoveredByLeftLeaf > 1) = 1;
+
+%calculate fraction of fluence covered by right leaf
+%initial computation
+coveredByRightLeaf = bsxfun(@minus,centres,rightLeafPosI)./repmat(rightLeafPosF-rightLeafPosI,1,numBix);
+%correct for overshoot in initial and final leaf positions
+coveredByRightLeaf(xPosLinearIndRightLeafI) = coveredByRightLeaf(xPosLinearIndRightLeafI) + (rightLeafPosI-edges_l(xPosIndRightLeafI)').^2./((rightLeafPosF-rightLeafPosI).*(widths(xPosIndRightLeafI)').*2);
+coveredByRightLeaf(xPosLinearIndRightLeafF) = coveredByRightLeaf(xPosLinearIndRightLeafF) - (edges_r(xPosIndRightLeafF)'-rightLeafPosF).^2./((rightLeafPosF-rightLeafPosI).*(widths(xPosIndRightLeafF)').*2);
+%round <0 to 0, >1 to 1
+coveredByRightLeaf(coveredByRightLeaf < 0) = 0;
+coveredByRightLeaf(coveredByRightLeaf > 1) = 1;
+
+%% gradient calculation
+
+dUl_dLI = bsxfun(@minus,centres,leftLeafPosF)./(repmat(leftLeafPosF-leftLeafPosI,1,numBix)).^2;
+dUl_dLF = bsxfun(@minus,leftLeafPosI,centres)./(repmat(leftLeafPosF-leftLeafPosI,1,numBix)).^2;
+
+dCr_dRI = bsxfun(@minus,centres,rightLeafPosF)./(repmat(rightLeafPosF-rightLeafPosI,1,numBix)).^2;
+dCr_dRF = bsxfun(@minus,rightLeafPosI,centres)./(repmat(rightLeafPosF-rightLeafPosI,1,numBix)).^2;
+
+dUl_dLI(xPosLinearIndLeftLeafI) = dUl_dLI(xPosLinearIndLeftLeafI) + ((leftLeafPosI-edges_l(xPosIndLeftLeafI)').*(2*leftLeafPosF-leftLeafPosI-edges_l(xPosIndLeftLeafI)'))./((leftLeafPosF-leftLeafPosI).^2.*(widths(xPosIndLeftLeafI)').*2);
+dUl_dLF(xPosLinearIndLeftLeafI) = dUl_dLF(xPosLinearIndLeftLeafI) - ((leftLeafPosI-edges_l(xPosIndLeftLeafI)').^2)./((leftLeafPosF-leftLeafPosI).^2.*(widths(xPosIndLeftLeafI)').*2);
+dUl_dLI(xPosLinearIndLeftLeafF) = dUl_dLI(xPosLinearIndLeftLeafF) - ((edges_r(xPosIndLeftLeafF)'-leftLeafPosF).^2)./((leftLeafPosF-leftLeafPosI).^2.*(widths(xPosIndLeftLeafF)').*2);
+dUl_dLF(xPosLinearIndLeftLeafF) = dUl_dLF(xPosLinearIndLeftLeafF) + ((edges_r(xPosIndLeftLeafF)'-leftLeafPosF).*(leftLeafPosF+edges_r(xPosIndLeftLeafF)'-2*leftLeafPosI))./((leftLeafPosF-leftLeafPosI).^2.*(widths(xPosIndLeftLeafF)').*2);
+
+dCr_dRI(xPosLinearIndRightLeafI) = dCr_dRI(xPosLinearIndRightLeafI) + ((rightLeafPosI-edges_l(xPosIndRightLeafI)').*(2*rightLeafPosF-rightLeafPosI-edges_l(xPosIndRightLeafI)'))./((rightLeafPosF-rightLeafPosI).^2.*(widths(xPosIndRightLeafI)').*2);
+dCr_dRF(xPosLinearIndRightLeafI) = dCr_dRF(xPosLinearIndRightLeafI) - ((rightLeafPosI-edges_l(xPosIndRightLeafI)').^2)./((rightLeafPosF-rightLeafPosI).^2.*(widths(xPosIndRightLeafI)').*2);
+dCr_dRI(xPosLinearIndRightLeafF) = dCr_dRI(xPosLinearIndRightLeafF) - ((edges_r(xPosIndRightLeafF)'-rightLeafPosF).^2)./((rightLeafPosF-rightLeafPosI).^2.*(widths(xPosIndRightLeafF)').*2);
+dCr_dRF(xPosLinearIndRightLeafF) = dCr_dRF(xPosLinearIndRightLeafF) + ((edges_r(xPosIndRightLeafF)'-rightLeafPosF).*(rightLeafPosF+edges_r(xPosIndRightLeafF)'-2*rightLeafPosI))./((rightLeafPosF-rightLeafPosI).^2.*(widths(xPosIndRightLeafF)').*2);
+
+for k = 1:n
+ dUl_dLI(k,1:(xPosIndLeftLeafI(k)-1)) = 0;
+ dUl_dLF(k,1:(xPosIndLeftLeafI(k)-1)) = 0;
+ dUl_dLI(k,(xPosIndLeftLeafF(k)+1):numBix) = 0;
+ dUl_dLF(k,(xPosIndLeftLeafF(k)+1):numBix) = 0;
+
+ if xPosIndLeftLeafI(k) >= xPosIndLeftLeafF(k)
+ % in discrete aperture, the xPosIndLeftLeafI is greater than
+ % xPosIndLeftLeafM when leaf positions are at a bixel boundary
+
+ %19 July 2017 in journal
+ dUl_dLI(k,xPosIndLeftLeafI(k)) = -1/(2*widths(xPosIndLeftLeafI(k))');
+ dUl_dLF(k,xPosIndLeftLeafF(k)) = -1/(2*widths(xPosIndLeftLeafF(k))');
+ if leftLeafPosF(k)-leftLeafPosI(k) <= eps(max(lim_r))
+ uncoveredByLeftLeaf(k,xPosIndLeftLeafI(k)) = (edges_r(xPosIndLeftLeafI(k))-leftLeafPosI(k))./widths(xPosIndLeftLeafI(k));
+ uncoveredByLeftLeaf(k,xPosIndLeftLeafF(k)) = (edges_r(xPosIndLeftLeafF(k))-leftLeafPosF(k))./widths(xPosIndLeftLeafF(k));
+ end
+ end
+
+ dCr_dRI(k,1:(xPosIndRightLeafI(k)-1)) = 0;
+ dCr_dRF(k,1:(xPosIndRightLeafI(k)-1)) = 0;
+ dCr_dRI(k,(xPosIndRightLeafF(k)+1):numBix) = 0;
+ dCr_dRF(k,(xPosIndRightLeafF(k)+1):numBix) = 0;
+
+ if xPosIndRightLeafI(k) >= xPosIndRightLeafF(k)
+ dCr_dRI(k,xPosIndRightLeafI(k)) = -1/(2*widths(xPosIndRightLeafI(k))');
+ dCr_dRF(k,xPosIndRightLeafF(k)) = -1/(2*widths(xPosIndRightLeafF(k))');
+ if rightLeafPosF(k)-rightLeafPosI(k) <= eps(max(lim_r))
+ coveredByRightLeaf(k,xPosIndRightLeafI(k)) = (edges_r(xPosIndRightLeafI(k))-rightLeafPosI(k))./widths(xPosIndRightLeafI(k));
+ coveredByRightLeaf(k,xPosIndRightLeafF(k)) = (edges_r(xPosIndRightLeafF(k))-rightLeafPosF(k))./widths(xPosIndRightLeafF(k));
+ end
+ end
+end
+
+% store information for Jacobi preconditioning
+sumGradSq = sumGradSq+mean([sum((dUl_dLI).^2,2); sum((dUl_dLF).^2,2); sum((dUl_dLF).^2,2); sum((dCr_dRI).^2,2); sum((dCr_dRF).^2,2); sum((dCr_dRF).^2,2)]);
+
+%% save the bixel weights
+%fluence is equal to fluence not covered by left leaf minus
+%fluence covered by left leaf
+shapeMap = uncoveredByLeftLeaf-coveredByRightLeaf;
+shapeMap = round2(shapeMap,15);
+shapeMap(isnan(shapeMap)) = 0;
+
+% find open bixels
+%shapeMapIx = shapeMap > 0;
+shapeMapIx = ~isnan(bixelIndMap);
+
+currBixelIx = bixelIndMap(shapeMapIx);
+w(currBixelIx) = w(currBixelIx) + shapeMap(shapeMapIx).*weight;
+shapeMapW = shapeMapW+shapeMap.*weight;
+
+%% save the gradients
+
+if calcOptions.saveJacobian
+
+ numSaveBixel = nnz(shapeMapIx);
+
+ if calcOptions.DAOBeam
+ % indices
+ vectorIxMat_LI = repmat(vectorIx_LI',1,numBix);
+ vectorIxMat_LF = repmat(vectorIx_LF',1,numBix);
+ vectorIxMat_RI = repmat(vectorIx_RI',1,numBix);
+ vectorIxMat_RF = repmat(vectorIx_RF',1,numBix);
+
+ % wrt weight
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = shapeMap(shapeMapIx)./jacobiScale;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = DAOindex;
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt initial left
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = dUl_dLI(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_LI(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt final left
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = dUl_dLF(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_LF(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt initial right
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = -dCr_dRI(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_RI(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt final right
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = -dCr_dRF(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_RF(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ else
+ % indices
+ vectorIxMat_LF_last = repmat(vectorIx_LF_last',1,numBix);
+ vectorIxMat_LI_next = repmat(vectorIx_LI_next',1,numBix);
+ vectorIxMat_RF_last = repmat(vectorIx_RF_last',1,numBix);
+ vectorIxMat_RI_next = repmat(vectorIx_RI_next',1,numBix);
+
+ % wrt last weight
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = fracFromLastOpt*(time./time_last)*shapeMap(shapeMapIx)./jacobiScale_last;
+ %bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = (updatedInfo.beam(i).doseAngleBordersDiff*fracFromLastOpt*updatedInfo.beam(apertureInfo.beam(i).lastOptIndex).gantryRot ...
+ %/(updatedInfo.beam(apertureInfo.beam(i).lastOptIndex).doseAngleBordersDiff*updatedInfo.beam(i).gantryRot))*updatedInfo.beam(i).shape(j).shapeMap(shapeMapIx) ...
+ %./ apertureInfo.beam(apertureInfo.beam(i).lastOptIndex).shape(1).jacobiScale;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = DAOindex_last;
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt next weight
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = (1-fracFromLastOpt)*(time./time_next)*shapeMap(shapeMapIx)./jacobiScale_next;
+ %bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = (updatedInfo.beam(i).doseAngleBordersDiff*(1-fracFromLastOpt)*updatedInfo.beam(apertureInfo.beam(i).nextOptIndex).gantryRot ...
+ %/(updatedInfo.beam(apertureInfo.beam(i).nextOptIndex).doseAngleBordersDiff*updatedInfo.beam(i).gantryRot))*updatedInfo.beam(i).shape(j).shapeMap(shapeMapIx) ...
+ %./ apertureInfo.beam(apertureInfo.beam(i).nextOptIndex).shape(1).jacobiScale;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = DAOindex_next;
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+
+ % updatedInfo.beam(i).shape(j).leftLeafPos_I = fracFromLastOptI.*leftLeafPos_F_last+fracFromNextOptI.*leftLeafPos_I_next;
+ %updatedInfo.beam(i).shape(j).rightLeafPos_I = fracFromLastOptI.*rightLeafPos_F_last+fracFromNextOptI.*rightLeafPos_I_next;
+
+ % updatedInfo.beam(i).shape(j).leftLeafPos_F = fracFromLastOptF.*leftLeafPos_F_last+fracFromNextOptF.*leftLeafPos_I_next;
+ % updatedInfo.beam(i).shape(j).rightLeafPos_F = fracFromLastOptF.*rightLeafPos_F_last+fracFromNextOptF.*rightLeafPos_I_next;
+
+ % wrt initial left (optimization vector)
+ % initial (interpolated arc)
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = fracFromLastOptI(shapeMapIx).*dUl_dLI(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_LF_last(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+ % final (interpolated arc)
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = fracFromLastOptF(shapeMapIx).*dUl_dLF(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_LF_last(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt final left (optimization vector)
+ % initial (interpolated arc)
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = fracFromNextOptI(shapeMapIx).*dUl_dLI(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_LI_next(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+ % final (interpolated arc)
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = fracFromNextOptF(shapeMapIx).*dUl_dLF(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_LI_next(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt initial right (optimization vector)
+ % initial (interpolated arc)
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = -fracFromLastOptI(shapeMapIx).*dCr_dRI(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_RF_last(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+ % final (interpolated arc)
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = -fracFromLastOptF(shapeMapIx).*dCr_dRF(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_RF_last(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt final right (optimization vector)
+ % initial (interpolated arc)
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = -fracFromNextOptI(shapeMapIx).*dCr_dRI(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_RI_next(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+ % final (interpolated arc)
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = -fracFromNextOptF(shapeMapIx).*dCr_dRF(shapeMapIx).*weight;
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = vectorIxMat_RI_next(shapeMapIx);
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt last time
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = (doseAngleBordersDiff.*timeFacCurr_last) ...
+ .*(-fracFromLastDAO.*timeFracFromNextDAO.*(weight_last./doseAngleBordersDiff_next).*(time_next./time_last.^2) ...
+ +(1-fracFromLastDAO).*timeFracFromLastDAO.*(weight_next./doseAngleBordersDiff_last).*(1./time_next)) ...
+ * shapeMap(shapeMapIx);
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = tIx_last;
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+
+ % wrt next time
+ bixelJApVec_vec(bixelJApVec_offset+(1:numSaveBixel)) = (doseAngleBordersDiff.*timeFacCurr_next) ...
+ .*(fracFromLastDAO.*timeFracFromNextDAO.*(weight_last./doseAngleBordersDiff_next).*(1./time_last) ...
+ -(1-fracFromLastDAO).*timeFracFromLastDAO.*(weight_next./doseAngleBordersDiff_last).*(time_last./time_next.^2)) ...
+ * shapeMap(shapeMapIx);
+ bixelJApVec_i(bixelJApVec_offset+(1:numSaveBixel)) = tIx_next;
+ bixelJApVec_j(bixelJApVec_offset+(1:numSaveBixel)) = bixelIndMap(shapeMapIx);
+ bixelJApVec_offset = bixelJApVec_offset+numSaveBixel;
+ end
+
+end
+
+% update counters
+counters.bixelJApVec_offset = bixelJApVec_offset;
+
+end
\ No newline at end of file
diff --git a/matRad/optimization/matRad_daoVec2ApertureInfo_VMATrecalcDynamic.m b/matRad/optimization/matRad_daoVec2ApertureInfo_VMATrecalcDynamic.m
new file mode 100644
index 000000000..5d5346c08
--- /dev/null
+++ b/matRad/optimization/matRad_daoVec2ApertureInfo_VMATrecalcDynamic.m
@@ -0,0 +1,211 @@
+function updatedInfo = matRad_daoVec2ApertureInfo_VMATrecalcDynamic(apertureInfo,apertureInfoVect)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad function to translate the vector representation of the aperture
+% shape and weight into an aperture info struct. At the same time, the
+% updated bixel weight vector w is computed and a vector listing the
+% correspondence between leaf tips and bixel indices for gradient
+% calculation
+%
+% call
+% updatedInfo = matRad_daoVec2ApertureInfo(apertureInfo,apertureInfoVect)
+%
+% input
+% apertureInfo: aperture shape info struct
+% apertureInfoVect: aperture weights and shapes parameterized as vector
+% touchingFlag: if this is one, clean up instances of leaf touching,
+% otherwise, do not
+%
+% output
+% updatedInfo: updated aperture shape info struct according to apertureInfoVect
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% function to update the apertureInfo struct after the each iteraton of the
+% optimization
+
+% initializing variables
+updatedInfo = apertureInfo;
+
+updatedInfo.apertureVector = apertureInfoVect;
+
+% options for bixel and Jacobian calculation
+mlcOptions.bixelWidth = apertureInfo.bixelWidth;
+calcOptions.continuousAperture = updatedInfo.propVMAT.continuousAperture;
+vectorIndices.totalNumOfShapes = apertureInfo.totalNumOfShapes;
+
+w = cell(apertureInfo.numPhases,1);
+w(:) = {zeros(apertureInfo.totalNumOfBixels,1)};
+
+if updatedInfo.runVMAT && ~all([updatedInfo.propVMAT.beam.DAOBeam])
+ j = 1;
+ for i = 1:numel(updatedInfo.beam)
+ if updatedInfo.propVMAT.beam(i).DAOBeam
+ % update the shape weight
+ % rescale the weight from the vector using the previous
+ % iteration scaling factor
+ updatedInfo.beam(i).shape(j).weight = apertureInfoVect(updatedInfo.beam(i).shape(j).weightOffset)./updatedInfo.beam(i).shape(j).jacobiScale;
+
+ updatedInfo.beam(i).shape(j).MU = updatedInfo.beam(i).shape(j).weight*updatedInfo.weightToMU;
+ updatedInfo.beam(i).time = apertureInfoVect((updatedInfo.totalNumOfShapes+updatedInfo.totalNumOfLeafPairs*2)+updatedInfo.propVMAT.beam(i).DAOIndex)*updatedInfo.propVMAT.beam(i).timeFacCurr;
+ updatedInfo.beam(i).gantryRot = updatedInfo.propVMAT.beam(i).doseAngleBordersDiff/updatedInfo.beam(i).time;
+ updatedInfo.beam(i).shape(j).MURate = updatedInfo.beam(i).shape(j).MU./updatedInfo.beam(i).time;
+ end
+ end
+end
+
+bixelJApVec_vec = cell(apertureInfo.numPhases,1);
+
+% dummy variables
+bixelJApVec_i = 0;
+bixelJApVec_j = 0;
+bixelJApVec_offset = 0;
+counters.bixelJApVec_offset = bixelJApVec_offset;
+
+% Interpolate segment between adjacent optimized gantry angles.
+% Include in updatedInfo, but NOT the vector (since these are not
+% optimized by DAO). Also update bixel weights to include these.
+
+
+%% update the shapeMaps
+% here the new colimator positions are used to create new shapeMaps that
+% now include decimal values instead of binary
+
+calcOptions.saveJacobian = false;
+
+% loop over all beams
+for i = 1:numel(updatedInfo.beam)
+
+ %posOfRightCornerPixel = apertureInfo.beam(i).posOfCornerBixel(1) + (size(apertureInfo.beam(i).bixelIndMap,2)-1)*apertureInfo.bixelWidth;
+
+ % pre compute left and right bixel edges
+ edges_l = updatedInfo.beam(i).posOfCornerBixel(1)...
+ + ([1:size(apertureInfo.beam(i).bixelIndMap,2)]-1-1/2)*updatedInfo.bixelWidth;
+ edges_r = updatedInfo.beam(i).posOfCornerBixel(1)...
+ + ([1:size(apertureInfo.beam(i).bixelIndMap,2)]-1+1/2)*updatedInfo.bixelWidth;
+
+ % get dimensions of 2d matrices that store shape/bixel information
+ n = apertureInfo.beam(i).numOfActiveLeafPairs;
+
+ %weightFactor_I = updatedInfo.propVMAT.beam(i).doseAngleBorderCentreDiff(1)./updatedInfo.propVMAT.beam(i).doseAngleBordersDiff;
+ %weightFactor_F = updatedInfo.propVMAT.beam(i).doseAngleBorderCentreDiff(2)./updatedInfo.propVMAT.beam(i).doseAngleBordersDiff;
+
+ % we are necessarily doing VMAT
+ numOfShapes = 1;
+
+ mlcOptions.lim_l = apertureInfo.beam(i).lim_l;
+ mlcOptions.lim_r = apertureInfo.beam(i).lim_r;
+ mlcOptions.edges_l = edges_l;
+ mlcOptions.edges_r = edges_r;
+ mlcOptions.centres = (edges_l+edges_r)/2;
+ mlcOptions.widths = edges_r-edges_l;
+ mlcOptions.n = n;
+ mlcOptions.numBix = size(apertureInfo.beam(i).bixelIndMap,2);
+ mlcOptions.bixelIndMap = apertureInfo.beam(i).bixelIndMap;
+ calcOptions.DAOBeam = updatedInfo.propVMAT.beam(i).DAOBeam;
+
+ % loop over all shapes
+ for j = 1:numOfShapes
+
+ % shapeMap
+ shapeMap_I = zeros(size(updatedInfo.beam(i).bixelIndMap));
+ shapeMap_F = zeros(size(updatedInfo.beam(i).bixelIndMap));
+ % sumGradSq
+ sumGradSq = 0;
+
+ % no need to update weights or anything from the vector, just
+ % extract the weights and leaf positions from the apertureInfo
+
+ weight = updatedInfo.beam(i).shape(j).weight;
+ if isfield(updatedInfo.beam(i).shape(j),'weight_I')
+ weight_I = updatedInfo.beam(i).shape(j).weight_I;
+ weight_F = updatedInfo.beam(i).shape(j).weight_F;
+ else
+ %only happens at original angular resolution
+ weight_I = weight.*updatedInfo.beam(i).doseAngleBorderCentreDiff(1)./updatedInfo.beam(i).doseAngleBordersDiff;
+ weight_F = weight.*updatedInfo.beam(i).doseAngleBorderCentreDiff(2)./updatedInfo.beam(i).doseAngleBordersDiff;
+ end
+
+ if weight_I+weight_F ~= weight
+ %sometimes the sum is different than one by ~10^-16
+ %(rounding error in the division)
+ weight_F = weight-weight_I;
+ end
+
+ %% enter in variables and options
+
+ %%%%%%%%%%%%%%%%
+ %do initial and final arc separately, more accurate
+ %calculation
+
+ %INITIAL
+ variables.weight_I = weight_I;
+ variables.weight_F = weight_I;
+ variables.weightFactor_I = 1/2;
+ variables.weightFactor_F = 1/2;
+
+ if updatedInfo.propVMAT.continuousAperture
+ variables.leftLeafPos_I = updatedInfo.beam(i).shape(j).leftLeafPos_I;
+ variables.leftLeafPos_F = updatedInfo.beam(i).shape(j).leftLeafPos;
+ variables.rightLeafPos_I = updatedInfo.beam(i).shape(j).rightLeafPos_I;
+ variables.rightLeafPos_F = updatedInfo.beam(i).shape(j).rightLeafPos;
+ else
+ variables.leftLeafPos_I = updatedInfo.beam(i).shape(j).leftLeafPos;
+ variables.leftLeafPos_F = updatedInfo.beam(i).shape(j).leftLeafPos;
+ variables.rightLeafPos_I = updatedInfo.beam(i).shape(j).rightLeafPos;
+ variables.rightLeafPos_F = updatedInfo.beam(i).shape(j).rightLeafPos;
+ end
+
+ % calculate bixel weight and derivative in function
+ [w,~,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMap_I,counters] = ...
+ matRad_bixWeightAndGrad(calcOptions,mlcOptions,variables,vectorIndices,counters,w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMap_I);
+
+ %FINAL
+ variables.weight_I = weight_F;
+ variables.weight_F = weight_F;
+ variables.weightFactor_I = 1/2;
+ variables.weightFactor_F = 1/2;
+
+ if updatedInfo.propVMAT.continuousAperture
+ variables.leftLeafPos_I = updatedInfo.beam(i).shape(j).leftLeafPos;
+ variables.leftLeafPos_F = updatedInfo.beam(i).shape(j).leftLeafPos_F;
+ variables.rightLeafPos_I = updatedInfo.beam(i).shape(j).rightLeafPos;
+ variables.rightLeafPos_F = updatedInfo.beam(i).shape(j).rightLeafPos_F;
+ else
+ variables.leftLeafPos_I = updatedInfo.beam(i).shape(j).leftLeafPos;
+ variables.leftLeafPos_F = updatedInfo.beam(i).shape(j).leftLeafPos;
+ variables.rightLeafPos_I = updatedInfo.beam(i).shape(j).rightLeafPos;
+ variables.rightLeafPos_F = updatedInfo.beam(i).shape(j).rightLeafPos;
+ end
+
+ % calculate bixel weight and derivative in function
+ [w,~,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMap_F,counters] = ...
+ matRad_bixWeightAndGrad(calcOptions,mlcOptions,variables,vectorIndices,counters,w,bixelJApVec_vec,bixelJApVec_i,bixelJApVec_j,sumGradSq,shapeMap_F);
+
+ % save the tempMap
+ shapeMap = shapeMap_I+shapeMap_F;
+ updatedInfo.beam(i).shape(j).shapeMap = shapeMap;
+ end
+
+end
+
+% save bixelWeight, apertureVector, and Jacobian between the two
+updatedInfo.bixelWeights = w;
+updatedInfo.apertureVector = apertureInfoVect;
+
+end
\ No newline at end of file
diff --git a/matRad/optimization/projections/matRad_DoseProjection.m b/matRad/optimization/projections/matRad_DoseProjection.m
index e3001973c..f3cf28c7a 100644
--- a/matRad/optimization/projections/matRad_DoseProjection.m
+++ b/matRad/optimization/projections/matRad_DoseProjection.m
@@ -22,8 +22,14 @@
methods
function d = computeSingleScenario(~,dij,scen,w)
+ if ~isfield(dij,'scaleFactor')
+ factor = 1;
+ else
+ factor = dij.scaleFactor;
+ end
+
if ~isempty(dij.physicalDose{scen})
- d = dij.physicalDose{scen}*w;
+ d = dij.physicalDose{scen} * (factor * w);
else
d = [];
matRad_cfg = MatRad_Config.instance();
@@ -45,8 +51,14 @@
end
function wGrad = projectSingleScenarioGradient(~,dij,doseGrad,scen,~)
+ if ~isfield(dij,'scaleFactor')
+ factor = 1;
+ else
+ factor = dij.scaleFactor;
+ end
+
if ~isempty(dij.physicalDose{scen})
- wGrad = (doseGrad{scen}' * dij.physicalDose{scen})';
+ wGrad = factor * (doseGrad{scen}' * dij.physicalDose{scen})';
else
wGrad = [];
matRad_cfg = MatRad_Config.instance();
diff --git a/matRad/sequencing/matRad_arcSequencing.m b/matRad/sequencing/matRad_arcSequencing.m
new file mode 100644
index 000000000..3616a108c
--- /dev/null
+++ b/matRad/sequencing/matRad_arcSequencing.m
@@ -0,0 +1,121 @@
+function beam = matRad_arcSequencing(beam,stf,pln,weightToMU)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% The sequencing algorithm generates an a priori unkown number of apertures.
+% We only want to keep a certain number of them (numToKeep). These will be
+% the ones with the highest intensity-area product.
+%
+%
+% call
+% beam =
+% matRad_arcSequencing(beam)
+%
+% input
+% beam: beam struct with shapes and weights only at the
+% initGantyAngles
+%
+%
+% output
+% beam: beam struct with shapes and weights distributed to
+% the correct optGantryAngles
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+machine = matRad_loadMachine(pln);
+
+numOfBeams = numel(stf);
+
+leafDir = 1;
+
+for i = 1:numOfBeams
+
+ if stf(i).propVMAT.FMOBeam
+
+ %Spread apertures to each child angle
+ %according to the trajectory (mean leaf position). Assume that
+ %shapes are already order in increased (left to right) position
+ leafDir = -1*leafDir;
+
+ childrenIndex = stf(i).propVMAT.beamChildrenIndex;
+ if leafDir == -1
+ % reverse order of shapes
+ childrenIndex = flipud(childrenIndex);
+ end
+
+ count = 1;
+ numOfShapes = beam(i).numOfShapes;
+
+ for shape = 1:numOfShapes
+ childIndex = childrenIndex(count);
+ beam(childIndex).leafDir = leafDir;
+
+ if childIndex == i
+ % do not overwrite information, since we will need it for
+ % the remaining beams (DAO, not init)
+ beam(childIndex).tempNumOfShapes = 1;
+ beam(childIndex).tempShapes = beam(i).shapes(:,:,shape);
+ beam(childIndex).tempShapesWeight = beam(i).shapesWeight(shape);
+ beam(childIndex).fluence = beam(childIndex).tempShapes;
+ beam(childIndex).sum = beam(childIndex).tempShapesWeight*beam(childIndex).tempShapes;
+ else
+ % don't worry about overwriting
+ beam(childIndex).numOfShapes = 1;
+ beam(childIndex).shapes = beam(i).shapes(:,:,shape);
+ beam(childIndex).shapesWeight = beam(i).shapesWeight(shape);
+ beam(childIndex).fluence = beam(childIndex).shapes;
+ beam(childIndex).sum = beam(childIndex).shapesWeight*beam(childIndex).shapes;
+ end
+
+ count = count+1;
+ end
+ else
+ % if beam isn't an FMO beam, then there is no info in the beam
+ % struct
+ continue
+ end
+end
+
+for i = 1:numOfBeams
+ % now go through and calculate gantry rotation speed, MU rate, etc.
+
+ if stf(i).propVMAT.FMOBeam
+ beam(i).numOfShapes = beam(i).tempNumOfShapes;
+ beam(i).shapes = beam(i).tempShapes;
+ beam(i).shapesWeight = beam(i).tempShapesWeight;
+
+ beam(i).tempNumOfShapes = [];
+ beam(i).tempShapes = [];
+ beam(i).tempShapesWeight = [];
+
+ for j = 1:stf(i).propVMAT.numOfBeamSubChildren
+ %Prevents matRad_sequencing2ApertureInfo from attempting to
+ %convert shape to aperturevec for subchildren
+ beam(stf(i).propVMAT.beamSubChildrenIndex(j)).numOfShapes = 0;
+ end
+ end
+
+ if stf(i).propVMAT.DAOBeam
+ beam(i).gantryRot = machine.constraints.gantryRotationSpeed(2); %gantry rotation rate until next opt angle
+ beam(i).MURate = weightToMU.*beam(i).shapesWeight.*beam(i).gantryRot./stf(i).propVMAT.DAOAngleBordersDiff; %dose rate until next opt angle
+ %Rescale weight to represent only this control point; weight will be shared
+ %with the interpolared control points in matRad_daoVec2ApertureInfo
+ beam(i).shapesWeight = beam(i).shapesWeight.*stf(i).propVMAT.timeFacCurr;
+ end
+end
+
+beam = rmfield(beam,{'tempShapes','tempShapesWeight','tempNumOfShapes'});
+
+
diff --git a/matRad/sequencing/matRad_discardApertures.m b/matRad/sequencing/matRad_discardApertures.m
new file mode 100644
index 000000000..07dcf2f90
--- /dev/null
+++ b/matRad/sequencing/matRad_discardApertures.m
@@ -0,0 +1,108 @@
+function newBeam = matRad_discardApertures(beam,numToKeep)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% The sequencing algorithm generates an a priori unkown number of aperture.
+% We only want to keep a certain number of them (numToKeep). These will be
+% the ones with the highest intensity-area product.
+%
+%
+% call
+% beam =
+% matRad_discardApertures(beam,numToKeep)
+%
+% input
+% beam: beam struct containing original shapes and
+% intensities
+%
+% numToKeep: number of apertures to keep
+%
+% output
+% beam: beam struct containing shapes and re-scaled
+% intensities of the apertures we are keeping
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+newBeam = beam;
+newBeam.shapes = zeros(size(newBeam.shapes,1),size(newBeam.shapes,2),numToKeep);
+newBeam.shapesWeight = zeros(numToKeep,1);
+
+%Find the numToKeep apertures having the highest dose-area product
+numToKeep = min(numToKeep,beam.numOfShapes);
+
+DAP = zeros(beam.numOfShapes,1);
+comPos = zeros(beam.numOfShapes,1);
+
+for shape = 1:beam.numOfShapes
+ DAP(shape) = nnz(beam.shapes(:,:,shape)).*beam.shapesWeight(shape);
+ x = repmat(1:size(beam.shapes(:,:,shape),2),size(beam.shapes(:,:,shape),1),1);
+ comPosRow = sum(beam.shapes(:,:,shape).*x,2)./sum(beam.shapes(:,:,shape),2);
+ comPos(shape) = mean(comPosRow(~isnan(comPosRow),1));
+end
+
+%{
+% This code will sort the aperture shapes in increasing order of centre of mass.
+However, some algorithms (in particular, Siochi) already sort shapes in
+increasing (left to right) leaf position. So this is not necessary (and
+will in fact some times change the order of the apertures generated by
+Siochi).
+
+% sort comPos into ascending order
+[comPosSorted,comPosSortInd] = sort(beam.comPos);
+
+if any(comPosSorted ~= beam.comPos)
+ tempShapes = zeros(size(beam.shapes));
+ tempShapesWeight = zeros(size(beam.shapesWeight));
+ tempDAP = zeros(size(beam.DAP));
+
+ for shape = 1:beam.numOfShapes
+ ind = comPosSortInd(shape);
+
+ tempShapes(:,:,shape) = beam.shapes(:,:,ind);
+ tempShapesWeight(shape) = beam.shapesWeight(ind);
+ tempDAP(shape) = beam.DAP(ind);
+ end
+
+ % now the shapes should be sorted in increasing center of mass position
+ beam.shapes = tempShapes;
+ beam.shapesWeight = tempShapesWeight;
+ beam.DAP = tempDAP;
+ beam.comPos = comPosSorted;
+end
+%}
+
+[~,comPosToDAPSort] = sort(DAP,'descend');
+
+totDAP_all = sum(DAP(:));
+totDAP_keep = sum(DAP(comPosToDAPSort(1:numToKeep)));
+
+segmentKeep = 1;
+
+%Keep only those numToKeep apertures with the highest DAP
+%Preserve the shapes of the apertures, but scale the weights so
+%that the total DAP is kept
+for shape = 1:beam.numOfShapes
+ if comPosToDAPSort(shape) <= numToKeep
+ newBeam.shapes(:,:,segmentKeep) = beam.shapes(:,:,shape);
+ tempNewDAP = totDAP_all*DAP(shape)/totDAP_keep;
+ newBeam.shapesWeight(segmentKeep) = tempNewDAP/(nnz(newBeam.shapes(:,:,segmentKeep))); %sequencing.beam.shapesWeight(sequencing.beam.segmentSortedDAP(segment))
+
+ segmentKeep = segmentKeep+1;
+ else
+ continue
+ end
+end
+
+newBeam.numOfShapes = numToKeep;
diff --git a/matRad/sequencing/matRad_engelLeafSequencing.m b/matRad/sequencing/matRad_engelLeafSequencing.m
index a1d944374..9fe4b35e2 100644
--- a/matRad/sequencing/matRad_engelLeafSequencing.m
+++ b/matRad/sequencing/matRad_engelLeafSequencing.m
@@ -1,17 +1,17 @@
-function resultGUI = matRad_engelLeafSequencing(resultGUI,stf,dij,numOfLevels,visBool)
-% multileaf collimator leaf sequencing algorithm
-% for intensity modulated beams with multiple static segments accroding
-% to Engel et al. 2005 Discrete Applied Mathematics
+function resultGUI = matRad_engelLeafSequencing(resultGUI,stf,dij,pln,visBool)
+% multileaf collimator leaf sequencing algorithm for intensity modulated
+% beams with multiple static segments accroding to Engel et al. 2005
+% Discrete Applied Mathematics
%
% call
-% resultGUI = matRad_engelLeafSequencing(resultGUI,stf,dij,numOfLevels,visBool)
+% resultSequencing = matRad_engelSequencing(w,stf,pln,pln,visBool)
%
% input
% resultGUI: resultGUI struct to which the output data will be added, if
% this field is empty resultGUI struct will be created
% stf: matRad steering information struct
% dij: matRad's dij matrix
-% numOfLevels: number of stratification levels
+% pln: pln struct
% visBool: toggle on/off visualization (optional)
%
% output
@@ -34,6 +34,8 @@
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+
% if visBool not set toogle off visualization
if nargin < 5
visBool = 0;
@@ -50,6 +52,10 @@
seqFig = figure('position',[xpos,ypos,sz(2),sz(1)]);
end
+if ~isfield(pln,'propSeq') || ~isfield(pln.propSeq,'numLevels')
+ pln.propSeq.numLevels = matRad_cfg.defaults.propSeq.numLevels;
+end
+
offset = 0;
for i = 1:numOfBeams
@@ -92,7 +98,7 @@
% Stratification
calFac = max(fluenceMx(:));
- D_k = round(fluenceMx/calFac*numOfLevels);
+ D_k = round(fluenceMx/calFac*pln.propSeq.numLevels);
% Save the stratification in the initial intensity matrix D_0.
D_0 = D_k;
@@ -108,7 +114,7 @@
seqSubPlots(1) = subplot(2,2,1,'parent',seqFig);
imagesc(D_k,'parent',seqSubPlots(1));
- set(seqSubPlots(1),'CLim',[0 numOfLevels],'YDir','normal');
+ set(seqSubPlots(1),'CLim',[0 pln.propSeq.numLevels],'YDir','normal');
title(seqSubPlots(1),['Beam # ' num2str(i) ': max(D_0) = ' num2str(max(D_0(:))) ' - ' num2str(numel(unique(D_0))) ' intensity levels']);
xlabel(seqSubPlots(1),'x - direction parallel to leaf motion ')
ylabel(seqSubPlots(1),'z - direction perpendicular to leaf motion ')
@@ -136,7 +142,7 @@
if visBool
seqSubPlots(2) = subplot(2,2,2,'parent',seqFig);
imagesc(D_k,'parent',seqSubPlots(2));
- set(seqSubPlots(2),'CLim',[0 numOfLevels],'YDir','normal');
+ set(seqSubPlots(2),'CLim',[0 pln.propSeq.numLevels],'YDir','normal');
title(seqSubPlots(2),['k = ' num2str(k)]);
colorbar
drawnow
@@ -355,11 +361,11 @@
sequencing.beam(i).numOfShapes = k;
sequencing.beam(i).shapes = shapes(:,:,1:k);
- sequencing.beam(i).shapesWeight = shapesWeight(1:k)/numOfLevels*calFac;
+ sequencing.beam(i).shapesWeight = shapesWeight(1:k)/pln.propSeq.numLevels*calFac;
sequencing.beam(i).bixelIx = 1+offset:numOfRaysPerBeam+offset;
sequencing.beam(i).fluence = D_0;
- sequencing.w(1+offset:numOfRaysPerBeam+offset,1) = D_0(indInFluenceMx)/numOfLevels*calFac;
+ sequencing.w(1+offset:numOfRaysPerBeam+offset,1) = D_0(indInFluenceMx)/pln.propSeq.numLevels*calFac;
offset = offset + numOfRaysPerBeam;
@@ -369,7 +375,7 @@
resultGUI.wSequenced = sequencing.w;
resultGUI.sequencing = sequencing;
-resultGUI.apertureInfo = matRad_sequencing2ApertureInfo(sequencing,stf);
+resultGUI.apertureInfo = matRad_sequencing2ApertureInfo(sequencing,stf,pln);
doseSequencedDoseGrid = reshape(dij.physicalDose{1} * sequencing.w,dij.doseGrid.dimensions);
% interpolate to ct grid for visualiation & analysis
diff --git a/matRad/sequencing/matRad_leafTouching.m b/matRad/sequencing/matRad_leafTouching.m
new file mode 100644
index 000000000..a55f61a0f
--- /dev/null
+++ b/matRad/sequencing/matRad_leafTouching.m
@@ -0,0 +1,178 @@
+function apertureInfo = matRad_leafTouching(apertureInfo)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad function to improve instances of leaf touching by moving leaves
+% from the centre to sweep with the non-touching leaves.
+%
+% Currently only works with VMAT, add option to work with IMRT (not as
+% crucial)
+%
+% call
+% apertureInfo = matRad_leafTouching(apertureInfo)
+%
+% input
+% apertureInfo: matRad aperture weight and shape info struct
+%
+% output
+% apertureInfo: matRad aperture weight and shape info 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+%initialize
+dimZ = apertureInfo.beam(1).numOfActiveLeafPairs;
+numBeams = nnz([apertureInfo.propVMAT.beam.DAOBeam]);
+if ~isfield(apertureInfo.beam(1).shape(1),'leftLeafPos_I')
+ % Each non-interpolated beam should have 1 left/right leaf position
+ leftLeafPoss = nan(dimZ,numBeams);
+ rightLeafPoss = nan(dimZ,numBeams);
+ gantryAngles = zeros(1,numBeams);
+else
+ % Each non-interpolated beam should have 2 left/right leaf positions
+ leftLeafPoss = nan(dimZ,2*numBeams);
+ rightLeafPoss = nan(dimZ,2*numBeams);
+ gantryAngles = zeros(1,2*numBeams);
+end
+initBorderGantryAngles = unique([apertureInfo.propVMAT.beam.FMOAngleBorders]);
+initBorderLeftLeafPoss = nan(dimZ,numel(initBorderGantryAngles));
+
+l = 1;
+m = 1;
+%collect all leaf positions
+for k = 1:numel(apertureInfo.beam)
+ if (k ~= 1 && apertureInfo.beam(k).gantryAngle == apertureInfo.beam(k-1).gantryAngle) || ~apertureInfo.propVMAT.beam(k).DAOBeam
+ continue
+ end
+
+ if ~isfield(apertureInfo.beam(1).shape(1),'leftLeafPos_I')
+ leftLeafPoss(:,l) = apertureInfo.beam(k).shape(1).leftLeafPos;
+ rightLeafPoss(:,l) = apertureInfo.beam(k).shape(1).rightLeafPos;
+ gantryAngles(l) = apertureInfo.beam(k).gantryAngle;
+
+ l = l+1;
+ else
+ leftLeafPoss(:,l) = apertureInfo.beam(k).shape(1).leftLeafPos_I;
+ rightLeafPoss(:,l) = apertureInfo.beam(k).shape(1).rightLeafPos_I;
+ gantryAngles(l) = apertureInfo.beam(k).doseAngleBorders(1);
+
+ l = l+1;
+
+ leftLeafPoss(:,l) = apertureInfo.beam(k).shape(1).leftLeafPos_F;
+ rightLeafPoss(:,l) = apertureInfo.beam(k).shape(1).rightLeafPos_F;
+ gantryAngles(l) = apertureInfo.beam(k).doseAngleBorders(2);
+
+ l = l+1;
+ end
+
+ %Only important when cleaning up instances of opposing
+ %leaves touching.
+ if apertureInfo.propVMAT.beam(k).FMOBeam
+ if apertureInfo.propVMAT.beam(k).leafDir == 1
+ %This means that the current arc sector is moving
+ %in the normal direction (L-R).
+ initBorderLeftLeafPoss(:,m) = apertureInfo.beam(k).lim_l;
+
+ elseif apertureInfo.propVMAT.beam(k).leafDir == -1
+ %This means that the current arc sector is moving
+ %in the reverse direction (R-L).
+ initBorderLeftLeafPoss(:,m) = apertureInfo.beam(k).lim_r;
+ end
+ m = m+1;
+
+ %end of last sector
+ if m == numel(initBorderGantryAngles)
+ %This gives ending angle of the current sector.
+ if apertureInfo.propVMAT.beam(k).leafDir == 1
+ %This means that the current arc sector is moving
+ %in the normal direction (L-R), so the next arc
+ %sector is moving opposite
+ initBorderLeftLeafPoss(:,m) = apertureInfo.beam(k).lim_r;
+ elseif apertureInfo.propVMAT.beam(k).leafDir == -1
+ %This means that the current arc sector is moving
+ %in the reverse direction (R-L), so the next
+ %arc sector is moving opposite
+ initBorderLeftLeafPoss(:,m) = apertureInfo.beam(k).lim_l;
+ end
+ end
+ end
+end
+
+[gantryAngles,ind] = unique(gantryAngles);
+leftLeafPoss = leftLeafPoss(:,ind);
+rightLeafPoss = rightLeafPoss(:,ind);
+
+%Any time leaf pairs are touching, they are set to
+%be in the middle of the field. Instead, move them
+%so that they are still touching, but that they
+%follow the motion of the MLCs across the field.
+for row = 1:dimZ
+
+ touchingInd = find(leftLeafPoss(row,:) == rightLeafPoss(row,:));
+
+ if ~exist('leftLeafPossAug','var')
+ %leftLeafPossAug = [reshape(mean([leftLeafPoss(:) rightLeafPoss(:)],2),size(leftLeafPoss)),borderLeftLeafPoss];
+ leftLeafPossAugTemp = reshape(mean([leftLeafPoss(:) rightLeafPoss(:)],2),size(leftLeafPoss));
+
+ numRep = 0;
+ repInd = nan(size(gantryAngles));
+ for j = 1:numel(gantryAngles)
+ if any(gantryAngles(j) == initBorderGantryAngles)
+ %replace leaf positions with the ones at
+ %the borders (eliminates repetitions)
+ numRep = numRep+1;
+ %these are the gantry angles that are
+ %repeated
+ repInd(numRep) = j;
+
+ delInd = find(gantryAngles(j) == initBorderGantryAngles);
+ leftLeafPossAugTemp(:,j) = initBorderLeftLeafPoss(:,delInd);
+ initBorderLeftLeafPoss(:,delInd) = [];
+ initBorderGantryAngles(delInd) = [];
+ end
+ end
+ repInd(isnan(repInd)) = [];
+ leftLeafPossAug = [leftLeafPossAugTemp,initBorderLeftLeafPoss];
+ gantryAnglesAug = [gantryAngles,initBorderGantryAngles];
+ end
+ notTouchingInd = [setdiff(1:numBeams,touchingInd),repInd];
+ notTouchingInd = unique(notTouchingInd);
+ %make sure to include the repeated ones in the
+ %interpolation!
+
+ notTouchingIndAug = [notTouchingInd,(1+numel(gantryAngles)):(numel(gantryAngles)+numel(initBorderGantryAngles))];
+
+ leftLeafPoss(row,touchingInd) = interp1(gantryAnglesAug(notTouchingIndAug),leftLeafPossAug(row,notTouchingIndAug),gantryAngles(touchingInd))-0.5;
+ rightLeafPoss(row,touchingInd) = leftLeafPoss(row,touchingInd)+1;
+end
+
+
+%finally, set new leaf positions
+for i = 1:numel(apertureInfo.beam)
+ apertureInfo.beam(i).shape(1).leftLeafPos = max((interp1(gantryAngles',leftLeafPoss',apertureInfo.beam(i).gantryAngle))',apertureInfo.beam(i).lim_l);
+ apertureInfo.beam(i).shape(1).rightLeafPos = min((interp1(gantryAngles',rightLeafPoss',apertureInfo.beam(i).gantryAngle))',apertureInfo.beam(i).lim_r);
+
+ apertureInfo.beam(i).shape(1).leftLeafPos_I = max((interp1(gantryAngles',leftLeafPoss',apertureInfo.propVMAT.beam(i).doseAngleBorders(1)))',apertureInfo.beam(i).lim_l);
+ apertureInfo.beam(i).shape(1).rightLeafPos_I = min((interp1(gantryAngles',rightLeafPoss',apertureInfo.propVMAT.beam(i).doseAngleBorders(1)))',apertureInfo.beam(i).lim_r);
+
+ apertureInfo.beam(i).shape(1).leftLeafPos_F = max((interp1(gantryAngles',leftLeafPoss',apertureInfo.propVMAT.beam(i).doseAngleBorders(2)))',apertureInfo.beam(i).lim_l);
+ apertureInfo.beam(i).shape(1).rightLeafPos_F = min((interp1(gantryAngles',rightLeafPoss',apertureInfo.propVMAT.beam(i).doseAngleBorders(2)))',apertureInfo.beam(i).lim_r);
+end
+
+
+end
+
diff --git a/matRad/sequencing/matRad_maxLeafSpeed.m b/matRad/sequencing/matRad_maxLeafSpeed.m
new file mode 100644
index 000000000..939b4bebd
--- /dev/null
+++ b/matRad/sequencing/matRad_maxLeafSpeed.m
@@ -0,0 +1,194 @@
+function apertureInfo = matRad_maxLeafSpeed(apertureInfo)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad calculation of maximum leaf speed
+%
+% call
+% apertureInfo = matRad_maxLeafSpeed(apertureInfo)
+%
+% input
+% apertureInfo: aperture info struct
+%
+% output
+% apertureInfo: aperture info struct
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+apertureInfoVec = apertureInfo.apertureVector;
+
+% values of time differences of optimized gantry angles
+timeDAOBorderAngles = apertureInfoVec(1+(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2):end);
+
+% find values of leaf speeds of optimized gantry angles
+if apertureInfo.continuousAperture
+ % Using the dynamic fluence calculation, we have the leaf positions in
+ % the vector be the leaf positions at the borders of the Dij arcs (for optimized angles only).
+ % Therefore we must also use the times between the borders of the Dij
+ % arc (for optimized angles only).
+ timeFac = [apertureInfo.propVMAT.beam.timeFac]';
+ deleteInd = timeFac == 0;
+ timeFac(deleteInd) = [];
+
+ i = [apertureInfo.propVMAT.beam.timeFacInd]';
+ i(deleteInd) = [];
+
+ j = repelem(1:apertureInfo.totalNumOfShapes,1,3);
+ j(deleteInd) = [];
+
+ timeFacMatrix = sparse(i,j,timeFac,max(i),apertureInfo.totalNumOfShapes);
+ timeBNOptAngles = timeFacMatrix*timeDAOBorderAngles;
+
+ % prep
+ leftLeafDiff = zeros(apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs,1);
+ rightLeafDiff = zeros(apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs,1);
+ tVec = zeros(apertureInfo.propVMAT.numLeafSpeedConstraint*apertureInfo.beam(1).numOfActiveLeafPairs,1);
+ maxLeafSpeed = zeros(1,max(i));
+
+ offset = 0;
+ shapeInd = 1;
+
+ for i = 1:numel(apertureInfo.beam)
+ % loop over beams
+ n = apertureInfo.beam(i).numOfActiveLeafPairs;
+
+ if ~isempty(apertureInfo.propVMAT.beam(i).leafConstMask)
+
+ % get vector indices
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ % if it's a DAO beam, use own vector offset
+ vectorIx_LI = apertureInfo.beam(i).shape(1).vectorOffset(1) + ((1:n)-1);
+ vectorIx_LF = apertureInfo.beam(i).shape(1).vectorOffset(2) + ((1:n)-1);
+ else
+ % otherwise, use vector offset of previous and next
+ % beams
+ vectorIx_LI = apertureInfo.beam(apertureInfo.propVMAT.beam(i).lastDAOIndex).shape(1).vectorOffset(2) + ((1:n)-1);
+ vectorIx_LF = apertureInfo.beam(apertureInfo.propVMAT.beam(i).nextDAOIndex).shape(1).vectorOffset(1) + ((1:n)-1);
+ end
+ vectorIx_RI = vectorIx_LI+apertureInfo.totalNumOfLeafPairs;
+ vectorIx_RF = vectorIx_LF+apertureInfo.totalNumOfLeafPairs;
+
+ % extract leaf positions, time
+ leftLeafPos_I = apertureInfoVec(vectorIx_LI);
+ rightLeafPos_I = apertureInfoVec(vectorIx_RI);
+ leftLeafPos_F = apertureInfoVec(vectorIx_LF);
+ rightLeafPos_F = apertureInfoVec(vectorIx_RF);
+ t = timeBNOptAngles(shapeInd);
+
+ % determine indices
+ indInDiffVec = offset+(1:n);
+
+ % insert differences, time
+ leftLeafDiff(indInDiffVec) = abs(leftLeafPos_F-leftLeafPos_I);
+ rightLeafDiff(indInDiffVec) = abs(rightLeafPos_F-rightLeafPos_I);
+ tVec(indInDiffVec) = t;
+
+ % get max speed
+ leftLeafSpeed = abs(leftLeafPos_F-leftLeafPos_I)./t;
+ rightLeafSpeed = abs(leftLeafPos_F-leftLeafPos_I)./t;
+ maxLeafSpeed_temp = max([leftLeafSpeed; rightLeafSpeed]);
+
+ % update max speed
+ if maxLeafSpeed_temp > maxLeafSpeed(shapeInd)
+ maxLeafSpeed(shapeInd) = maxLeafSpeed_temp;
+ end
+
+ % update offset
+ offset = offset+n;
+
+ % increment shapeInd only for beams which have transtion
+ % defined
+ shapeInd = shapeInd+1;
+ end
+ end
+else
+ % value of constraints for leaves
+ %leftLeafPos = apertureInfoVec([1:apertureInfo.totalNumOfLeafPairs]+apertureInfo.totalNumOfShapes);
+ %rightLeafPos = apertureInfoVec(1+apertureInfo.totalNumOfLeafPairs+apertureInfo.totalNumOfShapes:apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2);
+ leftLeafPos = apertureInfoVec((1:(apertureInfo.totalNumOfLeafPairs))+apertureInfo.totalNumOfShapes);
+ rightLeafPos = apertureInfoVec(1+(apertureInfo.totalNumOfLeafPairs+apertureInfo.totalNumOfShapes):(apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2));
+
+ % Using the static fluence calculation, we have the leaf positions in
+ % the vector be the leaf positions at the centre of the Dij arcs (for optimized angles only).
+ % Therefore we must use the times between the centres of the Dij arcs (for optimized angles only).
+ i = sort(repmat(1:(apertureInfo.totalNumOfShapes-1),1,2));
+ j = sort(repmat(1:apertureInfo.totalNumOfShapes,1,2));
+ j(1) = [];
+ j(end) = [];
+
+ timeFac = [apertureInfo.propVMAT.beam.timeFac]';
+ timeFac(1) = [];
+ timeFac(end) = [];
+
+ timeFacMatrix = sparse(i,j,timeFac,(apertureInfo.totalNumOfShapes-1),apertureInfo.totalNumOfShapes);
+ timeBNOptAngles = timeFacMatrix*timeDAOBorderAngles;
+
+ leftLeafSpeed = abs(diff(reshape(leftLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,[]),1,2))./repmat(timeBNOptAngles',apertureInfo.beam(1).numOfActiveLeafPairs,1);
+ rightLeafSpeed = abs(diff(reshape(rightLeafPos,apertureInfo.beam(1).numOfActiveLeafPairs,[]),1,2))./repmat(timeBNOptAngles',apertureInfo.beam(1).numOfActiveLeafPairs,1);
+
+ % values of max leaf speeds
+ leftMaxLeafSpeed = max(leftLeafSpeed,[],1);
+ rightMaxLeafSpeed = max(rightLeafSpeed,[],1);
+ maxLeafSpeed = max([leftMaxLeafSpeed; rightMaxLeafSpeed],[],1);
+end
+
+
+% enter into apertureInfo
+l = 1;
+maxMaxLeafSpeed = 0;
+for i = 1:size(apertureInfo.beam,2)
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ if apertureInfo.continuousAperture
+ % for dynamic, we take the max leaf speed to be the actual leaf
+ % speed
+ ind = apertureInfo.propVMAT.beam(i).timeFacInd(apertureInfo.propVMAT.beam(i).timeFac ~= 0);
+
+ apertureInfo.beam(i).maxLeafSpeed = max(maxLeafSpeed(ind));
+ if apertureInfo.beam(i).maxLeafSpeed >= maxMaxLeafSpeed
+ maxMaxLeafSpeed = apertureInfo.beam(i).maxLeafSpeed;
+ end
+ else
+ % for static, we take the max leaf speed to be the max leaf
+ % of two speeds, one being the speed in the first half-arc, the
+ % second being the speed in the second half-arc (these will be
+ % different in general)
+
+ if l == 1
+ apertureInfo.beam(i).maxLeafSpeed = maxLeafSpeed(l);
+ elseif l == apertureInfo.totalNumOfShapes
+ apertureInfo.beam(i).maxLeafSpeed = maxLeafSpeed(l-1);
+ else
+ %apertureInfo.beam(i).maxLeafSpeed = maxLeafSpeed(l-1)*apertureInfo.beam(i).timeFac(1)+maxLeafSpeed(l)*apertureInfo.beam(i).timeFac(2);
+ apertureInfo.beam(i).maxLeafSpeed = max(maxLeafSpeed(l-1),maxLeafSpeed(l));
+ end
+
+
+ if l < apertureInfo.totalNumOfShapes && maxLeafSpeed(l) >= maxMaxLeafSpeed
+ maxMaxLeafSpeed = maxLeafSpeed(l);
+ end
+ end
+
+
+ if l < apertureInfo.totalNumOfShapes && maxLeafSpeed(l) >= maxMaxLeafSpeed
+ maxMaxLeafSpeed = maxLeafSpeed(l);
+ end
+
+ l = l+1;
+ end
+end
+
+apertureInfo.maxLeafSpeed = maxMaxLeafSpeed;
+
diff --git a/matRad/sequencing/matRad_optDelivery.m b/matRad/sequencing/matRad_optDelivery.m
new file mode 100644
index 000000000..f8b1f79c2
--- /dev/null
+++ b/matRad/sequencing/matRad_optDelivery.m
@@ -0,0 +1,109 @@
+function result = matRad_optDelivery(result,fast)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad: optimize VMAT delivery
+%
+% call
+% matRad_optDelivery(result,fast)
+%
+% input
+% result: result struct from fluence optimization/sequencing
+% fast: 1 => fastest possible delivery
+% 0 => mutliply delivery time by 10%
+%
+% output
+% apertureInfo: aperture shape info struct
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+if nargin < 3
+ fast = 1;
+end
+
+matRad_cfg = MatRad_Config.instance();
+
+
+%speed up delivery time, when it is permitted by constraints
+%constraints to consider: doserate, leaf speed, and gantry speed
+
+%Do this after DAO
+
+apertureInfo = result.apertureInfo;
+
+%calculate max leaf speed
+apertureInfo = matRad_maxLeafSpeed(apertureInfo);
+
+doInterp = false;
+
+for i = 1:size(apertureInfo.beam,2)
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+
+ %all of these should be greater than 1, since DAO respects the
+ %constraints
+
+ %if one of them is less than 1, then a constraint is violated
+ factorMURate = apertureInfo.propVMAT.constraints.monitorUnitRate(2)/apertureInfo.beam(i).shape(1).MURate;
+ factorLeafSpeed = apertureInfo.propVMAT.constraints.leafSpeed(2)/apertureInfo.beam(i).maxLeafSpeed;
+ factorGantryRot = apertureInfo.propVMAT.constraints.gantryRotationSpeed(2)/apertureInfo.beam(i).gantryRot;
+
+ %The constraint that is limiting the speed the most is the one
+ %whose factor is closest to 1
+ factor = min([factorMURate factorLeafSpeed factorGantryRot]);
+ if ~fast
+ %if the limiting rate is already 10% lower than the limit,
+ %then do nothing (factor = 1)
+ %otherwise, scale rates so that the limiting rate is 10% lower
+ %than the limit
+ factor = min([1 factor*0.9]);
+ end
+
+ %multiply each speed by this factor
+ apertureInfo.beam(i).shape.MURate = factor*apertureInfo.beam(i).shape.MURate;
+ apertureInfo.beam(i).maxLeafSpeed = factor*apertureInfo.beam(i).maxLeafSpeed;
+ apertureInfo.beam(i).gantryRot = factor*apertureInfo.beam(i).gantryRot;
+ apertureInfo.beam(i).time = apertureInfo.beam(i).time/factor;
+
+ factorMURate = apertureInfo.propVMAT.constraints.monitorUnitRate(1)/apertureInfo.beam(i).shape(1).MURate;
+
+ if factorMURate > 1
+ apertureInfo.beam(i).shape(1).MURate = factorMURate*apertureInfo.beam(i).shape(1).MURate;
+ apertureInfo.beam(i).shape(1).weight = factorMURate*apertureInfo.beam(i).shape(1).weight;
+
+ doInterp = true;
+ end
+ end
+end
+
+% doInterp is set to true if, during the previous step, the dose rate
+% somehow was lower than the minimum dose rate. This can happen if the
+% gantry speed has to go low enough to accomodate a slowly-moving leaf. In
+% this case, the weight has to increase to bring the dose rate above the
+% minimum threshold. If this is done, the bixel weights will be different.
+if doInterp
+ matRad_cfg.dispInfo('Interpolation needs to be redone due to low dose rate.\n');
+end
+
+%recalculate vector with new times
+
+%%%LOOK PAST THIS
+[apertureInfo.apertureVector,~,~] = matRad_OptimizationProblemVMAT.matRad_daoApertureInfo2Vec(apertureInfo);
+
+%redo interpolation
+apertureInfo = matRad_OptimizationProblemVMAT.matRad_daoVec2ApertureInfo(apertureInfo,apertureInfo.apertureVector);
+
+
+result.apertureInfo = apertureInfo;
+
diff --git a/matRad/sequencing/matRad_sequencing2ApertureInfo.m b/matRad/sequencing/matRad_sequencing2ApertureInfo.m
index 867cabd43..7098a087e 100644
--- a/matRad/sequencing/matRad_sequencing2ApertureInfo.m
+++ b/matRad/sequencing/matRad_sequencing2ApertureInfo.m
@@ -1,6 +1,6 @@
-function apertureInfo = matRad_sequencing2ApertureInfo(Sequencing,stf)
-% matRad function to generate a shape info struct
-% based on the result of multileaf collimator sequencing
+function apertureInfo = matRad_sequencing2ApertureInfo(sequencing,stf,pln)
+% matRad function to generate a shape info struct based on the result of
+% multileaf collimator sequencing
%
% call
% apertureInfo = matRad_sequencing2ApertureInfo(Sequencing,stf)
@@ -13,7 +13,6 @@
% apertureInfo: matRad aperture weight and shape info struct
%
% References
-%
% -
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
@@ -28,6 +27,7 @@
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
% MLC parameters:
bixelWidth = stf(1).bixelWidth; % [mm]
numOfMLCLeafPairs = 80;
@@ -39,8 +39,27 @@
% initializing variables
bixelIndOffset = 0; % used for creation of bixel index maps
totalNumOfBixels = sum([stf(:).totalNumOfBixels]);
-totalNumOfShapes = sum([Sequencing.beam.numOfShapes]);
+totalNumOfShapes = sum([sequencing.beam.numOfShapes]);
+weightOffset = 1;
vectorOffset = totalNumOfShapes + 1; % used for bookkeeping in the vector for optimization
+bixOffset = 1; %used for gradient calculations
+interpGetsTransition = false; % boolean to determine if an interpolated beam is responsible for a leaf speed constraint check
+
+if ~isfield(pln, 'propOpt') || ~isfield(pln.propOpt,'runVMAT')
+ pln.propOpt.runVMAT = false;
+end
+
+if ~isfield(pln, 'propStf') || ~isfield(pln.propStf,'continuousAperture')
+ pln.propStf.continuousAperture = false;
+end
+
+if pln.propOpt.runVMAT
+ totalNumOfOptBixels = 0;
+ totalNumOfLeafPairs = 0;
+ apertureInfo.propVMAT.jacobT = zeros(sum([sequencing.beam.numOfShapes]),numel(sequencing.beam));
+end
+
+apertureInfo.jacobiScale = ones(totalNumOfShapes,1);
% loop over all beams
for i=1:size(stf,2)
@@ -53,12 +72,12 @@
Z = rayPos_bev(3,:)';
% create ray-map
- maxX = max(X); minX = min(X);
+ maxX = max(X); minX = min(X);
maxZ = max(Z); minZ = min(Z);
dimX = (maxX-minX)/stf(i).bixelWidth + 1;
dimZ = (maxZ-minZ)/stf(i).bixelWidth + 1;
-
+
rayMap = zeros(dimZ,dimX);
% get indices for x and z positions
@@ -67,13 +86,13 @@
% get indices in the ray-map
indInRay = zPos + (xPos-1)*dimZ;
-
+
% fill ray-map
rayMap(indInRay) = 1;
% create map of bixel indices
bixelIndMap = NaN * ones(dimZ,dimX);
- bixelIndMap(indInRay) = [1:stf(i).numOfRays] + bixelIndOffset;
+ bixelIndMap(indInRay) = (1:stf(i).numOfRays) + bixelIndOffset;
bixelIndOffset = bixelIndOffset + stf(i).numOfRays;
% store physical position of first entry in bixelIndMap
@@ -90,70 +109,96 @@
lim_l(l) = (lim_lInd-1)*bixelWidth + minX - 1/2*bixelWidth;
lim_r(l) = (lim_rInd-1)*bixelWidth + minX + 1/2*bixelWidth;
end
-
+
% get leaf positions for all shapes
% leaf positions can be extracted from the shapes created in Sequencing
- for m = 1:Sequencing.beam(i).numOfShapes
+ for m = 1:sequencing.beam(i).numOfShapes
- % loading shape from Sequencing result
- shapeMap = Sequencing.beam(i).shapes(:,:,m);
- % get left and right leaf indices from shapemap
- % initializing limits
- leftLeafPos = NaN * ones(dimZ,1);
- rightLeafPos = NaN * ones(dimZ,1);
- % looping over leaf pairs
- for l = 1:dimZ
- leftLeafPosInd = find(shapeMap(l,:),1,'first');
- rightLeafPosInd = find(shapeMap(l,:),1,'last');
-
- if isempty(leftLeafPosInd) && isempty(rightLeafPosInd) % if no bixel is open, use limits from Ray positions
- leftLeafPos(l) = (lim_l(l)+lim_r(l))/2;
- rightLeafPos(l) = leftLeafPos(l);
- else
- % the physical position [mm] can be calculated from the indices
- leftLeafPos(l) = (leftLeafPosInd-1)*bixelWidth...
- + minX - 1/2*bixelWidth;
- rightLeafPos(l) = (rightLeafPosInd-1)*bixelWidth...
- + minX + 1/2*bixelWidth;
-
+ if isfield(sequencing.beam(i),'shapes')
+
+ % loading shape from Sequencing result
+ shapeMap = sequencing.beam(i).shapes(:,:,m);
+ % get left and right leaf indices from shapemap
+ % initializing limits
+ leftLeafPos = NaN * ones(dimZ,1);
+ rightLeafPos = NaN * ones(dimZ,1);
+ % looping over leaf pairs
+ for l = 1:dimZ
+ leftLeafPosInd = find(shapeMap(l,:),1,'first');
+ rightLeafPosInd = find(shapeMap(l,:),1,'last');
+
+ if isempty(leftLeafPosInd) && isempty(rightLeafPosInd) % if no bixel is open, use limits from Ray positions
+ leftLeafPos(l) = (lim_l(l)+lim_r(l))/2;
+ rightLeafPos(l) = leftLeafPos(l);
+ else
+ % the physical position [mm] can be calculated from the indices
+ leftLeafPos(l) = (leftLeafPosInd-1)*bixelWidth...
+ + minX - 1/2*bixelWidth;
+ rightLeafPos(l) = (rightLeafPosInd-1)*bixelWidth...
+ + minX + 1/2*bixelWidth;
+
+ %Can happen in some cases in SW trajectory sampling
+ if leftLeafPos(l) < lim_l(l)
+ leftLeafPos(l) = lim_l(l);
+ end
+ if rightLeafPos(l) > lim_r(l)
+ rightLeafPos(l) = lim_r(l);
+ end
+
+ end
end
+
+ % save data for each shape of this beam
+ apertureInfo.beam(i).shape(m).leftLeafPos = leftLeafPos;
+ apertureInfo.beam(i).shape(m).rightLeafPos = rightLeafPos;
+ apertureInfo.beam(i).shape(m).weight = sequencing.beam(i).shapesWeight(m);
+ apertureInfo.beam(i).shape(m).shapeMap = shapeMap;
end
- % save data for each shape of this beam
- apertureInfo.beam(i).shape(m).leftLeafPos = leftLeafPos;
- apertureInfo.beam(i).shape(m).rightLeafPos = rightLeafPos;
- apertureInfo.beam(i).shape(m).weight = Sequencing.beam(i).shapesWeight(m);
- apertureInfo.beam(i).shape(m).shapeMap = shapeMap;
- apertureInfo.beam(i).shape(m).vectorOffset = vectorOffset;
+ if pln.propOpt.runVMAT
+ apertureInfo.beam(i).shape(m).MURate = sequencing.beam(i).MURate;
+ end
- % update index for bookkeeping
- vectorOffset = vectorOffset + dimZ;
-
- end
+ apertureInfo.beam(i).shape(m).jacobiScale = 1;
- % z-coordinates of active leaf pairs
+ if pln.propOpt.runVMAT && pln.propStf.continuousAperture
+ apertureInfo.beam(i).shape(m).vectorOffset = [vectorOffset vectorOffset+dimZ];
+
+ % update index for bookkeeping
+ vectorOffset = vectorOffset + dimZ*nnz(stf(i).propVMAT.doseAngleDAO);
+ else
+ apertureInfo.beam(i).shape(m).vectorOffset = vectorOffset;
+
+ % update index for bookkeeping
+ vectorOffset = vectorOffset + dimZ;
+ end
+ apertureInfo.beam(i).shape(m).weightOffset = weightOffset;
+ weightOffset = weightOffset+1;
+ end
+
+ % z-coordinates of active leaf pairs
% get z-coordinates from bixel positions
leafPairPos = unique(Z);
-
+
% find upmost and downmost leaf pair
topLeafPairPos = maxZ;
bottomLeafPairPos = minZ;
topLeafPair = centralLeafPair - topLeafPairPos/bixelWidth;
bottomLeafPair = centralLeafPair - bottomLeafPairPos/bixelWidth;
-
+
% create bool map of active leaf pairs
isActiveLeafPair = zeros(numOfMLCLeafPairs,1);
isActiveLeafPair(topLeafPair:bottomLeafPair) = 1;
-
+
% create MLC window
% getting the dimensions of the MLC in order to be able to plot the
% shapes using physical coordinates
MLCWindow = [minX-bixelWidth/2 maxX+bixelWidth/2 ...
- minZ-bixelWidth/2 maxZ+bixelWidth/2];
+ minZ-bixelWidth/2 maxZ+bixelWidth/2];
% save data for each beam
- apertureInfo.beam(i).numOfShapes = Sequencing.beam(i).numOfShapes;
+ apertureInfo.beam(i).numOfShapes = sequencing.beam(i).numOfShapes;
apertureInfo.beam(i).numOfActiveLeafPairs = dimZ;
apertureInfo.beam(i).leafPairPos = leafPairPos;
apertureInfo.beam(i).isActiveLeafPair = isActiveLeafPair;
@@ -163,17 +208,144 @@
apertureInfo.beam(i).bixelIndMap = bixelIndMap;
apertureInfo.beam(i).posOfCornerBixel = posOfCornerBixel;
apertureInfo.beam(i).MLCWindow = MLCWindow;
+ apertureInfo.beam(i).gantryAngle = stf(i).gantryAngle;
+ if pln.propOpt.runVMAT
+
+ apertureInfo.beam(i).bixOffset = bixOffset;
+ bixOffset = bixOffset+apertureInfo.beam(i).numOfActiveLeafPairs;
+
+ apertureInfo.propVMAT.beam(i).DAOBeam = stf(i).propVMAT.DAOBeam;
+ apertureInfo.propVMAT.beam(i).FMOBeam = stf(i).propVMAT.FMOBeam;
+
+ apertureInfo.propVMAT.beam(i).leafDir = sequencing.beam(i).leafDir;
+
+ apertureInfo.propVMAT.beam(i).doseAngleBorders = stf(i).propVMAT.doseAngleBorders;
+ apertureInfo.propVMAT.beam(i).doseAngleBorderCentreDiff = stf(i).propVMAT.doseAngleBorderCentreDiff;
+ apertureInfo.propVMAT.beam(i).doseAngleBordersDiff = stf(i).propVMAT.doseAngleBordersDiff;
+
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+
+ totalNumOfOptBixels = totalNumOfOptBixels+stf(i).totalNumOfBixels;
+ totalNumOfLeafPairs = totalNumOfLeafPairs+apertureInfo.beam(i).numOfShapes*apertureInfo.beam(i).numOfActiveLeafPairs;
+
+ apertureInfo.beam(i).gantryRot = sequencing.beam(i).gantryRot;
+
+ apertureInfo.propVMAT.beam(i).DAOAngleBorders = stf(i).propVMAT.DAOAngleBorders;
+ apertureInfo.propVMAT.beam(i).DAOAngleBorderCentreDiff = stf(i).propVMAT.DAOAngleBorderCentreDiff;
+ apertureInfo.propVMAT.beam(i).DAOAngleBordersDiff = stf(i).propVMAT.DAOAngleBordersDiff;
+
+ apertureInfo.propVMAT.beam(i).timeFacCurr = stf(i).propVMAT.timeFacCurr;
+ apertureInfo.propVMAT.beam(i).timeFac = stf(i).propVMAT.timeFac;
+
+ apertureInfo.propVMAT.beam(i).lastDAOIndex = stf(i).propVMAT.lastDAOIndex;
+ apertureInfo.propVMAT.beam(i).nextDAOIndex = stf(i).propVMAT.nextDAOIndex;
+ apertureInfo.propVMAT.beam(i).DAOIndex = stf(i).propVMAT.DAOIndex;
+
+ if apertureInfo.propVMAT.beam(i).FMOBeam
+ apertureInfo.propVMAT.beam(i).FMOAngleBorders = stf(i).propVMAT.FMOAngleBorders;
+ apertureInfo.propVMAT.beam(i).FMOAngleBorderCentreDiff = stf(i).propVMAT.FMOAngleBorderCentreDiff;
+ apertureInfo.propVMAT.beam(i).FMOAngleBordersDiff = stf(i).propVMAT.FMOAngleBordersDiff;
+ end
+
+ if pln.propStf.continuousAperture
+ apertureInfo.propVMAT.beam(i).timeFacInd = stf(i).propVMAT.timeFacInd;
+ apertureInfo.propVMAT.beam(i).doseAngleDAO = stf(i).propVMAT.doseAngleDAO;
+
+ apertureInfo.propVMAT.beam(i).leafConstMask = 1;
+ interpGetsTransition = apertureInfo.propVMAT.beam(i).timeFac(3) ~= 0;
+ end
+
+ apertureInfo.propVMAT.jacobT(stf(i).propVMAT.DAOIndex,i) = stf(i).propVMAT.timeFacCurr;
+
+ else
+ apertureInfo.propVMAT.beam(i).fracFromLastDAO = stf(i).propVMAT.fracFromLastDAO;
+ apertureInfo.propVMAT.beam(i).timeFracFromLastDAO = stf(i).propVMAT.timeFracFromLastDAO;
+ apertureInfo.propVMAT.beam(i).timeFracFromNextDAO = stf(i).propVMAT.timeFracFromNextDAO;
+ apertureInfo.propVMAT.beam(i).lastDAOIndex = stf(i).propVMAT.lastDAOIndex;
+ apertureInfo.propVMAT.beam(i).nextDAOIndex = stf(i).propVMAT.nextDAOIndex;
+
+ if pln.propStf.continuousAperture
+ apertureInfo.propVMAT.beam(i).fracFromLastDAO_I = stf(i).propVMAT.fracFromLastDAO_I;
+ apertureInfo.propVMAT.beam(i).fracFromLastDAO_F = stf(i).propVMAT.fracFromLastDAO_F;
+ apertureInfo.propVMAT.beam(i).fracFromNextDAO_I = stf(i).propVMAT.fracFromNextDAO_I;
+ apertureInfo.propVMAT.beam(i).fracFromNextDAO_F = stf(i).propVMAT.fracFromNextDAO_F;
+ end
+
+ apertureInfo.propVMAT.jacobT(stf(stf(i).propVMAT.lastDAOIndex).propVMAT.DAOIndex,i) = stf(stf(i).propVMAT.lastDAOIndex).propVMAT.timeFacCurr.*stf(i).propVMAT.timeFracFromLastDAO.*stf(i).propVMAT.doseAngleBordersDiff./stf(stf(i).propVMAT.lastDAOIndex).propVMAT.doseAngleBordersDiff;
+ apertureInfo.propVMAT.jacobT(stf(stf(i).propVMAT.nextDAOIndex).propVMAT.DAOIndex,i) = stf(stf(i).propVMAT.nextDAOIndex).propVMAT.timeFacCurr.*stf(i).propVMAT.timeFracFromNextDAO.*stf(i).propVMAT.doseAngleBordersDiff./stf(stf(i).propVMAT.lastDAOIndex).propVMAT.doseAngleBordersDiff;
+
+ if interpGetsTransition
+ apertureInfo.propVMAT.beam(i).leafConstMask = 1;
+ end
+ interpGetsTransition = false;
+ end
+ end
+end
+
+if ~isfield(pln.propOpt,'preconditioner')
+ pln.propOpt.preconditioner = false;
end
% save global data
-apertureInfo.bixelWidth = bixelWidth;
-apertureInfo.numOfMLCLeafPairs = numOfMLCLeafPairs;
-apertureInfo.totalNumOfBixels = totalNumOfBixels;
-apertureInfo.totalNumOfShapes = sum([apertureInfo.beam.numOfShapes]);
-apertureInfo.totalNumOfLeafPairs = sum([apertureInfo.beam.numOfShapes]*[apertureInfo.beam.numOfActiveLeafPairs]');
+apertureInfo.continuousAperture = pln.propStf.continuousAperture;
+apertureInfo.runVMAT = pln.propOpt.runVMAT;
+apertureInfo.preconditioner = pln.propOpt.preconditioner;
+apertureInfo.bixelWidth = bixelWidth;
+apertureInfo.numOfMLCLeafPairs = numOfMLCLeafPairs;
+apertureInfo.totalNumOfBixels = totalNumOfBixels;
+apertureInfo.totalNumOfShapes = sum([apertureInfo.beam.numOfShapes]);
-% create vectors for optimization
-[apertureInfo.apertureVector, apertureInfo.mappingMx, apertureInfo.limMx] = matRad_OptimizationProblemDAO.matRad_daoApertureInfo2Vec(apertureInfo);
+if isfield(sequencing,'weightToMU')
+ apertureInfo.weightToMU = sequencing.weightToMU;
+end
+
+if pln.propOpt.runVMAT
+
+ %tempStruct_beam = apertureInfo.propVMAT.beam;
+ %tempStruct_jacobT = apertureInfo.propVMAT.jacobT;
+ %apertureInfo.propVMAT.beam = tempStruct_beam;
+ %apertureInfo.propVMAT.jacobT = tempStruct_jacobT;
+
+ % put constraints in apertureInfo.propVMAT
+ machine = matRad_loadMachine(pln);
+ apertureInfo.propVMAT.constraints = machine.constraints;
+
+ apertureInfo.totalNumOfOptBixels = totalNumOfOptBixels;
+ apertureInfo.doseTotalNumOfLeafPairs = sum([apertureInfo.beam(:).numOfActiveLeafPairs]);
+
+ if apertureInfo.continuousAperture
+ apertureInfo.totalNumOfLeafPairs = sum(reshape([apertureInfo.propVMAT.beam([apertureInfo.propVMAT.beam.DAOBeam]).doseAngleDAO],2,[]),1)*[apertureInfo.beam([apertureInfo.propVMAT.beam.DAOBeam]).numOfActiveLeafPairs]';
+
+ % count number of transitions
+ apertureInfo.propVMAT.numLeafSpeedConstraint = nnz([apertureInfo.propVMAT.beam.leafConstMask]);
+ apertureInfo.propVMAT.numLeafSpeedConstraintDAO = nnz([apertureInfo.propVMAT.beam([apertureInfo.propVMAT.beam.DAOBeam]).leafConstMask]);
+ else
+ apertureInfo.totalNumOfLeafPairs = totalNumOfLeafPairs;
+ end
+
+ % fix instances of leaf touching
+ apertureInfo = matRad_leafTouching(apertureInfo);
+
+ shapeInd = 0;
+ for i = 1:numel(apertureInfo.beam)
+ if apertureInfo.propVMAT.beam(i).DAOBeam
+ shapeInd = shapeInd+1;
+ apertureInfo.propVMAT.beam(i).timeInd = apertureInfo.totalNumOfShapes+apertureInfo.totalNumOfLeafPairs*2+shapeInd;
+ end
+ end
+
+ % create vectors for optimization
+ [apertureInfo.apertureVector, apertureInfo.mappingMx, apertureInfo.limMx] = matRad_OptimizationProblemVMAT.matRad_daoApertureInfo2Vec(apertureInfo);
+
+else
+ apertureInfo.totalNumOfOptBixels = sum(stf(i).totalNumOfBixels);
+ apertureInfo.totalNumOfLeafPairs = sum([apertureInfo.beam.numOfShapes]*[apertureInfo.beam.numOfActiveLeafPairs]');
+ apertureInfo.doseTotalNumOfLeafPairs = apertureInfo.totalNumOfLeafPairs;
+
+ % create vectors for optimization
+ [apertureInfo.apertureVector, apertureInfo.mappingMx, apertureInfo.limMx] = matRad_OptimizationProblemDAO.matRad_daoApertureInfo2Vec(apertureInfo);
+end
end
+
diff --git a/matRad/sequencing/matRad_siochiLeafSequencing.m b/matRad/sequencing/matRad_siochiLeafSequencing.m
index 4240a6c9a..224d7e2b2 100644
--- a/matRad/sequencing/matRad_siochiLeafSequencing.m
+++ b/matRad/sequencing/matRad_siochiLeafSequencing.m
@@ -1,4 +1,7 @@
-function resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,numOfLevels,visBool)
+function resultGUI = matRad_siochiLeafSequencing(resultGUI,stf,dij,pln,visBool)
+% multileaf collimator leaf sequencing algorithm for intensity modulated
+% beams with multiple static segments according to Siochi (1999)
+% International Journal of Radiation Oncology * Biology * Physics,
% multileaf collimator leaf sequencing algorithm
% for intensity modulated beams with multiple static segments according to
% Siochi (1999)International Journal of Radiation Oncology * Biology * Physics,
@@ -8,9 +11,9 @@
%
% call
% resultGUI =
-% matRad_siochiLeafSequencing(resultGUI,stf,dij,numOfLevels)
+% matRad_siochiLeafSequencing(resultGUI,stf,dij,pln)
% resultGUI =
-% matRad_siochiLeafSequencing(resultGUI,stf,dij,numOfLevels,visBool)
+% matRad_siochiLeafSequencing(resultGUI,stf,dij,pln,visBool)
%
% input
% resultGUI: resultGUI struct to which the output data will be
@@ -18,7 +21,7 @@
% be created
% stf: matRad steering information struct
% dij: matRad's dij matrix
-% numOfLevels: number of stratification levels
+% pln: pln struct
% visBool: toggle on/off visualization (optional)
%
% output
@@ -41,6 +44,8 @@
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+matRad_cfg = MatRad_Config.instance();
+
% if visBool not set toogle off visualization
if nargin < 5
visBool = 0;
@@ -54,10 +59,24 @@
screensize = get(0,'ScreenSize');
xpos = ceil((screensize(3)-sz(2))/2); % center the figure on the screen horizontally
ypos = ceil((screensize(4)-sz(1))/2); % center the figure on the screen vertically
- seqFig = figure('position',[xpos,ypos,sz(2),sz(1)]);
+ seqFig = figure('position',[xpos,ypos,sz(2),sz(1)]);
+end
+
+offset = 0;
+
+if ~isfield(pln,'propSeq') || ~isfield(pln.propSeq,'numLevels')
+ pln.propSeq.numLevels = matRad_cfg.defaults.propSeq.numLevels;
end
-offset = 0;
+if ~isfield(pln, 'propOpt') || ~isfield(pln.propOpt,'runVMAT')
+ pln.propOpt.runVMAT = false;
+end
+
+sequencing.runVMAT = pln.propOpt.runVMAT;
+
+if isfield(resultGUI,'scaleFacRx_FMO')
+ resultGUI.wUnsequenced = resultGUI.wUnsequenced/resultGUI.scaleFacRx_FMO;
+end
if ~isfield(resultGUI,'wUnsequenced')
wUnsequenced = resultGUI.w;
@@ -65,10 +84,36 @@
wUnsequenced = resultGUI.wUnsequenced;
end
+if ~isfield(dij,'weightToMU')
+ dij.weightToMU = 1;
+end
+
for i = 1:numOfBeams
-
numOfRaysPerBeam = stf(i).numOfRays;
+ if pln.propOpt.runVMAT
+
+ if ~stf(i).propVMAT.FMOBeam
+ sequencing.w(1+offset:numOfRaysPerBeam+offset,1) = 0;
+
+ sequencing.beam(i).bixelIx = 1+offset:numOfRaysPerBeam+offset;
+
+ offset = offset + numOfRaysPerBeam;
+ continue %if this is not a beam to be initialized, continue to next iteration without generating segments
+ else
+ numToKeep = stf(i).propVMAT.numOfBeamChildren;
+ end
+ else
+
+ sequencing.beam(i).numOfShapes = 0;
+ %does this make sense to discard apertures if VMAT isn't run?
+ if isfield(pln.propOpt,'numApertures')
+ numToKeep = pln.propOpt.numApertures; %if all apertures are to be kept, this should be set to 0
+ else
+ numToKeep = 0;
+ end
+ end
+
% get relevant weights for current beam
wOfCurrBeams = wUnsequenced(1+offset:numOfRaysPerBeam+offset);%REVIEW OFFSET
@@ -103,47 +148,78 @@
%Save weights in fluence matrix.
fluenceMx(indInFluenceMx) = wOfCurrBeams;
- % Stratification
- calFac = max(fluenceMx(:));
- D_k = round(fluenceMx/calFac*numOfLevels);
-
- % Save the stratification in the initial intensity matrix D_0.
- D_0 = D_k;
-
- % container to remember generated shapes; allocate space for 10000
- % shapes
- shapes = NaN*ones(dimOfFluenceMxZ,dimOfFluenceMxX,10000);
- shapesWeight = zeros(10000,1);
- k = 0;
-
- if visBool
- clf(seqFig);
- colormap(seqFig,'jet');
-
- seqSubPlots(1) = subplot(2,2,1,'parent',seqFig);
- imagesc(D_k,'parent',seqSubPlots(1));
- set(seqSubPlots(1),'CLim',[0 numOfLevels],'YDir','normal');
- title(seqSubPlots(1),['Beam # ' num2str(i) ': max(D_0) = ' num2str(max(D_0(:))) ' - ' num2str(numel(unique(D_0))) ' intensity levels']);
- xlabel(seqSubPlots(1),'x - direction parallel to leaf motion ')
- ylabel(seqSubPlots(1),'z - direction perpendicular to leaf motion ')
- colorbar;
- drawnow
+ if matRad_cfg.isMatlab && license('test','image_toolbox')
+ temp = zeros(size(fluenceMx));
+ for row = 1:dimOfFluenceMxZ
+ temp(row,:) = imgaussfilt(fluenceMx(row,:),1);
+ end
+ fluenceMx = temp;
+ clear temp
end
+
+
+ %allow for possibility to repeat sequencing with higher number of
+ %levels if number of apertures is lower than required
+ notFinished = 1;
+ numOfLevels = pln.propSeq.numLevels;
- D_k_nonZero = (D_k~=0);
- [D_k_Z, D_k_X] = ind2sub([dimOfFluenceMxZ,dimOfFluenceMxX],find(D_k_nonZero));
- D_k_MinZ = min(D_k_Z);
- D_k_MaxZ = max(D_k_Z);
- D_k_MinX = min(D_k_X);
- D_k_MaxX = max(D_k_X);
-
- %Decompose the port, do rod pushing
- [tops, bases] = matRad_siochiDecomposePort(D_k,dimOfFluenceMxZ,dimOfFluenceMxX,D_k_MinZ,D_k_MaxZ,D_k_MinX,D_k_MaxX);
- %Form segments with and without visualization
- if visBool
- [shapes,shapesWeight,k,D_k]=matRad_siochiConvertToSegments(shapes,shapesWeight,k,tops,bases,visBool,i,D_k,numOfLevels,seqFig,seqSubPlots);
- else
- [shapes,shapesWeight,k]=matRad_siochiConvertToSegments(shapes,shapesWeight,k,tops,bases);
+ while notFinished
+ % Keep looping until we have at least as many apertures as
+ % numToKeep. Increment numOfLevels by 1 each iteration
+
+ % prepare sequencer
+ calFac = max(fluenceMx(:));
+
+ D_k = round(fluenceMx/calFac*numOfLevels);
+
+ % Save the stratification in the initial intensity matrix D_0.
+ D_0 = D_k;
+
+ % container to remember generated shapes; allocate space for 10000
+ % shapes
+ shapes = NaN*ones(dimOfFluenceMxZ,dimOfFluenceMxX,10000);
+ shapesWeight = zeros(10000,1);
+ k = 0;
+
+ if visBool
+ clf(seqFig);
+ colormap(seqFig,'jet');
+
+ seqSubPlots(1) = subplot(2,2,1,'parent',seqFig);
+ imagesc(D_k,'parent',seqSubPlots(1));
+ set(seqSubPlots(1),'CLim',[0 numOfLevels],'YDir','normal');
+ title(seqSubPlots(1),['Beam # ' num2str(i) ': max(D_0) = ' num2str(max(D_0(:))) ' - ' num2str(numel(unique(D_0))) ' intensity levels']);
+ xlabel(seqSubPlots(1),'x - direction parallel to leaf motion ')
+ ylabel(seqSubPlots(1),'z - direction perpendicular to leaf motion ')
+ colorbar;
+ drawnow
+ end
+
+ D_k_nonZero = (D_k~=0);
+ [D_k_Z, D_k_X] = ind2sub([dimOfFluenceMxZ,dimOfFluenceMxX],find(D_k_nonZero));
+ D_k_MinZ = min(D_k_Z);
+ D_k_MaxZ = max(D_k_Z);
+ D_k_MinX = min(D_k_X);
+ D_k_MaxX = max(D_k_X);
+
+ %Decompose the port, do rod pushing
+ [tops, bases] = matRad_siochiDecomposePort(D_k,dimOfFluenceMxZ,dimOfFluenceMxX,D_k_MinZ,D_k_MaxZ,D_k_MinX,D_k_MaxX);
+
+ %Form segments with and without visualization
+ if visBool
+ [shapes,shapesWeight,k,D_k]=matRad_siochiConvertToSegments(shapes,shapesWeight,k,tops,bases,visBool,i,D_k,numOfLevels,seqFig,seqSubPlots);
+ else
+ [shapes,shapesWeight,k]=matRad_siochiConvertToSegments(shapes,shapesWeight,k,tops,bases);
+ end
+
+ %are there enough apertures?
+ if numToKeep ~= 0 && k < numToKeep
+ % no, therefore increment numOfLevels
+ numOfLevels = numOfLevels+1;
+ else
+ % yes, therefore exit out of the while loop
+ notFinished = 0;
+ end
end
sequencing.beam(i).numOfShapes = k;
@@ -152,21 +228,67 @@
sequencing.beam(i).bixelIx = 1+offset:numOfRaysPerBeam+offset;
sequencing.beam(i).fluence = D_0;
sequencing.beam(i).sum = zeros(dimOfFluenceMxZ,dimOfFluenceMxX);
+
+ if numToKeep ~= 0
+ sequencing.beam(i) = matRad_discardApertures(sequencing.beam(i),numToKeep);
+ end
- for j = 1:k
- sequencing.beam(i).sum = sequencing.beam(i).sum+sequencing.beam(i).shapes(:,:,j)*sequencing.beam(i).shapesWeight(j);
+ if ~pln.propOpt.runVMAT
+
+ for shape = 1:sequencing.beam(i).numOfShapes
+ sequencing.beam(i).sum = sequencing.beam(i).sum+sequencing.beam(i).shapes(:,:,shape)*sequencing.beam(i).shapesWeight(shape);
+ end
+ sequencing.w(1+offset:numOfRaysPerBeam+offset,1) = sequencing.beam(i).sum(indInFluenceMx);
end
- sequencing.w(1+offset:numOfRaysPerBeam+offset,1) = sequencing.beam(i).sum(indInFluenceMx);
offset = offset + numOfRaysPerBeam;
+end
+
+if pln.propOpt.runVMAT
+
+ % do arc sequencing
+ sequencing.beam = matRad_arcSequencing(sequencing.beam,stf,pln,dij.weightToMU);
+
+ % carry variables
+ sequencing.weightToMU = dij.weightToMU;
+
+ % get apertureInfo
+ resultGUI.apertureInfo = matRad_sequencing2ApertureInfo(sequencing,stf,pln);
+
+ %matRad_daoVec2ApertureInfo will interpolate subchildren gantry
+ %segments
+ resultGUI.apertureInfo = matRad_OptimizationProblemVMAT.matRad_daoVec2ApertureInfo(resultGUI.apertureInfo,resultGUI.apertureInfo.apertureVector);
+
+ %calculate max leaf speed
+ resultGUI.apertureInfo = matRad_maxLeafSpeed(resultGUI.apertureInfo);
+
+ %optimize delivery
+ resultGUI = matRad_optDelivery(resultGUI,0);
+ resultGUI.apertureInfo = matRad_maxLeafSpeed(resultGUI.apertureInfo);
+
+ sequencing.w = resultGUI.apertureInfo.bixelWeights;
+
+else
+ sequencing.weightToMU = dij.weightToMU;
+
+ resultGUI.apertureInfo = matRad_sequencing2ApertureInfo(sequencing,stf,pln);
+
+ resultGUI.apertureInfo = matRad_OptimizationProblemDAO.matRad_daoVec2ApertureInfo(resultGUI.apertureInfo,resultGUI.apertureInfo.apertureVector);
+end
+if ~isfield(pln.propOpt,'preconditioner')
+ pln.propOpt.preconditioner = false;
+end
+
+if pln.propOpt.preconditioner
+ % calculation preconditioning factors
+ resultGUI.apertureInfo = matRad_preconditionFactors(resultGUI.apertureInfo);
end
resultGUI.w = sequencing.w;
resultGUI.wSequenced = sequencing.w;
resultGUI.sequencing = sequencing;
-resultGUI.apertureInfo = matRad_sequencing2ApertureInfo(sequencing,stf);
doseSequencedDoseGrid = reshape(dij.physicalDose{1} * sequencing.w,dij.doseGrid.dimensions);
% interpolate to ct grid for visualiation & analysis
@@ -191,7 +313,9 @@
for i = minX:maxX
maxTop = -1;
- TnG = 1;
+
+ TnG = 0; %FOR NOW
+
for j = minZ:maxZ
if i == minX
bases(j,i) = 1;
diff --git a/matRad/sequencing/matRad_xiaLeafSequencing.m b/matRad/sequencing/matRad_xiaLeafSequencing.m
index aaa893324..549311451 100644
--- a/matRad/sequencing/matRad_xiaLeafSequencing.m
+++ b/matRad/sequencing/matRad_xiaLeafSequencing.m
@@ -1,18 +1,18 @@
-function resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,numOfLevels,visBool)
+function resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,pln,visBool)
% multileaf collimator leaf sequencing algorithm
% for intensity modulated beams with multiple static segments according to
% Xia et al. (1998) Medical Physics
%
% call
-% resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,numOfLevels)
-% resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,numOfLevels,visBool)
+% resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,pln)
+% resultGUI = matRad_xiaLeafSequencing(resultGUI,stf,dij,pln,visBool)
%
% input
% resultGUI: resultGUI struct to which the output data will be added, if
% this field is empty resultGUI struct will be created
% stf: matRad steering information struct
% dij: matRad's dij matrix
-% numOfLevels: number of stratification levels
+% pln: pln structure
% visBool: toggle on/off visualization (optional)
%
% output
@@ -40,6 +40,8 @@
visBool = 0;
end
+matRad_cfg = MatRad_Config.instance();
+
mode = 'rl'; % sliding window (sw) or reducing level (rl)
numOfBeams = numel(stf);
@@ -53,6 +55,10 @@
seqFig = figure('position',[xpos,ypos,sz(2),sz(1)]);
end
+if ~isfield(pln,'propSeq') || ~isfield(pln.propSeq,'numLevels')
+ pln.propSeq.numLevels = matRad_cfg.defaults.propSeq.numLevels;
+end
+
offset = 0;
for i = 1:numOfBeams
@@ -95,7 +101,7 @@
% Stratification
calFac = max(fluenceMx(:));
- D_k = round(fluenceMx/calFac*numOfLevels);
+ D_k = round(fluenceMx/calFac*pln.propSeq.numLevels);
% Save the stratification in the initial intensity matrix D_0.
D_0 = D_k;
@@ -242,11 +248,11 @@
sequencing.beam(i).numOfShapes = k;
sequencing.beam(i).shapes = shapes(:,:,1:k);
- sequencing.beam(i).shapesWeight = shapesWeight(1:k)/numOfLevels*calFac;
+ sequencing.beam(i).shapesWeight = shapesWeight(1:k)/pln.propSeq.numLevels*calFac;
sequencing.beam(i).bixelIx = 1+offset:numOfRaysPerBeam+offset;
sequencing.beam(i).fluence = D_0;
- sequencing.w(1+offset:numOfRaysPerBeam+offset,1) = D_0(indInFluenceMx)/numOfLevels*calFac;
+ sequencing.w(1+offset:numOfRaysPerBeam+offset,1) = D_0(indInFluenceMx)/pln.propSeq.numLevels*calFac;
offset = offset + numOfRaysPerBeam;
@@ -256,7 +262,7 @@
resultGUI.wSequenced = sequencing.w;
resultGUI.sequencing = sequencing;
-resultGUI.apertureInfo = matRad_sequencing2ApertureInfo(sequencing,stf);
+resultGUI.apertureInfo = matRad_sequencing2ApertureInfo(sequencing,stf,pln);
doseSequencedDoseGrid = reshape(dij.physicalDose{1} * sequencing.w,dij.doseGrid.dimensions);
% interpolate to ct grid for visualiation & analysis
diff --git a/matRad/steering/matRad_StfGeneratorBase.m b/matRad/steering/matRad_StfGeneratorBase.m
index 31c5a11ee..a4f67649a 100644
--- a/matRad/steering/matRad_StfGeneratorBase.m
+++ b/matRad/steering/matRad_StfGeneratorBase.m
@@ -220,7 +220,7 @@ function assignPropertiesFromPln(this,pln,warnWhenPropertyChanged)
this.initialize();
this.createPatientGeometry();
- stf = this.generateSourceGeometry();
+ stf = this.generateSourceGeometry();
end
end
diff --git a/matRad/steering/matRad_StfGeneratorPhotonVMAT.m b/matRad/steering/matRad_StfGeneratorPhotonVMAT.m
new file mode 100644
index 000000000..73e35377d
--- /dev/null
+++ b/matRad/steering/matRad_StfGeneratorPhotonVMAT.m
@@ -0,0 +1,399 @@
+classdef matRad_StfGeneratorPhotonVMAT < matRad_StfGeneratorPhotonRayBixelAbstract
+
+ properties (Constant)
+ name = 'Photon VMAT stf Generator';
+ shortName = 'PhotonVMAT';
+ possibleRadiationModes = {'photons'};
+ end
+
+ properties
+ % TODO: set defaults for these
+ DAOGantryAngles;
+ FMOGantryAngles;
+ startingAngle;
+ finishingAngle;
+ continuousAperture;
+ end
+
+ methods
+ function this = matRad_StfGeneratorPhotonVMAT(pln)
+ if nargin < 1
+ pln = [];
+ end
+
+ this@matRad_StfGeneratorPhotonRayBixelAbstract(pln);
+
+ if ~isempty(pln)
+ % this could probably be integrated better
+ pln = matRad_VMATGantryAngles(pln);
+ % the following were just generated by the preceding
+ % function call matRad_VMATGantryAngles
+ this.gantryAngles = pln.propStf.gantryAngles;
+ this.DAOGantryAngles = pln.propStf.DAOGantryAngles;
+ this.FMOGantryAngles = pln.propStf.FMOGantryAngles;
+ this.couchAngles = pln.propStf.couchAngles;
+ this.isoCenter = pln.propStf.isoCenter;
+ end
+
+ if isempty(this.radiationMode)
+ this.radiationMode = 'photons';
+ end
+ end
+ end
+
+ methods (Access = protected)
+ function pbMargin = getPbMargin(this)
+ pbMargin = this.bixelWidth;
+ end
+
+ function stf = generateSourceGeometry(this)
+ stf = this.generateSourceGeometry@matRad_StfGeneratorPhotonRayBixelAbstract();
+
+ matRad_cfg = MatRad_Config.instance();
+ matRad_cfg.dispInfo('Apply VMAT configuration to stf...\n');
+
+ masterRayPosBEV = zeros(0,3);
+ for i = 1:numel(stf)
+ rayPosBEV = reshape([stf(i).ray(:).rayPos_bev]',3,stf(i).numOfRays)';
+ masterRayPosBEV = union(masterRayPosBEV,rayPosBEV,'rows');
+ end
+
+ % masterRayPosBEV
+ x = masterRayPosBEV(:,1);
+ y = masterRayPosBEV(:,2);
+ z = masterRayPosBEV(:,3);
+ uniZ = unique(z);
+ for j = 1:numel(uniZ)
+ x_loc = x(z == uniZ(j));
+ x_min = min(x_loc);
+ x_max = max(x_loc);
+ x = [x; (x_min:this.bixelWidth:x_max)'];
+ y = [y; zeros((x_max-x_min)/this.bixelWidth+1,1)];
+ z = [z; uniZ(j)*ones((x_max-x_min)/this.bixelWidth+1,1)];
+ end
+
+ SAD = this.machine.meta.SAD;
+
+ masterRayPosBEV = [x,y,z];
+ masterRayPosBEV = unique(masterRayPosBEV,'rows');
+ masterTargetPointBEV = [2*masterRayPosBEV(:,1) SAD*ones(size(masterRayPosBEV,1),1) 2*masterRayPosBEV(:,3)];
+
+ % post-processing function for VMAT
+ matRad_cfg.dispInfo('VMAT stf post-processing (1/2)... ');
+
+ numDAO = 1;
+ DAODoseAngleBorders = zeros(2*numel(this.DAOGantryAngles),1);
+ offset = 1;
+ timeFacIndOffset = 1;
+
+ for i = 1:length(this.gantryAngles)
+
+ %stf(i).continuousAperture = this.continuousAperture;
+
+ % Determine which FMO beam the current beam belongs to
+ [~,stf(i).propVMAT.beamParentFMOIndex] = min(abs(this.FMOGantryAngles-this.gantryAngles(i)));
+ stf(i).propVMAT.beamParentGantryAngle = this.FMOGantryAngles(stf(i).propVMAT.beamParentFMOIndex);
+ stf(i).propVMAT.beamParentIndex = find(abs(this.gantryAngles - stf(i).propVMAT.beamParentGantryAngle) < 1e-6);
+
+ % Indicate if this beam is to be included in DOA/FMO or not. All beams
+ % are still considered in dose calc for objective function in DAO
+ stf(i).propVMAT.FMOBeam = any(abs(this.FMOGantryAngles - this.gantryAngles(i)) < 1e-6);
+ stf(i).propVMAT.DAOBeam = any(abs(this.DAOGantryAngles - this.gantryAngles(i)) < 1e-6);
+
+ %% Determine different angle borders
+
+ % doseAngleBorders are the angular borders over which dose is deposited
+ if i == 1
+ stf(i).propVMAT.doseAngleBorders = [this.startingAngle (this.gantryAngles(i+1)+this.gantryAngles(i))/2];
+ elseif i == length(this.gantryAngles)
+
+ stf(i).propVMAT.doseAngleBorders = [(this.gantryAngles(i-1)+this.gantryAngles(i))/2 this.finishingAngle];
+ else
+
+ stf(i).propVMAT.doseAngleBorders = ([this.gantryAngles(i-1) this.gantryAngles(i+1)]+this.gantryAngles(i))/2;
+ end
+
+ stf(i).propVMAT.doseAngleBorderCentreDiff = [stf(i).gantryAngle-stf(i).propVMAT.doseAngleBorders(1) stf(i).propVMAT.doseAngleBorders(2)-stf(i).gantryAngle];
+ stf(i).propVMAT.doseAngleBordersDiff = sum(stf(i).propVMAT.doseAngleBorderCentreDiff);
+
+ %Assign beam to its Parent, either as child (optimized) or subchild
+ %(interpolated)
+ if stf(i).propVMAT.DAOBeam
+ DAODoseAngleBorders((offset):(offset+1)) = stf(i).propVMAT.doseAngleBorders;
+ offset= offset+2;
+
+ if ~isfield(stf(stf(i).propVMAT.beamParentIndex).propVMAT,'beamChildrenGantryAngles') || isempty(stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamChildrenGantryAngles)
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamChildren = 0;
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamChildrenGantryAngles = nan(1000,1);
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamChildrenIndex = nan(1000,1);
+ end
+
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamChildren = stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamChildren+1;
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamChildrenGantryAngles(stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamChildren) = this.gantryAngles(i);
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamChildrenIndex(stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamChildren) = i;
+
+ %optAngleBorders are the angular borders over which an optimized control point
+ %has influence
+ DAOIndex = find(abs(this.DAOGantryAngles - this.gantryAngles(i)) < 1e-8);
+
+ if DAOIndex == 1
+ stf(i).propVMAT.DAOAngleBorders = [this.startingAngle (this.DAOGantryAngles(DAOIndex+1)+this.DAOGantryAngles(DAOIndex))/2];
+
+ lastDAOIndex = i;
+ nextDAOIndex = find(abs(this.gantryAngles - this.DAOGantryAngles(DAOIndex+1)) < 1e-8);
+
+ stf(i).propVMAT.lastDAOIndex = lastDAOIndex;
+ stf(i).propVMAT.nextDAOIndex = nextDAOIndex;
+ elseif DAOIndex == length(this.DAOGantryAngles)
+ stf(i).propVMAT.DAOAngleBorders = [(this.DAOGantryAngles(DAOIndex-1)+this.DAOGantryAngles(DAOIndex))/2 this.finishingAngle];
+
+ stf(i).propVMAT.lastDAOIndex = find(abs(this.gantryAngles - this.DAOGantryAngles(DAOIndex-1)) < 1e-8);
+ stf(i).propVMAT.nextDAOIndex = i;
+ else
+ stf(i).propVMAT.DAOAngleBorders = ([this.DAOGantryAngles(DAOIndex-1) this.DAOGantryAngles(DAOIndex+1)]+this.DAOGantryAngles(DAOIndex))/2;
+
+ lastDAOIndex = i;
+ nextDAOIndex = find(abs(this.gantryAngles - this.DAOGantryAngles(DAOIndex+1)) < 1e-8);
+
+ stf(i).propVMAT.lastDAOIndex = lastDAOIndex;
+ stf(i).propVMAT.nextDAOIndex = nextDAOIndex;
+ end
+
+ stf(i).propVMAT.DAOIndex = numDAO;
+ numDAO = numDAO+1;
+
+ stf(i).propVMAT.DAOAngleBorderCentreDiff = [stf(i).gantryAngle-stf(i).propVMAT.DAOAngleBorders(1) stf(i).propVMAT.DAOAngleBorders(2)-stf(i).gantryAngle];
+ stf(i).propVMAT.DAOAngleBordersDiff = sum(stf(i).propVMAT.DAOAngleBorderCentreDiff);
+
+ %This is the factor that relates the total time in the
+ %optimized arc sector to the total time in the current dose
+ %sector
+ stf(i).propVMAT.timeFacCurr = stf(i).propVMAT.doseAngleBordersDiff./stf(i).propVMAT.DAOAngleBordersDiff;
+
+ if this.continuousAperture
+ %These are the factors that relate the total time in the
+ %optimized arc sector to the total time in the previous and
+ %next dose sectors
+ stf(i).propVMAT.timeFac = zeros(1,3);
+
+ stf(i).propVMAT.timeFac(1) = (stf(i).propVMAT.DAOAngleBorderCentreDiff(1)-stf(i).propVMAT.doseAngleBorderCentreDiff(1))/stf(i).propVMAT.DAOAngleBordersDiff;
+ stf(i).propVMAT.timeFac(2) = stf(i).propVMAT.timeFacCurr;
+ stf(i).propVMAT.timeFac(3) = (stf(i).propVMAT.DAOAngleBorderCentreDiff(2)-stf(i).propVMAT.doseAngleBorderCentreDiff(2))/stf(i).propVMAT.DAOAngleBordersDiff;
+
+ % keep entries with a non-0 timeFac
+ delInd = stf(i).propVMAT.timeFac == 0;
+
+ % write timeFacInd
+ stf(i).propVMAT.timeFacInd = [timeFacIndOffset-1 timeFacIndOffset timeFacIndOffset+1];
+ stf(i).propVMAT.timeFacInd(delInd) = 0;
+
+ % update offset
+ if delInd(3)
+ timeFacIndOffset = timeFacIndOffset+1;
+ else
+ timeFacIndOffset = timeFacIndOffset+2;
+ end
+
+ else
+ %These are the factors that relate the total time in the
+ %optimized arc sector to the total time in the previous and
+ %next dose sectors
+ stf(i).propVMAT.timeFac = zeros(1,2);
+
+ stf(i).propVMAT.timeFac(1) = stf(i).propVMAT.DAOAngleBorderCentreDiff(1)/stf(i).propVMAT.DAOAngleBordersDiff;
+ stf(i).propVMAT.timeFac(2) = stf(i).propVMAT.DAOAngleBorderCentreDiff(2)/stf(i).propVMAT.DAOAngleBordersDiff;
+ end
+
+ else
+ if ~isfield(stf(stf(i).propVMAT.beamParentIndex).propVMAT,'beamSubChildrenGantryAngles') || isempty(stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamSubChildrenGantryAngles)
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamSubChildren = 0;
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamSubChildrenGantryAngles = nan(1000,1);
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamSubChildrenIndex = nan(1000,1);
+ end
+
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamSubChildren = stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamSubChildren+1;
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamSubChildrenGantryAngles(stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamSubChildren) = this.gantryAngles(i);
+ stf(stf(i).propVMAT.beamParentIndex).propVMAT.beamSubChildrenIndex(stf(stf(i).propVMAT.beamParentIndex).propVMAT.numOfBeamSubChildren) = i;
+
+ stf(i).propVMAT.fracFromLastDAO = (this.gantryAngles(nextDAOIndex)-this.gantryAngles(i))./(this.gantryAngles(nextDAOIndex)-this.gantryAngles(lastDAOIndex));
+ stf(i).propVMAT.lastDAOIndex = lastDAOIndex;
+ stf(i).propVMAT.nextDAOIndex = nextDAOIndex;
+ end
+
+
+ if stf(i).propVMAT.FMOBeam
+ % FMOAngleBorders are the angular borders over which an optimized
+ % control point has influence
+ FMOIndex = find(abs(this.FMOGantryAngles - this.gantryAngles(i)) < 1e-8);
+
+ if FMOIndex == 1
+
+ stf(i).propVMAT.FMOAngleBorders = [this.startingAngle (this.FMOGantryAngles(FMOIndex+1)+this.FMOGantryAngles(FMOIndex))/2];
+ elseif FMOIndex == length(this.FMOGantryAngles)
+
+ stf(i).propVMAT.FMOAngleBorders = [(this.FMOGantryAngles(FMOIndex-1)+this.FMOGantryAngles(FMOIndex))/2 this.finishingAngle];
+ else
+
+ stf(i).propVMAT.FMOAngleBorders = ([this.FMOGantryAngles(FMOIndex-1) this.FMOGantryAngles(FMOIndex+1)]+this.FMOGantryAngles(FMOIndex))/2;
+ end
+ stf(i).propVMAT.FMOAngleBorderCentreDiff = [stf(i).gantryAngle-stf(i).propVMAT.FMOAngleBorders(1) stf(i).propVMAT.FMOAngleBorders(2)-stf(i).gantryAngle];
+ stf(i).propVMAT.FMOAngleBordersDiff = sum(stf(i).propVMAT.FMOAngleBorderCentreDiff);
+ end
+
+ %% transformation of union of rays
+
+ stf(i).numOfRays = size(masterRayPosBEV,1);
+ stf(i).numOfBixelsPerRay = ones(1,stf(i).numOfRays);
+ stf(i).totalNumOfBixels = sum(stf(i).numOfBixelsPerRay);
+
+
+ % source position in bev
+ stf(i).sourcePoint_bev = [0 -SAD 0];
+
+ % get (active) rotation matrix
+ % transpose matrix because we are working with row vectors
+ rotMat_vectors_T = transpose(matRad_getRotationMatrix(this.gantryAngles(i),this.couchAngles(i)));
+
+ stf(i).sourcePoint = stf(i).sourcePoint_bev*rotMat_vectors_T;
+
+ % Save ray and target position in lps system.
+ for j = 1:stf(i).numOfRays
+ stf(i).ray(j).rayPos_bev = masterRayPosBEV(j,:);
+ stf(i).ray(j).targetPoint_bev = masterTargetPointBEV(j,:);
+
+ stf(i).ray(j).rayPos = stf(i).ray(j).rayPos_bev*rotMat_vectors_T;
+ stf(i).ray(j).targetPoint = stf(i).ray(j).targetPoint_bev*rotMat_vectors_T;
+
+ stf(i).ray(j).rayCorners_SCD = (repmat([0, this.machine.meta.SCD - SAD, 0],4,1)+ (this.machine.meta.SCD/SAD) * ...
+ [masterRayPosBEV(j,:) + [+stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];...
+ masterRayPosBEV(j,:) + [-stf(i).bixelWidth/2,0,+stf(i).bixelWidth/2];...
+ masterRayPosBEV(j,:) + [-stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2];...
+ masterRayPosBEV(j,:) + [+stf(i).bixelWidth/2,0,-stf(i).bixelWidth/2]])*rotMat_vectors_T;
+ end
+
+ % loop over all rays to determine meta information for each ray
+ stf(i).totalNumOfBixels = sum(stf(i).numOfBixelsPerRay);
+ stf(i).numOfBixelsPerRay = ones(1,stf(i).numOfRays);
+
+ for j = stf(i).numOfRays:-1:1
+
+ % find appropriate energies for particles
+ if strcmp(stf(i).radiationMode,'photons')
+
+ % book keeping for photons
+ stf(i).ray(j).energy = this.machine.data.energy;
+ else
+ matRad_cfg.dispError('Error generating stf struct: invalid radiation modality for VMAT.');
+ end
+ end
+
+ matRad_progress(i,length(this.gantryAngles));
+ end
+
+
+ %% final cleanup and calculation of factors we couldn't calc before
+ matRad_cfg.dispInfo('VMAT post-processing (2/2)... ');
+
+ for i = 1:length(this.gantryAngles)
+ if stf(i).propVMAT.FMOBeam
+ %remove NaNs from beamChildren and beamSubChildren
+ if isfield(stf(i).propVMAT,'beamChildrenGantryAngles')
+ stf(i).propVMAT.beamChildrenGantryAngles(isnan(stf(i).propVMAT.beamChildrenGantryAngles)) = [];
+ stf(i).propVMAT.beamChildrenIndex(isnan(stf(i).propVMAT.beamChildrenIndex)) = [];
+ else
+ stf(i).propVMAT.numOfBeamChildren = 0;
+ end
+ if isfield(stf(i).propVMAT,'beamSubChildrenGantryAngles')
+ stf(i).propVMAT.beamSubChildrenGantryAngles(isnan(stf(i).propVMAT.beamSubChildrenGantryAngles)) = [];
+ stf(i).propVMAT.beamSubChildrenIndex(isnan(stf(i).propVMAT.beamSubChildrenIndex)) = [];
+ else
+ stf(i).propVMAT.numOfBeamSubChildren = 0;
+ end
+ end
+
+ if stf(i).propVMAT.DAOBeam
+ if this.continuousAperture
+
+ stf(i).propVMAT.doseAngleDAO = ones(1,2);
+
+ if sum(DAODoseAngleBorders == stf(i).propVMAT.doseAngleBorders(2)) > 1
+ %final dose angle is repeated
+ %do not count twice in optimization
+ stf(i).propVMAT.doseAngleDAO(2) = 0;
+ end
+ end
+ end
+
+ if ~stf(i).propVMAT.FMOBeam && ~stf(i).propVMAT.DAOBeam
+
+ % for leaf position interpolation
+ stf(i).propVMAT.fracFromLastDAO_I = (stf(stf(i).propVMAT.nextDAOIndex).propVMAT.doseAngleBorders(1)-stf(i).propVMAT.doseAngleBorders(1))./(stf(stf(i).propVMAT.nextDAOIndex).propVMAT.doseAngleBorders(1)-stf(stf(i).propVMAT.lastDAOIndex).propVMAT.doseAngleBorders(2));
+ stf(i).propVMAT.fracFromLastDAO_F = (stf(stf(i).propVMAT.nextDAOIndex).propVMAT.doseAngleBorders(1)-stf(i).propVMAT.doseAngleBorders(2))./(stf(stf(i).propVMAT.nextDAOIndex).propVMAT.doseAngleBorders(1)-stf(stf(i).propVMAT.lastDAOIndex).propVMAT.doseAngleBorders(2));
+ stf(i).propVMAT.fracFromNextDAO_I = (stf(i).propVMAT.doseAngleBorders(1)-stf(stf(i).propVMAT.lastDAOIndex).propVMAT.doseAngleBorders(2))./(stf(stf(i).propVMAT.nextDAOIndex).propVMAT.doseAngleBorders(1)-stf(stf(i).propVMAT.lastDAOIndex).propVMAT.doseAngleBorders(2));
+ stf(i).propVMAT.fracFromNextDAO_F = (stf(i).propVMAT.doseAngleBorders(2)-stf(stf(i).propVMAT.lastDAOIndex).propVMAT.doseAngleBorders(2))./(stf(stf(i).propVMAT.nextDAOIndex).propVMAT.doseAngleBorders(1)-stf(stf(i).propVMAT.lastDAOIndex).propVMAT.doseAngleBorders(2));
+
+ % for time interpolation
+ stf(i).propVMAT.timeFracFromLastDAO = (stf(stf(i).propVMAT.lastDAOIndex).propVMAT.DAOAngleBorders(2)-stf(i).propVMAT.doseAngleBorders(1))./stf(i).propVMAT.doseAngleBordersDiff;
+ stf(i).propVMAT.timeFracFromNextDAO = (stf(i).propVMAT.doseAngleBorders(2)-stf(stf(i).propVMAT.lastDAOIndex).propVMAT.DAOAngleBorders(2))./stf(i).propVMAT.doseAngleBordersDiff;
+ if stf(i).propVMAT.timeFracFromLastDAO > 1
+ stf(i).propVMAT.timeFracFromLastDAO = 1;
+ elseif stf(i).propVMAT.timeFracFromLastDAO < 0
+ stf(i).propVMAT.timeFracFromLastDAO = 0;
+ end
+ if stf(i).propVMAT.timeFracFromNextDAO > 1
+ stf(i).propVMAT.timeFracFromNextDAO = 1;
+ elseif stf(i).propVMAT.timeFracFromNextDAO < 0
+ stf(i).propVMAT.timeFracFromNextDAO = 0;
+ end
+ end
+
+ matRad_progress(i,length(this.gantryAngles));
+ end
+
+ end
+ end
+
+ methods (Static)
+ function [available,msg] = isAvailable(pln,machine)
+ % see superclass for information
+
+ if nargin < 2
+ machine = matRad_loadMachine(pln);
+ end
+
+ % Check superclass availability
+ [available,msg] = matRad_StfGeneratorPhotonRayBixelAbstract.isAvailable(pln,machine);
+
+ if ~available
+ return;
+ else
+ available = false;
+ msg = [];
+ end
+
+ %checkBasic
+ try
+ checkBasic = isfield(machine,'meta') && isfield(machine,'data');
+
+ %check modality
+ checkModality = any(strcmp(matRad_StfGeneratorPhotonVMAT.possibleRadiationModes, machine.meta.radiationMode)) && any(strcmp(matRad_StfGeneratorPhotonVMAT.possibleRadiationModes, pln.radiationMode));
+
+ %Sanity check compatibility
+ if checkModality
+ checkModality = strcmp(machine.meta.radiationMode,pln.radiationMode);
+ end
+
+ preCheck = checkBasic && checkModality;
+
+ if ~preCheck
+ return;
+ end
+ catch
+ msg = 'Your machine file is invalid and does not contain the basic field (meta/data/radiationMode)!';
+ return;
+ end
+
+ available = preCheck;
+ end
+ end
+end
diff --git a/matRad/steering/matRad_VMATGantryAngles.m b/matRad/steering/matRad_VMATGantryAngles.m
new file mode 100644
index 000000000..a91322e72
--- /dev/null
+++ b/matRad/steering/matRad_VMATGantryAngles.m
@@ -0,0 +1,126 @@
+function pln = matRad_VMATGantryAngles(pln)
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% matRad determine gantry angles for VMAT
+%
+% call
+% matRad_VMATGantryAngles(pln)
+%
+% input
+% pln: matRad plan meta information struct
+%
+% output
+% pln: matRad plan meta information struct
+%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%should have already defined fields:
+% maxGantryAngleSpacing
+% maxDAOGantryAngleSpacing
+% maxFMOGantryAngleSpacing
+% if any of these aren't defined, use corresponding DEFAULT value
+
+DEFAULT_GANTRY_SPACING = 4;
+DEFAULT_DAO_GANTRY_SPACING = 8;
+DEFAULT_FMO_GANTRY_SPACING = 32;
+
+if ~isfield(pln.propStf,'maxGantryAngleSpacing')
+ matRad_dispToConsole(['matRad_VMATGantryAngles: Using default value pln.propStf.maxGantryAngleSpacing = ' num2str(DEFAULT_GANTRY_SPACING) ' degree. \n'],[],'warning');
+ pln.propStf.maxGantryAngleSpacing = DEFAULT_GANTRY_SPACING; % upper bound for gantry angle spacing for dose calculation
+end
+
+if ~isfield(pln.propStf,'maxDAOGantryAngleSpacing')
+ matRad_dispToConsole(['matRad_VMATGantryAngles: Using default value pln.propStf.maxDAOGantryAngleSpacing= ' num2str(DEFAULT_DAO_GANTRY_SPACING) ' degree. \n'],[],'warning');
+ pln.propStf.maxDAOGantryAngleSpacing = DEFAULT_DAO_GANTRY_SPACING; % upper bound for gantry angle spacing for DAO
+end
+
+if ~isfield(pln.propStf,'maxFMOGantryAngleSpacing')
+ matRad_dispToConsole(['matRad_VMATGantryAngles: Using default value pln.propStf.maxFMOGantryAngleSpacing= ' num2str(DEFAULT_FMO_GANTRY_SPACING) ' degree. \n'],[],'warning');
+ pln.propStf.maxFMOGantryAngleSpacing = DEFAULT_FMO_GANTRY_SPACING; % upper bound for gantry angle spacing for FMO
+end
+
+angularRange = abs(pln.propStf.finishingAngle-pln.propStf.startingAngle);
+
+if pln.propStf.continuousAperture
+
+ % angularRange = gantryAngleSpacing*numGantryAngles
+ % ensure that gantryAngleSpacing < maxGantryAngleSpacing (as close as
+ % possible)
+ numGantryAngles = ceil(angularRange./pln.propStf.maxGantryAngleSpacing);
+ gantryAngleSpacing = angularRange./numGantryAngles;
+
+ % (numDAOGantryAngles-1)*DAOGantryAngleSpacing = (numGantryAngles-1)*gantryAngleSpacing
+ % where
+ % ensure that DAOGantryAngleSpacing < maxDAOGantryAngleSpacing (as close as
+ % possible)
+ numDAOGantryAngles = ceil((numGantryAngles-1).*gantryAngleSpacing./pln.propStf.maxDAOGantryAngleSpacing)+1;
+ % now ensure that numGantryAngles-1 is a multiple of numDAOGantryAngles-1 so
+ % that they align
+ numGantryAngles = (numDAOGantryAngles-1).*ceil((numGantryAngles-1)./(numDAOGantryAngles-1))+1;
+ gantryAngleSpacing = angularRange./numGantryAngles;
+ DAOGantryAngleSpacing = (angularRange-gantryAngleSpacing)/(numDAOGantryAngles-1);
+
+ % first and last gantry angles are in centre of arc
+ firstGantryAngle = pln.propStf.startingAngle+gantryAngleSpacing/2;
+ lastGantryAngle = pln.propStf.finishingAngle-gantryAngleSpacing/2;
+
+else
+
+ % ensure that an integer number of DAO gantry angles will fit in angularRange degrees,
+ % spaced at least as close as maxDAOGantryAngleSpacing
+ numDAOGantryAngles = ceil(angularRange/pln.propStf.maxDAOGantryAngleSpacing);
+ DAOGantryAngleSpacing = angularRange/numDAOGantryAngles;
+ % actual number of gantry angles is numDAOGantryAngles+1;
+ % ensure that DAOGantryAngleSpacing is an integer multiple of gantryAngleSpacing
+ numGantryAngles = ceil(numDAOGantryAngles*DAOGantryAngleSpacing/pln.propStf.maxGantryAngleSpacing);
+ gantryAngleSpacing = angularRange/numGantryAngles;
+
+ % first and last gantry angles are at beginning and end of arc
+ firstGantryAngle = pln.propStf.startingAngle;
+ lastGantryAngle = pln.propStf.finishingAngle;
+end
+
+% ensure that FMOGantryAngleSpacing is an odd integer multiple of DAOGantryAngleSpacing
+numApertures = floor(pln.propStf.maxFMOGantryAngleSpacing/DAOGantryAngleSpacing);
+if mod(numApertures,2) == 0
+ numApertures = numApertures-1;
+end
+FMOGantryAngleSpacing = numApertures*DAOGantryAngleSpacing;
+
+firstFMOGantryAngle = firstGantryAngle+DAOGantryAngleSpacing*floor(numApertures/2);
+lastFMOGantryAngle = lastGantryAngle-DAOGantryAngleSpacing*floor(numApertures/2);
+
+% define angles
+pln.propStf.gantryAngles = firstGantryAngle:gantryAngleSpacing:lastGantryAngle;
+pln.propStf.DAOGantryAngles = firstGantryAngle:DAOGantryAngleSpacing:lastGantryAngle;
+pln.propStf.FMOGantryAngles = firstFMOGantryAngle:FMOGantryAngleSpacing:lastFMOGantryAngle;
+
+% everything else
+pln.propStf.numOfBeams = numel(pln.propStf.gantryAngles);
+pln.propStf.couchAngles = ones(1,pln.propStf.numOfBeams) * pln.propStf.couchAngle;
+pln.propStf.isoCenter = ones(pln.propStf.numOfBeams,1) * pln.propStf.isoCenter;
+
+
+%[ixMember,~] = ismember(pln.propStf.gantryAngles,pln.propStf.DAOGantryAngles);
+%if sum(ixMember) <= 2
+
+% TODO: use updated warning message functions
+% This should give the expected behaviour according to the warning message.
+% if numel(pln.propStf.FMOGantryAngles) <= 2
+% matRad_dispToConsole('matRad_VMATGantryAngles: Two or fewer beam angles will be used for FMO. \n',[],'warning')
+% end
+
+pln.propStf.generator = 'PhotonVMAT';
diff --git a/matRad/util/matRad_visApertureInfo.m b/matRad/util/matRad_visApertureInfo.m
index 2647d728a..5cc1c1308 100644
--- a/matRad/util/matRad_visApertureInfo.m
+++ b/matRad/util/matRad_visApertureInfo.m
@@ -42,20 +42,36 @@ function matRad_visApertureInfo(apertureInfo,mode)
color(:,3) = 0;
color(:,2) = 0;
-% loop over all beams
-for i=1:numOfBeams
+if apertureInfo.runVMAT
+ % if doing VMAT, let wMax be the max weight across ALL angles
+ wMax = 0;
+ for i=1:numOfBeams
+ if wMax <= apertureInfo.beam(i).shape(1).weight
+ wMax = apertureInfo.beam(i).shape(1).weight;
+ end
+ end
+end
+
+for i=1:numOfBeams
+
+ numOfShapes = numel(apertureInfo.beam(i).shape);
+
% open new figure for every beam
figure('units','inches')
-
+
% get the MLC dimensions for this beam
minX = apertureInfo.beam(i).MLCWindow(1);
- maxX = apertureInfo.beam(i).MLCWindow(2);
+ maxX = apertureInfo.beam(i).MLCWindow(2);
- %get maximum weight
- wMax = max([apertureInfo.beam(i).shape(:).weight]);
+ if ~apertureInfo.runVMAT
+ % if not VMAT, let wMax be the max weight of a particular angle
+ if numOfShapes;
+ wMax = max([apertureInfo.beam(i).shape(:).weight]);
+ end
+ end
if strcmp(mode,'leafNum')
-
+
% get the active leaf Pairs
% the leaf indices have to be flipped in order to fit to the order of
% the leaf positions (1st row of leafPos is lowest row in physical
@@ -63,13 +79,16 @@ function matRad_visApertureInfo(apertureInfo,mode)
activeLeafInd = flipud(find(apertureInfo.beam(i).isActiveLeafPair));
end
- subplotColumns = ceil(apertureInfo.beam(i).numOfShapes/2);
- subplotLines = ceil(apertureInfo.beam(i).numOfShapes/subplotColumns);
+ %subplotColumns = ceil(apertureInfo.beam(i).numOfShapes/2);
+ %subplotLines = ceil(apertureInfo.beam(i).numOfShapes/subplotColumns);
+ subplotColumns = ceil(numOfShapes/2);
+ subplotLines = ceil(numOfShapes/subplotColumns);
+
%adjust figure position
set(gcf,'pos',[0 0 1.8*subplotColumns 3*subplotLines])
- % loop over all shapes of the beam
- for j = 1:apertureInfo.beam(i).numOfShapes
+ % loop over all shapes of the beam
+ for j = 1:numOfShapes
% creating subplots
subplot(subplotLines,subplotColumns,j)
@@ -78,6 +97,7 @@ function matRad_visApertureInfo(apertureInfo,mode)
num2str(apertureInfo.beam(i).shape(j).weight,2)],...
'Fontsize',8)
colorInd = max(ceil((apertureInfo.beam(i).shape(j).weight/wMax)*61+eps),1);
+
set(gca,'Color',color(colorInd,:));
hold on
@@ -86,18 +106,18 @@ function matRad_visApertureInfo(apertureInfo,mode)
% loop over all active leaf pairs
for k = 1:apertureInfo.beam(i).numOfActiveLeafPairs
fill([minX apertureInfo.beam(i).shape(j).leftLeafPos(k) ...
- apertureInfo.beam(i).shape(j).leftLeafPos(k) minX],...
- [apertureInfo.beam(i).leafPairPos(k)- bixelWidth/2 ...
- apertureInfo.beam(i).leafPairPos(k)- bixelWidth/2 ...
- apertureInfo.beam(i).leafPairPos(k)+ bixelWidth/2 ...
- apertureInfo.beam(i).leafPairPos(k)+ bixelWidth/2],'b')
- fill([apertureInfo.beam(i).shape(j).rightLeafPos(k) ...
- maxX maxX ...
- apertureInfo.beam(i).shape(j).rightLeafPos(k)],...
- [apertureInfo.beam(i).leafPairPos(k)- bixelWidth/2 ...
- apertureInfo.beam(i).leafPairPos(k)- bixelWidth/2 ...
- apertureInfo.beam(i).leafPairPos(k)+ bixelWidth/2 ...
- apertureInfo.beam(i).leafPairPos(k)+ bixelWidth/2],'b')
+ apertureInfo.beam(i).shape(j).leftLeafPos(k) minX],...
+ [apertureInfo.beam(i).leafPairPos(k)- bixelWidth/2 ...
+ apertureInfo.beam(i).leafPairPos(k)- bixelWidth/2 ...
+ apertureInfo.beam(i).leafPairPos(k)+ bixelWidth/2 ...
+ apertureInfo.beam(i).leafPairPos(k)+ bixelWidth/2],[0.5 0.5 0.5])
+ fill([apertureInfo.beam(i).shape(j).rightLeafPos(k) ...
+ maxX maxX ...
+ apertureInfo.beam(i).shape(j).rightLeafPos(k)],...
+ [apertureInfo.beam(i).leafPairPos(k)- bixelWidth/2 ...
+ apertureInfo.beam(i).leafPairPos(k)- bixelWidth/2 ...
+ apertureInfo.beam(i).leafPairPos(k)+ bixelWidth/2 ...
+ apertureInfo.beam(i).leafPairPos(k)+ bixelWidth/2],[0.5 0.5 0.5])
end
elseif strcmp(mode,'leafNum')
% loop over all active leaf pairs
@@ -107,28 +127,28 @@ function matRad_visApertureInfo(apertureInfo,mode)
[activeLeafInd(k) - 1/2 ...
activeLeafInd(k) - 1/2 ...
activeLeafInd(k) + 1/2 ...
- activeLeafInd(k) + 1/2],'b')
+ activeLeafInd(k) + 1/2],[0.5 0.5 0.5])
fill([apertureInfo.beam(i).shape(j).rightLeafPos(k) ...
maxX maxX ...
apertureInfo.beam(i).shape(j).rightLeafPos(k)],...
[activeLeafInd(k) - 1/2 ...
activeLeafInd(k) - 1/2 ...
activeLeafInd(k) + 1/2 ...
- activeLeafInd(k) + 1/2],'b')
+ activeLeafInd(k) + 1/2],[0.5 0.5 0.5])
end
end
-
+
axis tight
xlabel('horiz. pos. [mm]')
-
+
if strcmp(mode,'physical')
ylabel('vert. pos. [mm]')
elseif strcmp(mode,'leafNum')
ylabel('leaf pair #')
end
-
+
end
-
+
end
end
diff --git a/test/sequencing/test_engelLeafSequencing.m b/test/sequencing/test_engelLeafSequencing.m
index 98e0378fe..f8b5819e4 100644
--- a/test/sequencing/test_engelLeafSequencing.m
+++ b/test/sequencing/test_engelLeafSequencing.m
@@ -12,21 +12,23 @@
% Test Case, and add them to the test-runner
initTestSuite;
- function [resultGUI,stf,dij] = helper_getTestData()
+ function [resultGUI,stf,dij,pln] = helper_getTestData()
p = load('photons_testData.mat');
resultGUI = p.resultGUI;
stf = p.stf;
dij = p.dij;
+ pln = p.pln;
function test_run_sequencing_basic
- [resultGUI,stf,dij] = helper_getTestData();
+ [resultGUI,stf,dij,pln] = helper_getTestData();
fn_old = fieldnames(resultGUI);
numOfLevels = [1,10];
for levels = numOfLevels
- resultGUI_sequenced = matRad_engelLeafSequencing(resultGUI,stf,dij,levels);
+ pln.propSeq.numLevels = levels;
+ resultGUI_sequenced = matRad_engelLeafSequencing(resultGUI,stf,dij,pln);
fn_new = fieldnames(resultGUI_sequenced);
for i = 1:numel(fn_old)
diff --git a/test/sequencing/test_siochiLeafSequencing.m b/test/sequencing/test_siochiLeafSequencing.m
index af6e91859..3ed020a45 100644
--- a/test/sequencing/test_siochiLeafSequencing.m
+++ b/test/sequencing/test_siochiLeafSequencing.m
@@ -12,21 +12,23 @@
% Test Case, and add them to the test-runner
initTestSuite;
- function [resultGUI,stf,dij] = helper_getTestData()
+function [resultGUI,stf,dij,pln] = helper_getTestData()
p = load('photons_testData.mat');
resultGUI = p.resultGUI;
stf = p.stf;
dij = p.dij;
+ pln = p.pln;
function test_run_sequencing_basic
- [resultGUI,stf,dij] = helper_getTestData();
+ [resultGUI,stf,dij,pln] = helper_getTestData();
fn_old = fieldnames(resultGUI);
- numOfLevels = [1,10];
+ numOfLevels = [1,10];
for levels = numOfLevels
- resultGUI_sequenced = matRad_siochiLeafSequencing(resultGUI,stf,dij,levels);
+ pln.propSeq.numLevels = levels;
+ resultGUI_sequenced = matRad_siochiLeafSequencing(resultGUI,stf,dij,pln);
fn_new = fieldnames(resultGUI_sequenced);
for i = 1:numel(fn_old)
diff --git a/test/sequencing/test_xiaLeafSequencing.m b/test/sequencing/test_xiaLeafSequencing.m
index 9d6aaf943..f8dadf48e 100644
--- a/test/sequencing/test_xiaLeafSequencing.m
+++ b/test/sequencing/test_xiaLeafSequencing.m
@@ -12,21 +12,23 @@
% Test Case, and add them to the test-runner
initTestSuite;
-function [resultGUI,stf,dij] = helper_getTestData()
+function [resultGUI,stf,dij,pln] = helper_getTestData()
p = load('photons_testData.mat');
resultGUI = p.resultGUI;
stf = p.stf;
dij = p.dij;
+ pln = p.pln;
function test_run_sequencing_basic
- [resultGUI,stf,dij] = helper_getTestData();
+ [resultGUI,stf,dij,pln] = helper_getTestData();
fn_old = fieldnames(resultGUI);
numOfLevels = [1,10];
for levels = numOfLevels
- resultGUI_sequenced = matRad_xiaLeafSequencing(resultGUI,stf,dij,levels);
+ pln.propSeq.numLevels = levels;
+ resultGUI_sequenced = matRad_xiaLeafSequencing(resultGUI,stf,dij,pln);
fn_new = fieldnames(resultGUI_sequenced);
for i = 1:numel(fn_old)